From ad0943f88ba9f5ed6eae198ecb4835c0f44701de Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Wed, 17 Jun 2026 21:33:40 +0800 Subject: chore: initialize project structure and add core modules --- .cargo/config.toml | 7 ++ .gitignore | 5 + Cargo.lock | 87 +++++++++++++++++ Cargo.toml | 26 +++++ LICENSE | 21 ++++ rola-bucket/Cargo.toml | 8 ++ rola-bucket/src/lib.rs | 1 + rola-cli/Cargo.toml | 10 ++ rola-cli/src/bin/rola.rs | 1 + rola-cli/src/lib.rs | 1 + rola-desktop.sln | 18 ++++ rola-desktop/App.axaml | 15 +++ rola-desktop/App.axaml.cs | 47 +++++++++ rola-desktop/Assets/avalonia-logo.ico | Bin 0 -> 175875 bytes rola-desktop/Program.cs | 21 ++++ rola-desktop/ViewLocator.cs | 31 ++++++ rola-desktop/ViewModels/MainWindowViewModel.cs | 6 ++ rola-desktop/ViewModels/ViewModelBase.cs | 7 ++ rola-desktop/Views/MainWindow.axaml | 20 ++++ rola-desktop/Views/MainWindow.axaml.cs | 11 +++ rola-desktop/app.manifest | 18 ++++ rola-desktop/rola-desktop.csproj | 32 +++++++ rola-devtools/Cargo.lock | 7 ++ rola-devtools/Cargo.toml | 6 ++ rola-devtools/scripts/check-all.py | 38 ++++++++ rola-devtools/scripts/run-desktop.py | 11 +++ rola-devtools/scripts/windows-folder-hide.ps1 | 43 +++++++++ rola-devtools/src/bin/welcome.rs | 3 + rola-devtools/src/lib.rs | 1 + rola-draft/Cargo.toml | 8 ++ rola-draft/src/lib.rs | 1 + rola-utils/functions/Cargo.toml | 8 ++ rola-utils/functions/src/levenshtein_distance.rs | 117 +++++++++++++++++++++++ rola-utils/functions/src/lib.rs | 2 + rola-utils/macros/Cargo.toml | 14 +++ rola-utils/macros/src/lib.rs | 1 + rola-vcs/Cargo.toml | 13 +++ rola-vcs/internal_macros/Cargo.toml | 14 +++ rola-vcs/internal_macros/src/constants.rs | 115 ++++++++++++++++++++++ rola-vcs/internal_macros/src/lib.rs | 39 ++++++++ rola-vcs/src/consts/common.rs | 7 ++ rola-vcs/src/consts/mod.rs | 2 + rola-vcs/src/lib.rs | 11 +++ run-tools.ps1 | 60 ++++++++++++ run-tools.sh | 64 +++++++++++++ 45 files changed, 978 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 rola-bucket/Cargo.toml create mode 100644 rola-bucket/src/lib.rs create mode 100644 rola-cli/Cargo.toml create mode 100644 rola-cli/src/bin/rola.rs create mode 100644 rola-cli/src/lib.rs create mode 100644 rola-desktop.sln create mode 100644 rola-desktop/App.axaml create mode 100644 rola-desktop/App.axaml.cs create mode 100644 rola-desktop/Assets/avalonia-logo.ico create mode 100644 rola-desktop/Program.cs create mode 100644 rola-desktop/ViewLocator.cs create mode 100644 rola-desktop/ViewModels/MainWindowViewModel.cs create mode 100644 rola-desktop/ViewModels/ViewModelBase.cs create mode 100644 rola-desktop/Views/MainWindow.axaml create mode 100644 rola-desktop/Views/MainWindow.axaml.cs create mode 100644 rola-desktop/app.manifest create mode 100644 rola-desktop/rola-desktop.csproj create mode 100644 rola-devtools/Cargo.lock create mode 100644 rola-devtools/Cargo.toml create mode 100644 rola-devtools/scripts/check-all.py create mode 100644 rola-devtools/scripts/run-desktop.py create mode 100644 rola-devtools/scripts/windows-folder-hide.ps1 create mode 100644 rola-devtools/src/bin/welcome.rs create mode 100644 rola-devtools/src/lib.rs create mode 100644 rola-draft/Cargo.toml create mode 100644 rola-draft/src/lib.rs create mode 100644 rola-utils/functions/Cargo.toml create mode 100644 rola-utils/functions/src/levenshtein_distance.rs create mode 100644 rola-utils/functions/src/lib.rs create mode 100644 rola-utils/macros/Cargo.toml create mode 100644 rola-utils/macros/src/lib.rs create mode 100644 rola-vcs/Cargo.toml create mode 100644 rola-vcs/internal_macros/Cargo.toml create mode 100644 rola-vcs/internal_macros/src/constants.rs create mode 100644 rola-vcs/internal_macros/src/lib.rs create mode 100644 rola-vcs/src/consts/common.rs create mode 100644 rola-vcs/src/consts/mod.rs create mode 100644 rola-vcs/src/lib.rs create mode 100644 run-tools.ps1 create mode 100644 run-tools.sh diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..11eebcf --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,7 @@ +[build] +target-dir = "./.temp/target" + +[env] + +[alias] +rola-doc = "doc --workspace --no-deps --open" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2ecd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.idea +.temp +rola-desktop/obj +rola-desktop/bin +rola-devtools/scripts/last_check diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..edb6412 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,87 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rola-bucket" +version = "0.1.0" + +[[package]] +name = "rola-cli" +version = "0.1.0" +dependencies = [ + "shared_functions", + "shared_macros", +] + +[[package]] +name = "rola-draft" +version = "0.1.0" + +[[package]] +name = "rorolala" +version = "0.1.0" +dependencies = [ + "rola-bucket", + "rola-draft", + "rorolala_internal_macros", + "shared_functions", + "shared_macros", +] + +[[package]] +name = "rorolala_internal_macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shared_functions" +version = "0.1.0" + +[[package]] +name = "shared_macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7f14aac --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,26 @@ +[workspace] +resolver = "2" +members = [ + "rola-bucket", + "rola-cli", + "rola-vcs/internal_macros", + "rola-utils/functions", + "rola-utils/macros", + "rola-draft", + "rola-vcs", +] +exclude = ["rola-devtools"] + +[workspace.package] +version = "0.1.0" +edition = "2024" +authors = ["RoroLalaVCS", "Weicao-CatilGrass "] +license = "MIT" + +[workspace.dependencies] +rorolala = { path = "rola-vcs" } +rorolala_internal_macros = { path = "rola-vcs/internal_macros" } +shared_functions = { path = "rola-utils/functions" } +shared_macros = { path = "rola-utils/macros" } +rola-bucket = { path = "rola-bucket" } +rola-draft = { path = "rola-draft" } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..96889a1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 RoroLala VCS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/rola-bucket/Cargo.toml b/rola-bucket/Cargo.toml new file mode 100644 index 0000000..85d7fab --- /dev/null +++ b/rola-bucket/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rola-bucket" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] diff --git a/rola-bucket/src/lib.rs b/rola-bucket/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/rola-bucket/src/lib.rs @@ -0,0 +1 @@ + diff --git a/rola-cli/Cargo.toml b/rola-cli/Cargo.toml new file mode 100644 index 0000000..9ee6816 --- /dev/null +++ b/rola-cli/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rola-cli" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] +shared_functions.workspace = true +shared_macros.workspace = true diff --git a/rola-cli/src/bin/rola.rs b/rola-cli/src/bin/rola.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/rola-cli/src/bin/rola.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/rola-cli/src/lib.rs b/rola-cli/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/rola-cli/src/lib.rs @@ -0,0 +1 @@ + diff --git a/rola-desktop.sln b/rola-desktop.sln new file mode 100644 index 0000000..8864f46 --- /dev/null +++ b/rola-desktop.sln @@ -0,0 +1,18 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "rola-desktop", "rola-desktop\rola-desktop.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/rola-desktop/App.axaml b/rola-desktop/App.axaml new file mode 100644 index 0000000..bd879fe --- /dev/null +++ b/rola-desktop/App.axaml @@ -0,0 +1,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/rola-desktop/App.axaml.cs b/rola-desktop/App.axaml.cs new file mode 100644 index 0000000..6b665bc --- /dev/null +++ b/rola-desktop/App.axaml.cs @@ -0,0 +1,47 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Data.Core; +using Avalonia.Data.Core.Plugins; +using System.Linq; +using Avalonia.Markup.Xaml; +using rola_desktop.ViewModels; +using rola_desktop.Views; + +namespace rola_desktop; + +public partial class App : Application +{ + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + // Avoid duplicate validations from both Avalonia and the CommunityToolkit. + // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins + DisableAvaloniaDataAnnotationValidation(); + desktop.MainWindow = new MainWindow + { + DataContext = new MainWindowViewModel(), + }; + } + + base.OnFrameworkInitializationCompleted(); + } + + private void DisableAvaloniaDataAnnotationValidation() + { + // Get an array of plugins to remove + var dataValidationPluginsToRemove = + BindingPlugins.DataValidators.OfType().ToArray(); + + // remove each entry found + foreach (var plugin in dataValidationPluginsToRemove) + { + BindingPlugins.DataValidators.Remove(plugin); + } + } +} \ No newline at end of file diff --git a/rola-desktop/Assets/avalonia-logo.ico b/rola-desktop/Assets/avalonia-logo.ico new file mode 100644 index 0000000..f7da8bb Binary files /dev/null and b/rola-desktop/Assets/avalonia-logo.ico differ diff --git a/rola-desktop/Program.cs b/rola-desktop/Program.cs new file mode 100644 index 0000000..30e9ca6 --- /dev/null +++ b/rola-desktop/Program.cs @@ -0,0 +1,21 @@ +using Avalonia; +using System; + +namespace rola_desktop; + +sealed class Program +{ + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace(); +} diff --git a/rola-desktop/ViewLocator.cs b/rola-desktop/ViewLocator.cs new file mode 100644 index 0000000..eec4713 --- /dev/null +++ b/rola-desktop/ViewLocator.cs @@ -0,0 +1,31 @@ +using System; +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using rola_desktop.ViewModels; + +namespace rola_desktop; + +public class ViewLocator : IDataTemplate +{ + + public Control? Build(object? param) + { + if (param is null) + return null; + + var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); + var type = Type.GetType(name); + + if (type != null) + { + return (Control)Activator.CreateInstance(type)!; + } + + return new TextBlock { Text = "Not Found: " + name }; + } + + public bool Match(object? data) + { + return data is ViewModelBase; + } +} diff --git a/rola-desktop/ViewModels/MainWindowViewModel.cs b/rola-desktop/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000..2db588d --- /dev/null +++ b/rola-desktop/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,6 @@ +namespace rola_desktop.ViewModels; + +public partial class MainWindowViewModel : ViewModelBase +{ + public string Greeting { get; } = "Welcome to Avalonia!"; +} diff --git a/rola-desktop/ViewModels/ViewModelBase.cs b/rola-desktop/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..f0aa7f1 --- /dev/null +++ b/rola-desktop/ViewModels/ViewModelBase.cs @@ -0,0 +1,7 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace rola_desktop.ViewModels; + +public class ViewModelBase : ObservableObject +{ +} diff --git a/rola-desktop/Views/MainWindow.axaml b/rola-desktop/Views/MainWindow.axaml new file mode 100644 index 0000000..6ed88af --- /dev/null +++ b/rola-desktop/Views/MainWindow.axaml @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/rola-desktop/Views/MainWindow.axaml.cs b/rola-desktop/Views/MainWindow.axaml.cs new file mode 100644 index 0000000..14f9599 --- /dev/null +++ b/rola-desktop/Views/MainWindow.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace rola_desktop.Views; + +public partial class MainWindow : Window +{ + public MainWindow() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/rola-desktop/app.manifest b/rola-desktop/app.manifest new file mode 100644 index 0000000..6454463 --- /dev/null +++ b/rola-desktop/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/rola-desktop/rola-desktop.csproj b/rola-desktop/rola-desktop.csproj new file mode 100644 index 0000000..5d6e1f8 --- /dev/null +++ b/rola-desktop/rola-desktop.csproj @@ -0,0 +1,32 @@ + + + WinExe + net9.0 + enable + true + app.manifest + true + + + + + + + + + + + + + + None + All + + + + diff --git a/rola-devtools/Cargo.lock b/rola-devtools/Cargo.lock new file mode 100644 index 0000000..f03c9e3 --- /dev/null +++ b/rola-devtools/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "rola-devtools" +version = "0.1.0" diff --git a/rola-devtools/Cargo.toml b/rola-devtools/Cargo.toml new file mode 100644 index 0000000..dd6e6d7 --- /dev/null +++ b/rola-devtools/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rola-devtools" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/rola-devtools/scripts/check-all.py b/rola-devtools/scripts/check-all.py new file mode 100644 index 0000000..cd2ddf1 --- /dev/null +++ b/rola-devtools/scripts/check-all.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 + +import subprocess +import sys +from pathlib import Path + +script_dir = Path(__file__).resolve().parent +root_dir = script_dir.parent.parent + +steps = [ + ("cargo check --workspace", [ + "cargo", "check", "--workspace", + ]), + ("cargo clippy --workspace -- -D warnings", [ + "cargo", "clippy", "--workspace", "--", "-D", "warnings", + ]), + ("cargo build --workspace --release", [ + "cargo", "build", "--workspace", "--release", + ]), + ("dotnet restore", [ + "dotnet", "restore", "rola-desktop.sln", + ]), + ("dotnet build", [ + "dotnet", "build", "rola-desktop.sln", "--nologo", + ]), +] + +ok = True +for label, cmd in steps: + print(f"\nSTEP - \"{label}\": ", flush=True) + result = subprocess.run(cmd, cwd=root_dir) + if result.returncode != 0: + print(f" [FAILED] Failed (exit code {result.returncode})", flush=True) + ok = False + break + print(f" [SUCCESS] Passed", flush=True) + +sys.exit(0 if ok else 1) diff --git a/rola-devtools/scripts/run-desktop.py b/rola-devtools/scripts/run-desktop.py new file mode 100644 index 0000000..e9d4093 --- /dev/null +++ b/rola-devtools/scripts/run-desktop.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 + +import subprocess +import sys +from pathlib import Path + +script_dir = Path(__file__).resolve().parent +project_dir = script_dir.parent.parent / "rola-desktop" +csproj = project_dir / "rola-desktop.csproj" + +subprocess.run(["dotnet", "run", "--project", str(csproj), *sys.argv[1:]]) diff --git a/rola-devtools/scripts/windows-folder-hide.ps1 b/rola-devtools/scripts/windows-folder-hide.ps1 new file mode 100644 index 0000000..0ab2632 --- /dev/null +++ b/rola-devtools/scripts/windows-folder-hide.ps1 @@ -0,0 +1,43 @@ +# Check `last_check` + +$lastCheckFile = Join-Path $PSScriptRoot "last_check" +$currentTime = Get-Date +$timeThreshold = 10 + +if (Test-Path $lastCheckFile) { + $lastCheckTime = Get-Content $lastCheckFile | Get-Date + $timeDiff = ($currentTime - $lastCheckTime).TotalMinutes + + if ($timeDiff -lt $timeThreshold) { + exit + } +} + +$currentTime.ToString() | Out-File -FilePath $lastCheckFile -Force + +# Hide Files + +Set-Location -Path (Join-Path $PSScriptRoot "..\..") + +Get-ChildItem -Path . -Force -Recurse -ErrorAction SilentlyContinue | Where-Object { + $_.FullName -notmatch '\\.temp\\' -and $_.FullName -notmatch '\\.git\\' +} | ForEach-Object { + attrib -h $_.FullName 2>&1 | Out-Null +} + +Get-ChildItem -Path . -Force -Recurse -ErrorAction SilentlyContinue | Where-Object { + $_.Name -match '^\..*' -and $_.FullName -notmatch '\\\.\.$' -and $_.FullName -notmatch '\\\.$' +} | ForEach-Object { + attrib +h $_.FullName 2>&1 | Out-Null +} + +if (Get-Command git -ErrorAction SilentlyContinue) { + git status --ignored --short | ForEach-Object { + if ($_ -match '^!!\s+(.+)$') { + $ignoredPath = $matches[1] + if ($ignoredPath -notmatch '\.lnk$' -and (Test-Path $ignoredPath)) { + attrib +h $ignoredPath 2>&1 | Out-Null + } + } + } +} diff --git a/rola-devtools/src/bin/welcome.rs b/rola-devtools/src/bin/welcome.rs new file mode 100644 index 0000000..3b1982f --- /dev/null +++ b/rola-devtools/src/bin/welcome.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Welcome!"); +} diff --git a/rola-devtools/src/lib.rs b/rola-devtools/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/rola-devtools/src/lib.rs @@ -0,0 +1 @@ + diff --git a/rola-draft/Cargo.toml b/rola-draft/Cargo.toml new file mode 100644 index 0000000..af5edc6 --- /dev/null +++ b/rola-draft/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rola-draft" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] diff --git a/rola-draft/src/lib.rs b/rola-draft/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/rola-draft/src/lib.rs @@ -0,0 +1 @@ + diff --git a/rola-utils/functions/Cargo.toml b/rola-utils/functions/Cargo.toml new file mode 100644 index 0000000..dd482c4 --- /dev/null +++ b/rola-utils/functions/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "shared_functions" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] diff --git a/rola-utils/functions/src/levenshtein_distance.rs b/rola-utils/functions/src/levenshtein_distance.rs new file mode 100644 index 0000000..7b8be6c --- /dev/null +++ b/rola-utils/functions/src/levenshtein_distance.rs @@ -0,0 +1,117 @@ +pub struct LevenshteinDistance; + +impl LevenshteinDistance { + pub fn filter_similar<'a>(str: &str, slice: &[&'a str], threshold: usize) -> Vec<&'a str> { + slice + .iter() + .filter(|s| levenshtein_distance(str, s) <= threshold) + .copied() + .collect() + } + + pub fn compare(a: impl AsRef, b: impl AsRef) -> usize { + let a = a.as_ref(); + let b = b.as_ref(); + levenshtein_distance(a, b) + } +} + +fn levenshtein_distance(a: &str, b: &str) -> usize { + let a_chars: Vec = a.chars().collect(); + let b_chars: Vec = b.chars().collect(); + let m = a_chars.len(); + let n = b_chars.len(); + if m == 0 { + return n; + } + if n == 0 { + return m; + } + + let mut prev: Vec = (0..=n).collect(); + let mut curr = vec![0; n + 1]; + for (i, ca) in a_chars.iter().enumerate() { + curr[0] = i + 1; + for (j, cb) in b_chars.iter().enumerate() { + let cost = if ca == cb { 0 } else { 1 }; + let del = prev[j + 1] + 1; + let ins = curr[j] + 1; + let rep = prev[j] + cost; + curr[j + 1] = del.min(ins).min(rep); + } + std::mem::swap(&mut prev, &mut curr); + } + prev[n] +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_levenshtein_distance_identical_strings() { + assert_eq!(LevenshteinDistance::compare("hello", "hello"), 0); + } + + #[test] + fn test_levenshtein_distance_completely_different() { + let dist = LevenshteinDistance::compare("abc", "xyz"); + assert_eq!(dist, 3); + } + + #[test] + fn test_levenshtein_distance_one_insertion() { + assert_eq!(LevenshteinDistance::compare("cat", "cats"), 1); + } + + #[test] + fn test_levenshtein_distance_one_deletion() { + assert_eq!(LevenshteinDistance::compare("dogs", "dog"), 1); + } + + #[test] + fn test_levenshtein_distance_one_substitution() { + assert_eq!(LevenshteinDistance::compare("cat", "cut"), 1); + } + + #[test] + fn test_levenshtein_distance_empty_strings() { + assert_eq!(LevenshteinDistance::compare("", ""), 0); + } + + #[test] + fn test_levenshtein_distance_empty_vs_nonempty() { + assert_eq!(LevenshteinDistance::compare("", "abc"), 3); + assert_eq!(LevenshteinDistance::compare("xyz", ""), 3); + } + + #[test] + fn test_levenshtein_distance_unicode() { + // Chinese characters + assert_eq!(LevenshteinDistance::compare("你好", "你好"), 0); + assert_eq!(LevenshteinDistance::compare("你好", "您好"), 1); + assert_eq!(LevenshteinDistance::compare("你好", "您不好"), 2); + } + + #[test] + fn test_filter_similar_exact_threshold() { + let words = vec!["cat", "cart", "ca", "cats", "cut"]; + let result = LevenshteinDistance::filter_similar("cat", &words, 2); + // cat -> cat: 0, cat -> cart: 1, cat -> ca: 1, cat -> cats: 1, cat -> cut: 1 + assert_eq!(result.len(), 5); + } + + #[test] + fn test_filter_similar_empty_slice() { + let words: Vec<&str> = vec![]; + let result = LevenshteinDistance::filter_similar("hello", &words, 3); + assert!(result.is_empty()); + } + + #[test] + fn test_filter_similar_threshold_zero() { + let words = vec!["hello", "hallo", "hello!"]; + let result = LevenshteinDistance::filter_similar("hello", &words, 0); + assert_eq!(result, vec!["hello"]); + } +} diff --git a/rola-utils/functions/src/lib.rs b/rola-utils/functions/src/lib.rs new file mode 100644 index 0000000..ff2ee7e --- /dev/null +++ b/rola-utils/functions/src/lib.rs @@ -0,0 +1,2 @@ +mod levenshtein_distance; +pub use levenshtein_distance::*; diff --git a/rola-utils/macros/Cargo.toml b/rola-utils/macros/Cargo.toml new file mode 100644 index 0000000..f750afa --- /dev/null +++ b/rola-utils/macros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "shared_macros" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2.0", features = ["full", "visit-mut"] } +quote = "1.0" +proc-macro2 = "1.0" diff --git a/rola-utils/macros/src/lib.rs b/rola-utils/macros/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/rola-utils/macros/src/lib.rs @@ -0,0 +1 @@ + diff --git a/rola-vcs/Cargo.toml b/rola-vcs/Cargo.toml new file mode 100644 index 0000000..ce32c14 --- /dev/null +++ b/rola-vcs/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rorolala" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true + +[dependencies] +rorolala_internal_macros.workspace = true +shared_functions.workspace = true +shared_macros.workspace = true +rola-bucket.workspace = true +rola-draft.workspace = true diff --git a/rola-vcs/internal_macros/Cargo.toml b/rola-vcs/internal_macros/Cargo.toml new file mode 100644 index 0000000..0d6f641 --- /dev/null +++ b/rola-vcs/internal_macros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "rorolala_internal_macros" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2", features = ["full"] } +quote = "1" +proc-macro2 = "1" diff --git a/rola-vcs/internal_macros/src/constants.rs b/rola-vcs/internal_macros/src/constants.rs new file mode 100644 index 0000000..2e76bfe --- /dev/null +++ b/rola-vcs/internal_macros/src/constants.rs @@ -0,0 +1,115 @@ +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{Expr, Item, ItemConst, ItemMod, Lit, parse_macro_input, parse_quote}; + +/// Entry point called from lib.rs. +pub fn expand(_attr: TokenStream, item: TokenStream) -> TokenStream { + let mut input_mod = parse_macro_input!(item as ItemMod); + + let (_, items) = match &mut input_mod.content { + Some(content) => content, + None => panic!("#[constants] can only be applied to a module with a body"), + }; + + let mut new_items: Vec = Vec::with_capacity(items.len()); + + for item in items.iter() { + if let Item::Const(const_item) = item { + let func = transform_const(const_item); + new_items.push(func); + } else { + new_items.push(item.clone()); + } + } + + let mod_ident = &input_mod.ident; + let vis = &input_mod.vis; + + let output = quote! { + #[allow(non_snake_case)] + #vis mod #mod_ident { + #(#new_items)* + } + }; + + output.into() +} + +/// Transforms a single `const` item into a function. +fn transform_const(const_item: &ItemConst) -> Item { + let name = &const_item.ident; + let attrs = &const_item.attrs; + + // Extract the string literal value from the const + let value_str = match &*const_item.expr { + Expr::Lit(expr_lit) => match &expr_lit.lit { + Lit::Str(lit_str) => lit_str.value(), + _ => panic!( + "#[constants] only supports `&str` literals, \ + but `{name}` has a non-string literal" + ), + }, + _ => panic!( + "#[constants] only supports literal expressions, \ + but `{name}` has a non-literal expression" + ), + }; + + let placeholders = extract_placeholders(&value_str); + + if placeholders.is_empty() { + parse_quote! { + #(#attrs)* + pub fn #name() -> String { + #value_str.to_string() + } + } + } else { + let params: Vec<_> = placeholders + .iter() + .map(|p| { + let ident = format_ident!("{p}"); + quote! { #ident: impl ::core::convert::AsRef } + }) + .collect(); + + let format_args: Vec<_> = placeholders + .iter() + .map(|p| { + let ident = format_ident!("{p}"); + quote! { #ident = #ident.as_ref() } + }) + .collect(); + + parse_quote! { + #(#attrs)* + pub fn #name(#(#params),*) -> String { + ::std::format!(#value_str, #(#format_args),*) + } + } + } +} + +/// Extracts all `{name}` placeholder identifiers from a format string. +fn extract_placeholders(s: &str) -> Vec { + let mut placeholders = Vec::new(); + let mut chars = s.char_indices().peekable(); + + while let Some((_, c)) = chars.next() { + if c == '{' { + let mut name = String::new(); + for (_, c) in &mut chars { + if c == '}' { + break; + } + name.push(c); + } + let trimmed = name.trim().to_string(); + if !trimmed.is_empty() { + placeholders.push(trimmed); + } + } + } + + placeholders +} diff --git a/rola-vcs/internal_macros/src/lib.rs b/rola-vcs/internal_macros/src/lib.rs new file mode 100644 index 0000000..f6c3cb7 --- /dev/null +++ b/rola-vcs/internal_macros/src/lib.rs @@ -0,0 +1,39 @@ +mod constants; + +use proc_macro::TokenStream; + +/// Transforms `pub const` items in a module into equivalent functions. +/// +/// Constants without `{param}` placeholders become `fn NAME() -> String`. +/// Constants with `{param}` placeholders become `fn NAME(param: impl AsRef) -> String`, +/// using `format!()` to fill in the placeholders. +/// +/// The entire module is annotated with `#[allow(non_snake_case)]`. +/// +/// # Example +/// +/// ```ignore +/// #[rorolala_internal_macros::constants] +/// pub mod paths { +/// pub const ROLA_DRAFT_DIR: &str = ".rola"; +/// pub const ROLA_BINDED_BUCKET_FILE: &str = ".rola/BIND/{bucket}"; +/// } +/// ``` +/// +/// expands to: +/// +/// ```ignore +/// #[allow(non_snake_case)] +/// pub mod paths { +/// pub fn ROLA_DRAFT_DIR() -> String { +/// ".rola".to_string() +/// } +/// pub fn ROLA_BINDED_BUCKET_FILE(bucket: impl AsRef) -> String { +/// format!(".rola/BIND/{bucket}", bucket = bucket.as_ref()) +/// } +/// } +/// ``` +#[proc_macro_attribute] +pub fn constants(attr: TokenStream, item: TokenStream) -> TokenStream { + constants::expand(attr, item) +} diff --git a/rola-vcs/src/consts/common.rs b/rola-vcs/src/consts/common.rs new file mode 100644 index 0000000..e2bfd8c --- /dev/null +++ b/rola-vcs/src/consts/common.rs @@ -0,0 +1,7 @@ +#[rorolala_internal_macros::constants] +mod consts { + pub const ROLA_DRAFT_DIR: &str = ".rola"; + pub const ROLA_BINDED_BUCKET_FILE: &str = ".rola/BIND/{bucket}"; +} + +pub use consts::*; diff --git a/rola-vcs/src/consts/mod.rs b/rola-vcs/src/consts/mod.rs new file mode 100644 index 0000000..598ae17 --- /dev/null +++ b/rola-vcs/src/consts/mod.rs @@ -0,0 +1,2 @@ +mod common; +pub use common::*; diff --git a/rola-vcs/src/lib.rs b/rola-vcs/src/lib.rs new file mode 100644 index 0000000..ac52569 --- /dev/null +++ b/rola-vcs/src/lib.rs @@ -0,0 +1,11 @@ +#![allow(unused)] + +pub mod bucket { + pub use rola_bucket::*; +} + +pub mod draft { + pub use rola_draft::*; +} + +pub mod consts; diff --git a/run-tools.ps1 b/run-tools.ps1 new file mode 100644 index 0000000..3b0bcf2 --- /dev/null +++ b/run-tools.ps1 @@ -0,0 +1,60 @@ +Set-Location -Path (Split-Path -Parent $MyInvocation.MyCommand.Path) -ErrorAction Stop + +# Collect all available tool names +$tools = @() + +if (Test-Path "rola-devtools/scripts") { + $scripts = Get-ChildItem -Path "rola-devtools/scripts/*.ps1", "rola-devtools/scripts/*.py" + foreach ($script in $scripts) { + if ($script -is [System.IO.FileInfo]) { + $tools += $script.BaseName + } + } +} +if (Test-Path "rola-devtools/src/bin") { + $files = Get-ChildItem -Path "rola-devtools/src/bin/*.rs" + foreach ($file in $files) { + if ($file -is [System.IO.FileInfo]) { + $tools += $file.BaseName + } + } +} + +if ($args.Count -eq 0) { + Write-Host "Available:" + for ($i = 0; $i -lt $tools.Count; $i++) { + Write-Host (" [{0,2}] {1}" -f ($i + 1), $tools[$i]) + } + exit 1 +} + +$target_name = $args[0] + +# Check if input is a number +if ($target_name -match '^\d+$') { + $idx = [int]$target_name - 1 + if ($idx -ge 0 -and $idx -lt $tools.Count) { + $target_name = $tools[$idx] + } else { + Write-Host "Error: invalid number '$target_name', valid range is 1-$($tools.Count)" + exit 1 + } +} + +# Collect remaining arguments to pass to the script +$script_args = $args[1..$args.Count] + +$script_file_ps1 = "rola-devtools/scripts/${target_name}.ps1" +$script_file_py = "rola-devtools/scripts/${target_name}.py" +$rust_file = "rola-devtools/src/bin/${target_name}.rs" + +if (Test-Path $script_file_ps1) { + & $script_file_ps1 $script_args +} elseif (Test-Path $script_file_py) { + python $script_file_py $script_args +} elseif (Test-Path $rust_file) { + cargo run --manifest-path rola-devtools/Cargo.toml --bin $target_name --quiet -- $script_args +} else { + Write-Host "Error: target '$target_name' does not exist as a script or Rust program" + exit 1 +} diff --git a/run-tools.sh b/run-tools.sh new file mode 100644 index 0000000..95464a8 --- /dev/null +++ b/run-tools.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +cd "$(dirname "$0")" || exit 1 + +# Collect all available tool names +tools=() + +if [ -d "rola-devtools/scripts" ]; then + for file in rola-devtools/scripts/*.sh; do + if [ -f "$file" ]; then + tools+=("$(basename "$file" .sh)") + fi + done + for file in rola-devtools/scripts/*.py; do + if [ -f "$file" ]; then + tools+=("$(basename "$file" .py)") + fi + done +fi +if [ -d "rola-devtools/src/bin" ]; then + for file in rola-devtools/src/bin/*.rs; do + if [ -f "$file" ]; then + tools+=("$(basename "$file" .rs)") + fi + done +fi + +if [ $# -eq 0 ]; then + echo "Available:" + for i in "${!tools[@]}"; do + printf " [%2d] %s\n" $((i + 1)) "${tools[$i]}" + done + exit 1 +fi + +target_bin="$1" +shift # Remove the first argument (tool name), keep the rest as tool arguments + +# Check if input is a number +if [[ "$target_bin" =~ ^[0-9]+$ ]]; then + idx=$((target_bin - 1)) + if [ "$idx" -ge 0 ] && [ "$idx" -lt "${#tools[@]}" ]; then + target_bin="${tools[$idx]}" + else + echo "Error: invalid number '$target_bin', valid range is 1-${#tools[@]}" + exit 1 + fi +fi + +target_script="rola-devtools/scripts/${target_bin}.sh" +target_python="rola-devtools/scripts/${target_bin}.py" +target_file="rola-devtools/src/bin/${target_bin}.rs" + +if [ -f "$target_script" ]; then + chmod +x "$target_script" + "$target_script" "$@" +elif [ -f "$target_python" ]; then + python "$target_python" "$@" +elif [ -f "$target_file" ]; then + cargo run --manifest-path rola-devtools/Cargo.toml --bin "$target_bin" --quiet -- "$@" +else + echo "Error: target '$target_bin' does not exist" + exit 1 +fi -- cgit