diff --git a/.azuredevops/Pipelines/build.yaml b/.azuredevops/Pipelines/build.yaml index 8a56f038..05d13267 100644 --- a/.azuredevops/Pipelines/build.yaml +++ b/.azuredevops/Pipelines/build.yaml @@ -46,9 +46,9 @@ stages: displayName: Update git submodules - task: UseDotNet@2 - displayName: "Use dotnet sdk 8.0.x" + displayName: "Use dotnet sdk 8.0.100" inputs: - version: 8.0.x + version: 8.0.100 includePreviewVersions: false - script: dotnet --info diff --git a/.azuredevops/Pipelines/pull-request.yaml b/.azuredevops/Pipelines/pull-request.yaml index ea4da240..32c41ad4 100644 --- a/.azuredevops/Pipelines/pull-request.yaml +++ b/.azuredevops/Pipelines/pull-request.yaml @@ -43,9 +43,9 @@ stages: includePreviewVersions: false - task: UseDotNet@2 - displayName: "Use dotnet sdk 8.0.x" + displayName: "Use dotnet sdk 8.0.100" inputs: - version: 8.0.x + version: 8.0.100 includePreviewVersions: false - script: dotnet --info @@ -58,7 +58,7 @@ stages: checkLatest: true - pwsh: | - dotnet tool install --global --version 25.1.0 MarkdownSnippets.Tool + dotnet tool install --global --version 25.1.0 MarkdownSnippets.Tool mdsnippets $changedLines = git status --porcelain=v1 diff --git a/Directory.Build.props b/Directory.Build.props index eaad5b05..4233c121 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,5 +10,7 @@ 3.6.133 all + + \ No newline at end of file diff --git a/README.md b/README.md index a043cd57..bc7395c9 100644 --- a/README.md +++ b/README.md @@ -14,16 +14,9 @@ It's populating itself as you work with git. It does not get in the way and only RepoM will not compete with your favourite git clients, so keep them. It's not about working within a repository: It's a new way to use all of your repositories to make your daily work easier. -📦 [Check the Releases page](https://github.com/coenm/RepoM/releases) to **download** the latest version and see **what's new**! - -## Credits - -RepoM is a fork of [RepoZ](https://github.com/awaescher/RepoZ), which was created by [Andreas Wäscher](https://github.com/awaescher). -RepoZ contains functionality that has been stripped in RepoM like supporting MacOS, releasing versions using chocolatey, the commandline sidekick (`grr``), and performing actions at multiple repostitories at once. - ## The Hub -The hub provides a quick overview of your repositories including their current branch and a short status information. Additionally, it offers some shortcuts like revealing a repository in the Windows Explorer, opening a command line tool in a given repository and checking out git branches. +The hub provides a quick overview of your repositories including their current branch, a short status information, and optionally some provided tags. Additionally, it offers some shortcuts like revealing a repository in the Windows Explorer, opening a command line tool in a given repository, checking out git branches and lots of other predefined or customizable actions. ![Screenshot](https://raw.githubusercontent.com/awaescher/RepoZ/master/_doc/RepoZ-ReadMe-UI-Both.png) @@ -38,10 +31,18 @@ For Windows, use the hotkeys Ctrl+Alt+R to show + +## Configuration + + + + ## Context Menu The main functionality of RepoM are the quick actions to execute per repository. For instance, you can quickly naviate to the repository by directly opening the windows explorer or by opening a command prompt. This context menu is user and repostirory specific and can be defined using yaml. This way, you can add an context menu item (action) for opening the repository in Visual Studio (for a C# project) and for an other repository you can add the action to open a repository in Eclipse. +To read more about the context menu, click here. + These actions are defined in the `RepositoryActions.yaml` located in your `%APPDATA%\RepoM\` folder. More information can be found [here](docs/RepositoryActions.md). ## Tagging @@ -77,3 +78,7 @@ RepoM uses plugins to extend functionality. At this moment, when a plugin is ava - [WebBrowser](docs/RepoM.Plugin.WebBrowser.md) - [WindowsExplorerGitInfo](docs/RepoM.Plugin.WindowsExplorerGitInfo.md) +## Credits + +RepoM is a fork of [RepoZ](https://github.com/awaescher/RepoZ), which was created by [Andreas Wäscher](https://github.com/awaescher). +RepoZ contains functionality that has been stripped in RepoM like supporting MacOS, releasing versions using chocolatey, the commandline sidekick (`grr``), and performing actions at multiple repostitories at once. diff --git a/README.source.md b/README.source.md index 5a040b6d..bc7395c9 100644 --- a/README.source.md +++ b/README.source.md @@ -14,16 +14,9 @@ It's populating itself as you work with git. It does not get in the way and only RepoM will not compete with your favourite git clients, so keep them. It's not about working within a repository: It's a new way to use all of your repositories to make your daily work easier. -📦 [Check the Releases page](https://github.com/coenm/RepoM/releases) to **download** the latest version and see **what's new**! - -## Credits - -RepoM is a fork of [RepoZ](https://github.com/awaescher/RepoZ), which was created by [Andreas Wäscher](https://github.com/awaescher). -RepoZ contains functionality that has been stripped in RepoM like supporting MacOS, releasing versions using chocolatey, the commandline sidekick (`grr``), and performing actions at multiple repostitories at once. - ## The Hub -The hub provides a quick overview of your repositories including their current branch and a short status information. Additionally, it offers some shortcuts like revealing a repository in the Windows Explorer, opening a command line tool in a given repository and checking out git branches. +The hub provides a quick overview of your repositories including their current branch, a short status information, and optionally some provided tags. Additionally, it offers some shortcuts like revealing a repository in the Windows Explorer, opening a command line tool in a given repository, checking out git branches and lots of other predefined or customizable actions. ![Screenshot](https://raw.githubusercontent.com/awaescher/RepoZ/master/_doc/RepoZ-ReadMe-UI-Both.png) @@ -38,10 +31,18 @@ For Windows, use the hotkeys Ctrl+Alt+R to show + +## Configuration + + + + ## Context Menu The main functionality of RepoM are the quick actions to execute per repository. For instance, you can quickly naviate to the repository by directly opening the windows explorer or by opening a command prompt. This context menu is user and repostirory specific and can be defined using yaml. This way, you can add an context menu item (action) for opening the repository in Visual Studio (for a C# project) and for an other repository you can add the action to open a repository in Eclipse. +To read more about the context menu, click here. + These actions are defined in the `RepositoryActions.yaml` located in your `%APPDATA%\RepoM\` folder. More information can be found [here](docs/RepositoryActions.md). ## Tagging @@ -76,4 +77,8 @@ RepoM uses plugins to extend functionality. At this moment, when a plugin is ava - [Statistics](docs/RepoM.Plugin.Statistics.md) - [WebBrowser](docs/RepoM.Plugin.WebBrowser.md) - [WindowsExplorerGitInfo](docs/RepoM.Plugin.WindowsExplorerGitInfo.md) - \ No newline at end of file + +## Credits + +RepoM is a fork of [RepoZ](https://github.com/awaescher/RepoZ), which was created by [Andreas Wäscher](https://github.com/awaescher). +RepoZ contains functionality that has been stripped in RepoM like supporting MacOS, releasing versions using chocolatey, the commandline sidekick (`grr``), and performing actions at multiple repostitories at once. diff --git a/RepoM.sln b/RepoM.sln index 87aa8f73..b40e5596 100644 --- a/RepoM.sln +++ b/RepoM.sln @@ -55,6 +55,22 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepoM.Plugin.WebBrowser", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepoM.Plugin.WebBrowser.Tests", "tests\RepoM.Plugin.WebBrowser.Tests\RepoM.Plugin.WebBrowser.Tests.csproj", "{E976587E-F48E-4647-A307-91EFEC8F571C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepoM.ActionMenu.Core", "src\RepoM.ActionMenu.Core\RepoM.ActionMenu.Core.csproj", "{2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepoM.ActionMenu.Interface", "src\RepoM.ActionMenu.Interface\RepoM.ActionMenu.Interface.csproj", "{2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepoM.ActionMenu.Core.Tests", "tests\RepoM.ActionMenu.Core.Tests\RepoM.ActionMenu.Core.Tests.csproj", "{4E22232B-1C6E-473A-AC82-F151C09D90CB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ActionMenu", "ActionMenu", "{D44E8C7C-76D4-4677-AB2C-4E4F32E93413}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepoM.ActionMenu.CodeGen", "src\RepoM.ActionMenu.CodeGen\RepoM.ActionMenu.CodeGen.csproj", "{F493CFD2-1352-4D17-9A93-2B18D31F6F2C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepoM.ActionMenu.CodeGen.Tests", "tests\RepoM.ActionMenu.CodeGen.Tests\RepoM.ActionMenu.CodeGen.Tests.csproj", "{0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepoM.ActionMenu.Core.TestLib", "tests\RepoM.ActionMenu.Core.TestLib\RepoM.ActionMenu.Core.TestLib.csproj", "{A803579D-E713-468B-9EBB-A014E473FFCD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RepoM.ActionMenu.CodeGenDummyLibrary", "tests\RepoM.ActionMenu.CodeGenDummyLibrary\RepoM.ActionMenu.CodeGenDummyLibrary.csproj", "{998B5CA0-158D-4B01-A343-07ED534D02B0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -569,6 +585,146 @@ Global {E976587E-F48E-4647-A307-91EFEC8F571C}.Release|x64.Build.0 = Release|Any CPU {E976587E-F48E-4647-A307-91EFEC8F571C}.Release|x86.ActiveCfg = Release|Any CPU {E976587E-F48E-4647-A307-91EFEC8F571C}.Release|x86.Build.0 = Release|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Debug|ARM.ActiveCfg = Debug|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Debug|ARM.Build.0 = Debug|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Debug|ARM64.Build.0 = Debug|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Debug|x64.ActiveCfg = Debug|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Debug|x64.Build.0 = Debug|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Debug|x86.ActiveCfg = Debug|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Debug|x86.Build.0 = Debug|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Release|Any CPU.Build.0 = Release|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Release|ARM.ActiveCfg = Release|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Release|ARM.Build.0 = Release|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Release|ARM64.ActiveCfg = Release|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Release|ARM64.Build.0 = Release|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Release|x64.ActiveCfg = Release|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Release|x64.Build.0 = Release|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Release|x86.ActiveCfg = Release|Any CPU + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C}.Release|x86.Build.0 = Release|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Debug|ARM.ActiveCfg = Debug|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Debug|ARM.Build.0 = Debug|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Debug|ARM64.Build.0 = Debug|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Debug|x64.ActiveCfg = Debug|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Debug|x64.Build.0 = Debug|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Debug|x86.ActiveCfg = Debug|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Debug|x86.Build.0 = Debug|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Release|Any CPU.Build.0 = Release|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Release|ARM.ActiveCfg = Release|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Release|ARM.Build.0 = Release|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Release|ARM64.ActiveCfg = Release|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Release|ARM64.Build.0 = Release|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Release|x64.ActiveCfg = Release|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Release|x64.Build.0 = Release|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Release|x86.ActiveCfg = Release|Any CPU + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96}.Release|x86.Build.0 = Release|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Debug|ARM.ActiveCfg = Debug|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Debug|ARM.Build.0 = Debug|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Debug|ARM64.Build.0 = Debug|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Debug|x64.ActiveCfg = Debug|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Debug|x64.Build.0 = Debug|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Debug|x86.ActiveCfg = Debug|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Debug|x86.Build.0 = Debug|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Release|Any CPU.Build.0 = Release|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Release|ARM.ActiveCfg = Release|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Release|ARM.Build.0 = Release|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Release|ARM64.ActiveCfg = Release|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Release|ARM64.Build.0 = Release|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Release|x64.ActiveCfg = Release|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Release|x64.Build.0 = Release|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Release|x86.ActiveCfg = Release|Any CPU + {4E22232B-1C6E-473A-AC82-F151C09D90CB}.Release|x86.Build.0 = Release|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Debug|ARM.ActiveCfg = Debug|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Debug|ARM.Build.0 = Debug|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Debug|ARM64.Build.0 = Debug|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Debug|x64.ActiveCfg = Debug|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Debug|x64.Build.0 = Debug|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Debug|x86.ActiveCfg = Debug|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Debug|x86.Build.0 = Debug|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Release|Any CPU.Build.0 = Release|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Release|ARM.ActiveCfg = Release|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Release|ARM.Build.0 = Release|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Release|ARM64.ActiveCfg = Release|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Release|ARM64.Build.0 = Release|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Release|x64.ActiveCfg = Release|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Release|x64.Build.0 = Release|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Release|x86.ActiveCfg = Release|Any CPU + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C}.Release|x86.Build.0 = Release|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Debug|ARM.ActiveCfg = Debug|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Debug|ARM.Build.0 = Debug|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Debug|ARM64.Build.0 = Debug|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Debug|x64.ActiveCfg = Debug|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Debug|x64.Build.0 = Debug|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Debug|x86.ActiveCfg = Debug|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Debug|x86.Build.0 = Debug|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Release|Any CPU.Build.0 = Release|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Release|ARM.ActiveCfg = Release|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Release|ARM.Build.0 = Release|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Release|ARM64.ActiveCfg = Release|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Release|ARM64.Build.0 = Release|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Release|x64.ActiveCfg = Release|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Release|x64.Build.0 = Release|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Release|x86.ActiveCfg = Release|Any CPU + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9}.Release|x86.Build.0 = Release|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Debug|ARM.ActiveCfg = Debug|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Debug|ARM.Build.0 = Debug|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Debug|ARM64.Build.0 = Debug|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Debug|x64.ActiveCfg = Debug|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Debug|x64.Build.0 = Debug|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Debug|x86.ActiveCfg = Debug|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Debug|x86.Build.0 = Debug|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Release|Any CPU.Build.0 = Release|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Release|ARM.ActiveCfg = Release|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Release|ARM.Build.0 = Release|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Release|ARM64.ActiveCfg = Release|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Release|ARM64.Build.0 = Release|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Release|x64.ActiveCfg = Release|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Release|x64.Build.0 = Release|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Release|x86.ActiveCfg = Release|Any CPU + {A803579D-E713-468B-9EBB-A014E473FFCD}.Release|x86.Build.0 = Release|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Debug|ARM.ActiveCfg = Debug|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Debug|ARM.Build.0 = Debug|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Debug|ARM64.Build.0 = Debug|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Debug|x64.ActiveCfg = Debug|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Debug|x64.Build.0 = Debug|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Debug|x86.ActiveCfg = Debug|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Debug|x86.Build.0 = Debug|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Release|Any CPU.Build.0 = Release|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Release|ARM.ActiveCfg = Release|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Release|ARM.Build.0 = Release|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Release|ARM64.ActiveCfg = Release|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Release|ARM64.Build.0 = Release|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Release|x64.ActiveCfg = Release|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Release|x64.Build.0 = Release|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Release|x86.ActiveCfg = Release|Any CPU + {998B5CA0-158D-4B01-A343-07ED534D02B0}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -593,6 +749,13 @@ Global {A2264CB6-39DE-4EA2-8C1D-BA28115351E7} = {D6E372DC-10D3-4997-9DFC-568B4666635A} {23D796D1-1902-4357-9C95-4FB120A24A6E} = {D6E372DC-10D3-4997-9DFC-568B4666635A} {E976587E-F48E-4647-A307-91EFEC8F571C} = {D6E372DC-10D3-4997-9DFC-568B4666635A} + {2C3EDAB0-915C-4BB4-91AD-0EE5F4B8741C} = {D44E8C7C-76D4-4677-AB2C-4E4F32E93413} + {2947A3C0-A3D5-457B-BEAA-F5C3A242EC96} = {D44E8C7C-76D4-4677-AB2C-4E4F32E93413} + {4E22232B-1C6E-473A-AC82-F151C09D90CB} = {D44E8C7C-76D4-4677-AB2C-4E4F32E93413} + {F493CFD2-1352-4D17-9A93-2B18D31F6F2C} = {D44E8C7C-76D4-4677-AB2C-4E4F32E93413} + {0C3FF659-8C0E-49D8-9A87-1F93ADE665F9} = {D44E8C7C-76D4-4677-AB2C-4E4F32E93413} + {A803579D-E713-468B-9EBB-A014E473FFCD} = {D44E8C7C-76D4-4677-AB2C-4E4F32E93413} + {998B5CA0-158D-4B01-A343-07ED534D02B0} = {D44E8C7C-76D4-4677-AB2C-4E4F32E93413} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1765ABAA-0652-4DA5-ABBF-05396F2957D7} diff --git a/RepoM.sln.DotSettings b/RepoM.sln.DotSettings index cbce9ef1..24b37438 100644 --- a/RepoM.sln.DotSettings +++ b/RepoM.sln.DotSettings @@ -9,5 +9,6 @@ True True True + True True True \ No newline at end of file diff --git a/docs/mdsource/ActionList.source.md b/docs/mdsource/ActionList.source.md.old similarity index 100% rename from docs/mdsource/ActionList.source.md rename to docs/mdsource/ActionList.source.md.old diff --git a/docs/mdsource/RepoM.Plugin.AzureDevOps.source.md b/docs/mdsource/RepoM.Plugin.AzureDevOps.source.md.old similarity index 100% rename from docs/mdsource/RepoM.Plugin.AzureDevOps.source.md rename to docs/mdsource/RepoM.Plugin.AzureDevOps.source.md.old diff --git a/docs/mdsource/RepoM.Plugin.Clipboard.source.md b/docs/mdsource/RepoM.Plugin.Clipboard.source.md.old similarity index 100% rename from docs/mdsource/RepoM.Plugin.Clipboard.source.md rename to docs/mdsource/RepoM.Plugin.Clipboard.source.md.old diff --git a/docs/mdsource/RepoM.Plugin.Heidi.source.md b/docs/mdsource/RepoM.Plugin.Heidi.source.md.old similarity index 100% rename from docs/mdsource/RepoM.Plugin.Heidi.source.md rename to docs/mdsource/RepoM.Plugin.Heidi.source.md.old diff --git a/docs/mdsource/RepoM.Plugin.SonarCloud.source.md b/docs/mdsource/RepoM.Plugin.SonarCloud.source.md.old similarity index 100% rename from docs/mdsource/RepoM.Plugin.SonarCloud.source.md rename to docs/mdsource/RepoM.Plugin.SonarCloud.source.md.old diff --git a/docs/mdsource/RepoM.Plugin.WebBrowser.source.md b/docs/mdsource/RepoM.Plugin.WebBrowser.source.md.old similarity index 100% rename from docs/mdsource/RepoM.Plugin.WebBrowser.source.md rename to docs/mdsource/RepoM.Plugin.WebBrowser.source.md.old diff --git a/docs/mdsource/RepositoryActions.source.md b/docs/mdsource/RepositoryActions.source.md.old similarity index 100% rename from docs/mdsource/RepositoryActions.source.md rename to docs/mdsource/RepositoryActions.source.md.old diff --git a/docs/snippets/file.find_files.verified.yaml b/docs/snippets/file.find_files.verified.yaml new file mode 100644 index 00000000..222d6a00 --- /dev/null +++ b/docs/snippets/file.find_files.verified.yaml @@ -0,0 +1,2 @@ +- C:\Project\My Repositories\my-solution.sln +- C:\Project\My Repositories\src\test solution.sln diff --git a/docs_new/mdsource/plugin_repom.plugin.azuredevops.generated.source.md b/docs_new/mdsource/plugin_repom.plugin.azuredevops.generated.source.md new file mode 100644 index 00000000..7e3578ef --- /dev/null +++ b/docs_new/mdsource/plugin_repom.plugin.azuredevops.generated.source.md @@ -0,0 +1,40 @@ +# AzureDevOps + +The AzureDevops module enables integration with one azure devops environment. The integration currently focuses on Pull Requests. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.AzureDevOps +- PluginName: AzureDevOps +- PluginDescription: Integration with Azure Devops providing fetching and creating pull requests. +- PluginMarkdownDescription: The AzureDevops module enables integration with one azure devops environment. The integration currently focuses on Pull Requests. + +This module contains the following methods, variables and/or constants: + +## azure-devops-create-pr@1 + +Action menu item to create a pull request in Azure Devops. + +Properties: + +- `name`: Menu item title. ([Text](repository_action_types.md#text)) +- `project-id`: The azure devops project id. ([Text](repository_action_types.md#text)) +- `pr-title`: Pull Request title. When not provided, the title will be defined based on the branch name. +Title will be the last part of the branchname split on `/`, so `feature/123-testBranch` will result in title `123-testBranch` ([Text](repository_action_types.md#text)) +- `to-branch`: Name of the branch the pull request should be merged into. For instance `develop`, or `main`. ([Text](repository_action_types.md#text)) +- `reviewer-ids`: List of reviewer ids. The id should be a valid Azure DevOps user id (i.e. GUID). (List) +- `draft-pr`: Boolean specifying if th PR should be marked as draft. ([Predicate](repository_action_types.md#predicate)) +- `include-work-items`: Boolean specifying if workitems should be included in the PR. RepoM will try to resolve the workitems by looping through the commit messages. ([Predicate](repository_action_types.md#predicate)) +- `open-in-browser`: Boolean specifying if the Pull request should be opened in the browser after creation. ([Predicate](repository_action_types.md#predicate)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `auto-complete`: Auto complete options. Please take a look at the same for more information (AutoCompleteOptionsV1, optional) + +### Example + +snippet: azure-devops-create-pr@1-scenario01 + +snippet: azure-devops-create-pr@1-scenario02 + +snippet: azure-devops-create-pr@1-scenario03 + diff --git a/docs_new/mdsource/plugin_repom.plugin.clipboard.generated.source.md b/docs_new/mdsource/plugin_repom.plugin.clipboard.generated.source.md new file mode 100644 index 00000000..a1b5a67c --- /dev/null +++ b/docs_new/mdsource/plugin_repom.plugin.clipboard.generated.source.md @@ -0,0 +1,30 @@ +# Clipboard + +This module provides a repository actions to copy specific (evaluated) text to the clipboard using the action provider type `clipboard-copy`. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.Clipboard +- PluginName: Clipboard +- PluginDescription: Provides a 'copy to clipboard' action. +- PluginMarkdownDescription: This module provides a repository actions to copy specific (evaluated) text to the clipboard using the action provider type `clipboard-copy`. + +This module contains the following methods, variables and/or constants: + +## clipboard-copy@1 + +This action makes it possible to copy text to the clipboard. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `text`: The text to copy to the clipboard. ([Text](repository_action_types.md#text)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) + +### Example + +snippet: clipboard-copy@1-scenario01 + +snippet: clipboard-copy@1-scenario02 + diff --git a/docs_new/mdsource/plugin_repom.plugin.everythingfilesearch.generated.source.md b/docs_new/mdsource/plugin_repom.plugin.everythingfilesearch.generated.source.md new file mode 100644 index 00000000..c67a6659 --- /dev/null +++ b/docs_new/mdsource/plugin_repom.plugin.everythingfilesearch.generated.source.md @@ -0,0 +1,11 @@ +# Everything + +This module integrates with VoidTool Everything in order to locate git repositories on your system. Using Everything cache, this process will be much faster then locating git repositories the default way. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.EverythingFileSearch +- PluginName: Everything +- PluginDescription: Uses VoidTool Everything as file search provider. +- PluginMarkdownDescription: This module integrates with VoidTool Everything in order to locate git repositories on your system. Using Everything cache, this process will be much faster then locating git repositories the default way. + diff --git a/docs_new/mdsource/plugin_repom.plugin.heidi.generated.source.md b/docs_new/mdsource/plugin_repom.plugin.heidi.generated.source.md new file mode 100644 index 00000000..48a6cda1 --- /dev/null +++ b/docs_new/mdsource/plugin_repom.plugin.heidi.generated.source.md @@ -0,0 +1,11 @@ +# HeidiSQL + +This module integrates with a portable [HeidiSQL](https://www.heidisql.com/) installation. The portable Heidi DB saves its database configuration in a portable configuration file. This module monitors this file and makes it possible to use this configuration in the action menu. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.Heidi +- PluginName: HeidiSQL +- PluginDescription: Contains context variables to be used in the action menu. The variables are extracted from the portable Heidi DB configuration. +- PluginMarkdownDescription: This module integrates with a portable [HeidiSQL](https://www.heidisql.com/) installation. The portable Heidi DB saves its database configuration in a portable configuration file. This module monitors this file and makes it possible to use this configuration in the action menu. + diff --git a/docs_new/mdsource/plugin_repom.plugin.lucenequeryparser.generated.source.md b/docs_new/mdsource/plugin_repom.plugin.lucenequeryparser.generated.source.md new file mode 100644 index 00000000..9d7a0f50 --- /dev/null +++ b/docs_new/mdsource/plugin_repom.plugin.lucenequeryparser.generated.source.md @@ -0,0 +1,11 @@ +# LuceneQueryParser + +Contains a custom query parser based on Lucene syntax for repository filtering. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.LuceneQueryParser +- PluginName: LuceneQueryParser +- PluginDescription: Contains a custom query parser based on Lucene syntax for repository filtering. +- PluginMarkdownDescription: \ + diff --git a/docs_new/mdsource/plugin_repom.plugin.sonarcloud.generated.source.md b/docs_new/mdsource/plugin_repom.plugin.sonarcloud.generated.source.md new file mode 100644 index 00000000..70c3136d --- /dev/null +++ b/docs_new/mdsource/plugin_repom.plugin.sonarcloud.generated.source.md @@ -0,0 +1,28 @@ +# SonarCloud + +This module integrates with SonarCloud. Currently, the only functionality is to star a given repository in SonarCloud using the repository action. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.SonarCloud +- PluginName: SonarCloud +- PluginDescription: Providing a repository action to mark a repository as favorite in SonarCloud +- PluginMarkdownDescription: This module integrates with SonarCloud. Currently, the only functionality is to star a given repository in SonarCloud using the repository action. + +This module contains the following methods, variables and/or constants: + +## sonarcloud-set-favorite@1 + +Action to mark a repository as favorite within SonarCloud. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `project`: The SonarCloud project key. ([Text](repository_action_types.md#text)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) + +### Example + +snippet: sonarcloud-set-favorite@1-scenario01 + diff --git a/docs_new/mdsource/plugin_repom.plugin.statistics.generated.source.md b/docs_new/mdsource/plugin_repom.plugin.statistics.generated.source.md new file mode 100644 index 00000000..807b2c48 --- /dev/null +++ b/docs_new/mdsource/plugin_repom.plugin.statistics.generated.source.md @@ -0,0 +1,11 @@ +# Statistics + +Provides functionality to keep track how may times an action is performed on a given repository. These numbers can be accessed using variable providers. The plugin also contains functionality to use these statistics in orderings. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.Statistics +- PluginName: Statistics +- PluginDescription: Provides functionality to keep track how may times an action is performed on a given repository. These numbers can be accessed using variable providers. The plugin also contains functionality to use these statistics in orderings. +- PluginMarkdownDescription: \ + diff --git a/docs_new/mdsource/plugin_repom.plugin.webbrowser.generated.source.md b/docs_new/mdsource/plugin_repom.plugin.webbrowser.generated.source.md new file mode 100644 index 00000000..6f72aeb0 --- /dev/null +++ b/docs_new/mdsource/plugin_repom.plugin.webbrowser.generated.source.md @@ -0,0 +1,29 @@ +# WebBrowser + +Provides functionality to start a web browser from an action with profile information. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.WebBrowser +- PluginName: WebBrowser +- PluginDescription: Provides functionality to start a web browser from an action with profile information. +- PluginMarkdownDescription: \ + +This module contains the following methods, variables and/or constants: + +## browser@1 + +Action opening a webbrowser with the provided url. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `url`: The url to browse to. ([Text](repository_action_types.md#text)) +- `profile`: profile name used to select browser and browser profile ([Text](repository_action_types.md#text)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) + +### Example + +snippet: webbrowser-browser@1-scenario01 + diff --git a/docs_new/mdsource/plugin_repom.plugin.windowsexplorergitinfo.generated.source.md b/docs_new/mdsource/plugin_repom.plugin.windowsexplorergitinfo.generated.source.md new file mode 100644 index 00000000..e96d440f --- /dev/null +++ b/docs_new/mdsource/plugin_repom.plugin.windowsexplorergitinfo.generated.source.md @@ -0,0 +1,11 @@ +# WindowsExplorerTitle + +As an extra goodie for Windows users, RepoM automatically detects open File Explorer windows and adds a status appendix to their title if they are in context of a git repository. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.WindowsExplorerGitInfo +- PluginName: WindowsExplorerTitle +- PluginDescription: Contains a hook updating Explorer views in Windows with the current git status. +- PluginMarkdownDescription: As an extra goodie for Windows users, RepoM automatically detects open File Explorer windows and adds a status appendix to their title if they are in context of a git repository. + diff --git a/docs_new/mdsource/repom.generated.source.md b/docs_new/mdsource/repom.generated.source.md new file mode 100644 index 00000000..ad0cc65b --- /dev/null +++ b/docs_new/mdsource/repom.generated.source.md @@ -0,0 +1,151 @@ +# RepoM Core Repository Actions + +This module contains the following methods, variables and/or constants: + +## browse-repository@1 + +Action to open the default webbrowser and go to the origin remote webinterface. When multiple remotes are available a sub menu is created for each remote. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `first-only`: Single menu for the first remote. ([Predicate](repository_action_types.md#predicate)) + +## command@1 + +Action to excute a command (related to the repository) + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `command`: The command to execute. ([Text](repository_action_types.md#text)) +- `arguments`: Arguments for the command. ([Text](repository_action_types.md#text)) + +## executable@1 + +Action to excute an application with additional arguments. This action is almost identical to the `command@1` action. When no existing executables are provided, the action will not show. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `executable`: The executable. ([Text](repository_action_types.md#text)) +- `arguments`: Arguments for the executable. ([Text](repository_action_types.md#text)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) + +## folder@1 + +Action to create a folder (sub menu) in the context menu of the repository allowing you to order actions. + +Properties: + +- `actions`: List of actions. (ActionMenu, optional) +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) + +## foreach@1 + +Action to create repeated actions based on a variable. + +Properties: + +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) +- `iteration-context`: Additional context added for each iteration. ([Context](repository_action_types.md#context)) +- `enumerable`: The list of items to enumerate on. (Variable) +- `variable`: The name of the variable to access to current enumeration of the items. For each iteration, the variable `{var.name}` has the value of the current iteration. (string?, optional) +- `skip`: Predicate to skip the current item. ([Predicate](repository_action_types.md#predicate)) +- `actions`: List of repeated actions. (List) + +## git-checkout@1 + +This action will create a menu and sub menus with all local and remote branches for an easy checkout. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) + +## git-fetch@1 + +Action to execute a `git fetch` command. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) + +## git-pull@1 + +Action to execute a `git pull` command. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) + +## git-push@1 + +Action to execute a `git push` command. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) + +## ignore-repository@1 + +Action to ignore the current repository. This repository will be added to the list of ignored repositories and will never show in RepoM. +To undo this action, clear all ignored repositories or manually edit the ignored repositories file (when RepoM is not running). + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) + +## just-text@1 + +Textual action to display some text in the action menu. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `enabled`: Show the menu as enabled (clickable) or disabled. ([Predicate](repository_action_types.md#predicate)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) + +## pin-repository@1 + +Action to pin (or unpin) the current repository. Pinning is not persistant and all pinned repositories will be cleared when RepoM exits. +Pinning a repository allowed custom filtering, ordering and searching. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) +- `mode`: The pin mode `[Toggle, Pin, UnPin]`. (Nullable, optional) + +## separator@1 + +Creates a visual separator in the action menu. + +Properties: + +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) + +## url@1 + +Action to open the url in the default browser. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `url`: The URL to browse to. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) diff --git a/docs_new/mdsource/repository_action_types.md b/docs_new/mdsource/repository_action_types.md new file mode 100644 index 00000000..7bb57e96 --- /dev/null +++ b/docs_new/mdsource/repository_action_types.md @@ -0,0 +1,103 @@ +# Repository Action types + +## Context + +The context type enables you to create or load variables and add some custom scriban methods in order to render [Text](#text) or calculate [Predicats](#predicate). +Currenlty, RepoM supports the following types of context actions which can be added as item to the context. + +- `evaluate-variable@1` A scriban template which, when evaluated, returns a value to be stored as variable. +- `evaluate-script@1` A scriban template which, when evaluated adds 'content' like variables or methods, to the current scriban context. +- `load-file@1` Loads a file (only `.env` or `.yaml`) and processes the content. An environment file is read and all environment variables are stored and accessable. +- `render-variable@1` Renders a scriban template and stores the outcomming text as variable. +- `set-variable@1` Sets a variable with static content. + +## Text + +Text is a scriban template for rendering textual content. These scriban templates can contain mixed content of text and scriban code blocks which are enclosed by `{{` and `}}`. + +### Examples + +For this example, the current repository branch name is `feature/abcdefghi` and the date is the 21st of december in 2023. + +| Input | Output (string) | +|---|---| +| `static text` | static text | +| `static {{ 1 + 2 }} text` | static 3 text | +| `today is {{ date.now \| date.to_string "%Y-%m-%d" }}` | today is 2023-12-21 | +| `this is {{ 42 < 11 && true }}!` | this is false! | +| `Create PR ({{ repository.branch \| string.replace "feature/" "" \| string.truncate 4 ".." }})` | Create PR abcd.. | + +### Internals + +Text uses the following scriban lexer and parser options: + + + +```cs +public static readonly ParserOptions DefaultParserOptions = new() +{ + ExpressionDepthLimit = 100, + LiquidFunctionsToScriban = false, + ParseFloatAsDecimal = default, +}; +``` +snippet source | anchor + + + + +```cs +public static readonly LexerOptions MixedLexer = new() +{ + FrontMatterMarker = LexerOptions.DefaultFrontMatterMarker, + Lang = ScriptLang.Default, + Mode = ScriptMode.Default, +}; +``` +snippet source | anchor + + +## Predicate + +A predicate is a scriban expression resulting in a boolean. Beause it is an expression, RepoM uses the pure scripting mode of scriban (`ScriptOnly` lexer mode) without templating (`{{` and `}}`). In other words, a Predicate is not about rendering text but evaluating a boolean expression. + +### Examples + +| Input | Output (boolean) | +|---|---| +| `true` | true | +| `false` | false | +| `1` | true | +| `0` | false | +| `1 == 2` | false | +| `a = [1, 2, 3]; a.size > 10` | false | +| `file.file_exists(repository.path + "\readme.md")` | true or false depending if file exists | + +### Internals + +Predicate uses the following scriban lexer and parser options: + + + +```cs +public static readonly ParserOptions DefaultParserOptions = new() +{ + ExpressionDepthLimit = 100, + LiquidFunctionsToScriban = false, + ParseFloatAsDecimal = default, +}; +``` +snippet source | anchor + + + + +```cs +public static readonly LexerOptions ScriptOnlyLexer = new() +{ + Lang = ScriptLang.Default, + Mode = ScriptMode.ScriptOnly, +}; +``` +snippet source | anchor + diff --git a/docs_new/mdsource/repository_action_types.source.md b/docs_new/mdsource/repository_action_types.source.md new file mode 100644 index 00000000..978f2832 --- /dev/null +++ b/docs_new/mdsource/repository_action_types.source.md @@ -0,0 +1,60 @@ +# Repository Action types + +## Context + +The context type enables you to create or load variables and add some custom scriban methods in order to render [Text](#text) or calculate [Predicats](#predicate). +Currenlty, RepoM supports the following types of context actions which can be added as item to the context. + +- `evaluate-variable@1` A scriban template which, when evaluated, returns a value to be stored as variable. +- `evaluate-script@1` A scriban template which, when evaluated adds 'content' like variables or methods, to the current scriban context. +- `load-file@1` Loads a file (only `.env` or `.yaml`) and processes the content. An environment file is read and all environment variables are stored and accessable. +- `render-variable@1` Renders a scriban template and stores the outcomming text as variable. +- `set-variable@1` Sets a variable with static content. + +## Text + +Text is a scriban template for rendering textual content. These scriban templates can contain mixed content of text and scriban code blocks which are enclosed by `{{` and `}}`. + +### Examples + +For this example, the current repository branch name is `feature/abcdefghi` and the date is the 21st of december in 2023. + +| Input | Output (string) | +|---|---| +| `static text` | static text | +| `static {{ 1 + 2 }} text` | static 3 text | +| `today is {{ date.now \| date.to_string "%Y-%m-%d" }}` | today is 2023-12-21 | +| `this is {{ 42 < 11 && true }}!` | this is false! | +| `Create PR ({{ repository.branch \| string.replace "feature/" "" \| string.truncate 4 ".." }})` | Create PR abcd.. | + +### Internals + +Text uses the following scriban lexer and parser options: + +snippet: DefaultLexerAndParserOptions_DefaultParserOptions + +snippet: DefaultLexerAndParserOptions_MixedLexer + +## Predicate + +A predicate is a scriban expression resulting in a boolean. Beause it is an expression, RepoM uses the pure scripting mode of scriban (`ScriptOnly` lexer mode) without templating (`{{` and `}}`). In other words, a Predicate is not about rendering text but evaluating a boolean expression. + +### Examples + +| Input | Output (boolean) | +|---|---| +| `true` | true | +| `false` | false | +| `1` | true | +| `0` | false | +| `1 == 2` | false | +| `a = [1, 2, 3]; a.size > 10` | false | +| `file.file_exists(repository.path + "\readme.md")` | true or false depending if file exists | + +### Internals + +Predicate uses the following scriban lexer and parser options: + +snippet: DefaultLexerAndParserOptions_DefaultParserOptions + +snippet: DefaultLexerAndParserOptions_ScriptOnlyLexer diff --git a/docs_new/mdsource/script_variables_azure_devops.generated.source.md b/docs_new/mdsource/script_variables_azure_devops.generated.source.md new file mode 100644 index 00000000..6a552843 --- /dev/null +++ b/docs_new/mdsource/script_variables_azure_devops.generated.source.md @@ -0,0 +1,44 @@ +# `azure_devops` + +Provides Azure Devops functions through `azure_devops`. + +This module contains the following methods, variables and/or constants: + +- [`azure_devops.get_pull_requests`](#get_pull_requests) + +## get_pull_requests + +`azure_devops.get_pull_requests(projectId)` + +Get pull requests for the given project. The result is an enumeration of PullRequest. + +Argument: + +- `projectId`: The azure devops project id. Cannot be null or empty. + +### Returns + +Returns an enumeration of pull requests for the selected repository (or an empty enumeration when no pull requests are found). + +### Example + +#### Usage + +Get all pull requests for the selected repository in a given devops project: + + +``` +devops_project_id = "805ACF64-0F06-47EC-96BF-E830895E2740"; +prs = azure_devops.get_pull_requests(devops_project_id); +``` + +#### Result + +As a result, the variable `prs` could contain two pull requests with the following dummy data: + +snippet: azure_devops.get_pull_requests + +#### RepositoryAction sample + +snippet: azure-devops-get-pull-requests@actionmenu01 + diff --git a/docs_new/mdsource/script_variables_file.generated.source.md b/docs_new/mdsource/script_variables_file.generated.source.md new file mode 100644 index 00000000..e00de116 --- /dev/null +++ b/docs_new/mdsource/script_variables_file.generated.source.md @@ -0,0 +1,112 @@ +# `file` + +Provides file related action menu functions and variables accessable through `file`. + +This module contains the following methods, variables and/or constants: + +- [`file.dir_exists`](#dir_exists) +- [`file.file_exists`](#file_exists) +- [`file.find_files`](#find_files) + +## dir_exists + +`file.dir_exists(path)` + +Checks if the specified directory path exists on the disk. + +Argument: + +- `path`: Absolute path to a directory. + +### Returns + +`true` if the specified directory path exists on the disk, `false` otherwise. + +### Example + +#### Usage + +Check if directory exists + + +``` +exists = file.dir_exists('C:\Project\'); +exists = file.dir_exists('C:\Project'); +exists = file.dir_exists('C:/Project/'); +``` + +#### RepositoryAction sample + +snippet: dir_exists@actionmenu01 + + +## file_exists + +`file.file_exists(path)` + +Checks if the specified file path exists on the disk. + +Argument: + +- `path`: Absolute path to a file. + +### Returns + +`true` if the specified file path exists on the disk, `false` otherwise. + +### Example + +#### Usage + +Check if file exists + + +``` +exists = file.file_exists('C:\Project\my-solution.sln'); +``` + +#### RepositoryAction sample + +snippet: file_exists@actionmenu01 + + +## find_files + +`file.find_files(rootPath,searchPattern)` + +Find files in a given directory based on the search pattern. Resulting filenames are absolute path based. + +Arguments: + +- `rootPath`: The root folder. +- `searchPattern`: The search string to match against the names of directories. This parameter can contain a combination of valid literal path and wildcard (`*` and `?`) characters, but it doesn't support regular expressions. + +### Returns + +Returns an enumerable collection of full paths of the files or directories that matches the specified search pattern. + +### Example + +#### Usage + +Locate all solution files in the given directory. + + +``` +solution_files = file.find_files('C:\Project\', '*.sln'); +``` + +#### Result + +As a result, the variable `solution_files` is an enumerable of strings, for example: + + +```yaml +- C:\Project\My Repositories\my-solution.sln +- C:\Project\My Repositories\src\test solution.sln +``` + +#### RepositoryAction sample + +snippet: find_files@actionmenu01 + diff --git a/docs_new/mdsource/script_variables_heidi.generated.source.md b/docs_new/mdsource/script_variables_heidi.generated.source.md new file mode 100644 index 00000000..15fab21a --- /dev/null +++ b/docs_new/mdsource/script_variables_heidi.generated.source.md @@ -0,0 +1,37 @@ +# `heidi` + +Provides variables provided by the Heidi module. The variables are accessable through `heidi`. + +This module contains the following methods, variables and/or constants: + +- [`heidi.databases`](#databases) + +## databases + +`heidi.databases` + +Gets all known databases configured in the Heidi configuration related to the selected repository. + +### Returns + +An enumerable of database configuration objects as shown in the example below. + +### Example + +Get all database configurations for the current repository: + + +``` +databases = heidi.databases; +``` + +#### Result + +As a result, the variable `databases` could contain the following dummy database configuration: + +snippet: heidi.databases@actionmenu01 + +#### RepositoryAction sample + +snippet: heidi.databases@actionmenu02 + diff --git a/docs_new/mdsource/script_variables_repository.generated.source.md b/docs_new/mdsource/script_variables_repository.generated.source.md new file mode 100644 index 00000000..166f3eb4 --- /dev/null +++ b/docs_new/mdsource/script_variables_repository.generated.source.md @@ -0,0 +1,115 @@ +# `repository` + +Provides action menu functions and variables for the current repository through `repository`. + +This module contains the following methods, variables and/or constants: + +- [`repository.branch`](#branch) +- [`repository.branches`](#branches) +- [`repository.linux_path`](#linux_path) +- [`repository.local_branches`](#local_branches) +- [`repository.location`](#location) +- [`repository.name`](#name) +- [`repository.path`](#path) +- [`repository.remotes`](#remotes) +- [`repository.windows_path`](#windows_path) + +## branch + +`repository.branch` + +Gets the current branch of the repository + +### Returns + +The name of the current branch. + +## branches + +`repository.branches` + +Gets the current branch of the repository + +### Returns + +The name of the current branch. + +## linux_path + +`repository.linux_path` + +Gets the path of the repository in linux style (i.e. use `\`). The path does NOT end with a backslash. + +### Returns + +The backslash based path of the repository without the last backslash. + +## local_branches + +`repository.local_branches` + +Gets the local branches + +### Returns + +All local branches. + +## location + +`repository.location` + +Gets the Location of the repository. + +### Returns + +The path of the repository. + +## name + +`repository.name` + +Gets the name of the repository. + +### Returns + +The name of the repository. + +### Example + +#### Usage + + +``` +repository.name +``` + + +## path + +`repository.path` + +Gets the path of the repository. The path is windows or linux based (depending on the running OS) and does NOT end with a (back)slash. + +### Returns + +The repository path. + +## remotes + +`repository.remotes` + +Gets the remotes. + +### Returns + +Remotes. + +## windows_path + +`repository.windows_path` + +Gets the path of the repository in windows style (i.e. use `/`). The path does NOT end with a slash. + +### Returns + +The path of the repository. diff --git a/docs_new/mdsource/script_variables_sonarcloud.generated.source.md b/docs_new/mdsource/script_variables_sonarcloud.generated.source.md new file mode 100644 index 00000000..6c18ff13 --- /dev/null +++ b/docs_new/mdsource/script_variables_sonarcloud.generated.source.md @@ -0,0 +1,42 @@ +# `sonarcloud` + +Provides a sonar cloud method providing the favorite status of the current repository. + +This module contains the following methods, variables and/or constants: + +- [`sonarcloud.is_favorite`](#is_favorite) + +## is_favorite + +`sonarcloud.is_favorite(id)` + +Get favorite status of repository related to the id. + +Argument: + +- `id`: The sonarcloud id related to the repository. + +### Returns + +`true` when the repository is set as favorite in SonarCloud, `false`, otherwise. + +### Example + +#### Usage + +Gets SonarClouds favorite status of the repository: + + +``` +sonarcloud_repository_id = "RepoM"; +is_favorite = sonarcloud.is_favorite(sonarcloud_repository_id); +``` + +#### Result + +As a result, the boolean variable `is_favorite` is set. + +#### RepositoryAction sample + +snippet: sonarcloud-is_favorite@actionmenu01 + diff --git a/docs_new/mdsource/script_variables_statistics.generated.source.md b/docs_new/mdsource/script_variables_statistics.generated.source.md new file mode 100644 index 00000000..d2fa9c4f --- /dev/null +++ b/docs_new/mdsource/script_variables_statistics.generated.source.md @@ -0,0 +1,48 @@ +# `statistics` + +Provides statistical information accessible through `statistics`. + +This module contains the following methods, variables and/or constants: + +- [`statistics.count`](#count) +- [`statistics.overall_count`](#overall_count) + +## count + +`statistics.count` + +Gets the number of actions performed on the current repository. + +### Returns + +Number of actions performed on the current repository. + +### Example + +#### Usage + + +``` +repo_call_count = statistics.count; +``` + + +## overall_count + +`statistics.overall_count` + +Gets the number of actions performed on all repositories known in RepoM. + +### Returns + +Number of actions performed on any known repository. + +### Example + +#### Usage + + +``` +repo_call_count = statistics.overall_count; +``` + diff --git a/docs_new/plugin_repom.plugin.azuredevops.generated.md b/docs_new/plugin_repom.plugin.azuredevops.generated.md new file mode 100644 index 00000000..5192725a --- /dev/null +++ b/docs_new/plugin_repom.plugin.azuredevops.generated.md @@ -0,0 +1,83 @@ +# AzureDevOps + +The AzureDevops module enables integration with one azure devops environment. The integration currently focuses on Pull Requests. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.AzureDevOps +- PluginName: AzureDevOps +- PluginDescription: Integration with Azure Devops providing fetching and creating pull requests. +- PluginMarkdownDescription: The AzureDevops module enables integration with one azure devops environment. The integration currently focuses on Pull Requests. + +This module contains the following methods, variables and/or constants: + +## azure-devops-create-pr@1 + +Action menu item to create a pull request in Azure Devops. + +Properties: + +- `name`: Menu item title. ([Text](repository_action_types.md#text)) +- `project-id`: The azure devops project id. ([Text](repository_action_types.md#text)) +- `pr-title`: Pull Request title. When not provided, the title will be defined based on the branch name. +Title will be the last part of the branchname split on `/`, so `feature/123-testBranch` will result in title `123-testBranch` ([Text](repository_action_types.md#text)) +- `to-branch`: Name of the branch the pull request should be merged into. For instance `develop`, or `main`. ([Text](repository_action_types.md#text)) +- `reviewer-ids`: List of reviewer ids. The id should be a valid Azure DevOps user id (i.e. GUID). (List) +- `draft-pr`: Boolean specifying if th PR should be marked as draft. ([Predicate](repository_action_types.md#predicate)) +- `include-work-items`: Boolean specifying if workitems should be included in the PR. RepoM will try to resolve the workitems by looping through the commit messages. ([Predicate](repository_action_types.md#predicate)) +- `open-in-browser`: Boolean specifying if the Pull request should be opened in the browser after creation. ([Predicate](repository_action_types.md#predicate)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `auto-complete`: Auto complete options. Please take a look at the same for more information (AutoCompleteOptionsV1, optional) + +### Example + + + +```yaml +- type: azure-devops-create-pr@1 + project-id: "{{ project_id }}" + name: Create feature to develop ({{ repository.branch | string.replace "feature/" "" | string.strip | string.truncate 20 ".." }}) + pr-title: 'Release {{ now }}' + to-branch: develop + reviewer-ids: + - "{{ devops_guid_reviewer_1 }}" + - "33333333-F973-4BE7-B39A-A9F85B18C75E" + draft-pr: false + include-work-items: true + open-in-browser: true + auto-complete: + merge-strategy: Squash + delete-source-branch: true + transition-work-items: true + active: 'repository.branch | string.starts_with "feature/"' +``` +snippet source | anchor + + + + +```yaml +- type: azure-devops-create-pr@1 + project-id: "{{ project_id }}" + name: Complete feature + pr-title: 'Feature {{ repository.branch | string.replace "feature/" "" }}' + to-branch: develop + reviewer-ids: + - "{{ devops_guid_reviewer_1 }}" + draft-pr: repository.banch == "develop" + active: true +``` +snippet source | anchor + + + + +```yaml +- type: azure-devops-create-pr@1 + project-id: "{{ project_id }}" + to-branch: develop +``` +snippet source | anchor + + diff --git a/docs_new/plugin_repom.plugin.clipboard.generated.md b/docs_new/plugin_repom.plugin.clipboard.generated.md new file mode 100644 index 00000000..a8317675 --- /dev/null +++ b/docs_new/plugin_repom.plugin.clipboard.generated.md @@ -0,0 +1,47 @@ +# Clipboard + +This module provides a repository actions to copy specific (evaluated) text to the clipboard using the action provider type `clipboard-copy`. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.Clipboard +- PluginName: Clipboard +- PluginDescription: Provides a 'copy to clipboard' action. +- PluginMarkdownDescription: This module provides a repository actions to copy specific (evaluated) text to the clipboard using the action provider type `clipboard-copy`. + +This module contains the following methods, variables and/or constants: + +## clipboard-copy@1 + +This action makes it possible to copy text to the clipboard. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `text`: The text to copy to the clipboard. ([Text](repository_action_types.md#text)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) + +### Example + + + +```yaml +- type: clipboard-copy@1 + name: Copy static text when feature branch + text: 'static text' + active: 'repository.branch | string.starts_with "feature/"' +``` +snippet source | anchor + + + + +```yaml +- type: clipboard-copy@1 + name: Copy git checkout command to clipboard + text: 'git checkout -b branch {{ repository.branch }}' +``` +snippet source | anchor + + diff --git a/docs_new/plugin_repom.plugin.everythingfilesearch.generated.md b/docs_new/plugin_repom.plugin.everythingfilesearch.generated.md new file mode 100644 index 00000000..c67a6659 --- /dev/null +++ b/docs_new/plugin_repom.plugin.everythingfilesearch.generated.md @@ -0,0 +1,11 @@ +# Everything + +This module integrates with VoidTool Everything in order to locate git repositories on your system. Using Everything cache, this process will be much faster then locating git repositories the default way. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.EverythingFileSearch +- PluginName: Everything +- PluginDescription: Uses VoidTool Everything as file search provider. +- PluginMarkdownDescription: This module integrates with VoidTool Everything in order to locate git repositories on your system. Using Everything cache, this process will be much faster then locating git repositories the default way. + diff --git a/docs_new/plugin_repom.plugin.heidi.generated.md b/docs_new/plugin_repom.plugin.heidi.generated.md new file mode 100644 index 00000000..48a6cda1 --- /dev/null +++ b/docs_new/plugin_repom.plugin.heidi.generated.md @@ -0,0 +1,11 @@ +# HeidiSQL + +This module integrates with a portable [HeidiSQL](https://www.heidisql.com/) installation. The portable Heidi DB saves its database configuration in a portable configuration file. This module monitors this file and makes it possible to use this configuration in the action menu. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.Heidi +- PluginName: HeidiSQL +- PluginDescription: Contains context variables to be used in the action menu. The variables are extracted from the portable Heidi DB configuration. +- PluginMarkdownDescription: This module integrates with a portable [HeidiSQL](https://www.heidisql.com/) installation. The portable Heidi DB saves its database configuration in a portable configuration file. This module monitors this file and makes it possible to use this configuration in the action menu. + diff --git a/docs_new/plugin_repom.plugin.lucenequeryparser.generated.md b/docs_new/plugin_repom.plugin.lucenequeryparser.generated.md new file mode 100644 index 00000000..9d7a0f50 --- /dev/null +++ b/docs_new/plugin_repom.plugin.lucenequeryparser.generated.md @@ -0,0 +1,11 @@ +# LuceneQueryParser + +Contains a custom query parser based on Lucene syntax for repository filtering. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.LuceneQueryParser +- PluginName: LuceneQueryParser +- PluginDescription: Contains a custom query parser based on Lucene syntax for repository filtering. +- PluginMarkdownDescription: \ + diff --git a/docs_new/plugin_repom.plugin.sonarcloud.generated.md b/docs_new/plugin_repom.plugin.sonarcloud.generated.md new file mode 100644 index 00000000..05662ce6 --- /dev/null +++ b/docs_new/plugin_repom.plugin.sonarcloud.generated.md @@ -0,0 +1,37 @@ +# SonarCloud + +This module integrates with SonarCloud. Currently, the only functionality is to star a given repository in SonarCloud using the repository action. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.SonarCloud +- PluginName: SonarCloud +- PluginDescription: Providing a repository action to mark a repository as favorite in SonarCloud +- PluginMarkdownDescription: This module integrates with SonarCloud. Currently, the only functionality is to star a given repository in SonarCloud using the repository action. + +This module contains the following methods, variables and/or constants: + +## sonarcloud-set-favorite@1 + +Action to mark a repository as favorite within SonarCloud. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `project`: The SonarCloud project key. ([Text](repository_action_types.md#text)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) + +### Example + + + +```yaml +- type: sonarcloud-set-favorite@1 + name: Star repository on SonarCloud + project: "{{ my_sonarcloud_repository_id }}" + active: "!sonarcloud.is_favorite(my_sonarcloud_repository_id)" +``` +snippet source | anchor + + diff --git a/docs_new/plugin_repom.plugin.statistics.generated.md b/docs_new/plugin_repom.plugin.statistics.generated.md new file mode 100644 index 00000000..807b2c48 --- /dev/null +++ b/docs_new/plugin_repom.plugin.statistics.generated.md @@ -0,0 +1,11 @@ +# Statistics + +Provides functionality to keep track how may times an action is performed on a given repository. These numbers can be accessed using variable providers. The plugin also contains functionality to use these statistics in orderings. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.Statistics +- PluginName: Statistics +- PluginDescription: Provides functionality to keep track how may times an action is performed on a given repository. These numbers can be accessed using variable providers. The plugin also contains functionality to use these statistics in orderings. +- PluginMarkdownDescription: \ + diff --git a/docs_new/plugin_repom.plugin.webbrowser.generated.md b/docs_new/plugin_repom.plugin.webbrowser.generated.md new file mode 100644 index 00000000..0da84f58 --- /dev/null +++ b/docs_new/plugin_repom.plugin.webbrowser.generated.md @@ -0,0 +1,39 @@ +# WebBrowser + +Provides functionality to start a web browser from an action with profile information. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.WebBrowser +- PluginName: WebBrowser +- PluginDescription: Provides functionality to start a web browser from an action with profile information. +- PluginMarkdownDescription: \ + +This module contains the following methods, variables and/or constants: + +## browser@1 + +Action opening a webbrowser with the provided url. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `url`: The url to browse to. ([Text](repository_action_types.md#text)) +- `profile`: profile name used to select browser and browser profile ([Text](repository_action_types.md#text)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) + +### Example + + + +```yaml +- type: browser@1 + name: My Github + url: https://github.com/coenm + profile: '{{ my_profile }}' + active: true +``` +snippet source | anchor + + diff --git a/docs_new/plugin_repom.plugin.windowsexplorergitinfo.generated.md b/docs_new/plugin_repom.plugin.windowsexplorergitinfo.generated.md new file mode 100644 index 00000000..e96d440f --- /dev/null +++ b/docs_new/plugin_repom.plugin.windowsexplorergitinfo.generated.md @@ -0,0 +1,11 @@ +# WindowsExplorerTitle + +As an extra goodie for Windows users, RepoM automatically detects open File Explorer windows and adds a status appendix to their title if they are in context of a git repository. + +To use this module, make sure it is enabled in RepoM by opening the menu and navigate to 'Plugins'. When enabling or disabling a plugin, you should restart RepoM. + +- ProjectName: RepoM.Plugin.WindowsExplorerGitInfo +- PluginName: WindowsExplorerTitle +- PluginDescription: Contains a hook updating Explorer views in Windows with the current git status. +- PluginMarkdownDescription: As an extra goodie for Windows users, RepoM automatically detects open File Explorer windows and adds a status appendix to their title if they are in context of a git repository. + diff --git a/docs_new/repom.generated.md b/docs_new/repom.generated.md new file mode 100644 index 00000000..ad0cc65b --- /dev/null +++ b/docs_new/repom.generated.md @@ -0,0 +1,151 @@ +# RepoM Core Repository Actions + +This module contains the following methods, variables and/or constants: + +## browse-repository@1 + +Action to open the default webbrowser and go to the origin remote webinterface. When multiple remotes are available a sub menu is created for each remote. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `first-only`: Single menu for the first remote. ([Predicate](repository_action_types.md#predicate)) + +## command@1 + +Action to excute a command (related to the repository) + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `command`: The command to execute. ([Text](repository_action_types.md#text)) +- `arguments`: Arguments for the command. ([Text](repository_action_types.md#text)) + +## executable@1 + +Action to excute an application with additional arguments. This action is almost identical to the `command@1` action. When no existing executables are provided, the action will not show. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `executable`: The executable. ([Text](repository_action_types.md#text)) +- `arguments`: Arguments for the executable. ([Text](repository_action_types.md#text)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) + +## folder@1 + +Action to create a folder (sub menu) in the context menu of the repository allowing you to order actions. + +Properties: + +- `actions`: List of actions. (ActionMenu, optional) +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) + +## foreach@1 + +Action to create repeated actions based on a variable. + +Properties: + +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) +- `iteration-context`: Additional context added for each iteration. ([Context](repository_action_types.md#context)) +- `enumerable`: The list of items to enumerate on. (Variable) +- `variable`: The name of the variable to access to current enumeration of the items. For each iteration, the variable `{var.name}` has the value of the current iteration. (string?, optional) +- `skip`: Predicate to skip the current item. ([Predicate](repository_action_types.md#predicate)) +- `actions`: List of repeated actions. (List) + +## git-checkout@1 + +This action will create a menu and sub menus with all local and remote branches for an easy checkout. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) + +## git-fetch@1 + +Action to execute a `git fetch` command. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) + +## git-pull@1 + +Action to execute a `git pull` command. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) + +## git-push@1 + +Action to execute a `git push` command. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) + +## ignore-repository@1 + +Action to ignore the current repository. This repository will be added to the list of ignored repositories and will never show in RepoM. +To undo this action, clear all ignored repositories or manually edit the ignored repositories file (when RepoM is not running). + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) + +## just-text@1 + +Textual action to display some text in the action menu. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `enabled`: Show the menu as enabled (clickable) or disabled. ([Predicate](repository_action_types.md#predicate)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) + +## pin-repository@1 + +Action to pin (or unpin) the current repository. Pinning is not persistant and all pinned repositories will be cleared when RepoM exits. +Pinning a repository allowed custom filtering, ordering and searching. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) +- `mode`: The pin mode `[Toggle, Pin, UnPin]`. (Nullable, optional) + +## separator@1 + +Creates a visual separator in the action menu. + +Properties: + +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) + +## url@1 + +Action to open the url in the default browser. + +Properties: + +- `name`: Name of the menu item. ([Text](repository_action_types.md#text)) +- `url`: The URL to browse to. ([Text](repository_action_types.md#text)) +- `active`: Whether the menu item is enabled. ([Predicate](repository_action_types.md#predicate)) +- `context`: The context in which the action is available. ([Context](repository_action_types.md#context)) diff --git a/docs_new/repository_action_types.md b/docs_new/repository_action_types.md new file mode 100644 index 00000000..7bb57e96 --- /dev/null +++ b/docs_new/repository_action_types.md @@ -0,0 +1,103 @@ +# Repository Action types + +## Context + +The context type enables you to create or load variables and add some custom scriban methods in order to render [Text](#text) or calculate [Predicats](#predicate). +Currenlty, RepoM supports the following types of context actions which can be added as item to the context. + +- `evaluate-variable@1` A scriban template which, when evaluated, returns a value to be stored as variable. +- `evaluate-script@1` A scriban template which, when evaluated adds 'content' like variables or methods, to the current scriban context. +- `load-file@1` Loads a file (only `.env` or `.yaml`) and processes the content. An environment file is read and all environment variables are stored and accessable. +- `render-variable@1` Renders a scriban template and stores the outcomming text as variable. +- `set-variable@1` Sets a variable with static content. + +## Text + +Text is a scriban template for rendering textual content. These scriban templates can contain mixed content of text and scriban code blocks which are enclosed by `{{` and `}}`. + +### Examples + +For this example, the current repository branch name is `feature/abcdefghi` and the date is the 21st of december in 2023. + +| Input | Output (string) | +|---|---| +| `static text` | static text | +| `static {{ 1 + 2 }} text` | static 3 text | +| `today is {{ date.now \| date.to_string "%Y-%m-%d" }}` | today is 2023-12-21 | +| `this is {{ 42 < 11 && true }}!` | this is false! | +| `Create PR ({{ repository.branch \| string.replace "feature/" "" \| string.truncate 4 ".." }})` | Create PR abcd.. | + +### Internals + +Text uses the following scriban lexer and parser options: + + + +```cs +public static readonly ParserOptions DefaultParserOptions = new() +{ + ExpressionDepthLimit = 100, + LiquidFunctionsToScriban = false, + ParseFloatAsDecimal = default, +}; +``` +snippet source | anchor + + + + +```cs +public static readonly LexerOptions MixedLexer = new() +{ + FrontMatterMarker = LexerOptions.DefaultFrontMatterMarker, + Lang = ScriptLang.Default, + Mode = ScriptMode.Default, +}; +``` +snippet source | anchor + + +## Predicate + +A predicate is a scriban expression resulting in a boolean. Beause it is an expression, RepoM uses the pure scripting mode of scriban (`ScriptOnly` lexer mode) without templating (`{{` and `}}`). In other words, a Predicate is not about rendering text but evaluating a boolean expression. + +### Examples + +| Input | Output (boolean) | +|---|---| +| `true` | true | +| `false` | false | +| `1` | true | +| `0` | false | +| `1 == 2` | false | +| `a = [1, 2, 3]; a.size > 10` | false | +| `file.file_exists(repository.path + "\readme.md")` | true or false depending if file exists | + +### Internals + +Predicate uses the following scriban lexer and parser options: + + + +```cs +public static readonly ParserOptions DefaultParserOptions = new() +{ + ExpressionDepthLimit = 100, + LiquidFunctionsToScriban = false, + ParseFloatAsDecimal = default, +}; +``` +snippet source | anchor + + + + +```cs +public static readonly LexerOptions ScriptOnlyLexer = new() +{ + Lang = ScriptLang.Default, + Mode = ScriptMode.ScriptOnly, +}; +``` +snippet source | anchor + diff --git a/docs_new/script_variables_azure_devops.generated.md b/docs_new/script_variables_azure_devops.generated.md new file mode 100644 index 00000000..1cdb849f --- /dev/null +++ b/docs_new/script_variables_azure_devops.generated.md @@ -0,0 +1,75 @@ +# `azure_devops` + +Provides Azure Devops functions through `azure_devops`. + +This module contains the following methods, variables and/or constants: + +- [`azure_devops.get_pull_requests`](#get_pull_requests) + +## get_pull_requests + +`azure_devops.get_pull_requests(projectId)` + +Get pull requests for the given project. The result is an enumeration of PullRequest. + +Argument: + +- `projectId`: The azure devops project id. Cannot be null or empty. + +### Returns + +Returns an enumeration of pull requests for the selected repository (or an empty enumeration when no pull requests are found). + +### Example + +#### Usage + +Get all pull requests for the selected repository in a given devops project: + + +``` +devops_project_id = "805ACF64-0F06-47EC-96BF-E830895E2740"; +prs = azure_devops.get_pull_requests(devops_project_id); +``` + +#### Result + +As a result, the variable `prs` could contain two pull requests with the following dummy data: + + + +```yaml +- repository-id: b1a0619a-cb69-4bf6-9b97-6c62481d9bff + name: some pr1 + url: https://my-url/pr1 +- repository-id: f99e85ee-2c23-414b-8804-6a6c34f8c349 + name: other pr - bug + url: https://my-url/pr3 +``` +snippet source | anchor + + +#### RepositoryAction sample + + + +```yaml +context: +- type: evaluate-script@1 + content: |- + devops_project_id = "805ACF64-0F06-47EC-96BF-E830895E2740"; + prs = azure_devops.get_pull_requests(devops_project_id); + +action-menu: +- type: foreach@1 + active: 'array.size(prs) > 1' + enumerable: prs + variable: pr + actions: + - type: url@1 + name: '{{ pr.name }}' + url: '{{ pr.url }}' +``` +snippet source | anchor + + diff --git a/docs_new/script_variables_file.generated.md b/docs_new/script_variables_file.generated.md new file mode 100644 index 00000000..f7e11acb --- /dev/null +++ b/docs_new/script_variables_file.generated.md @@ -0,0 +1,199 @@ +# `file` + +Provides file related action menu functions and variables accessable through `file`. + +This module contains the following methods, variables and/or constants: + +- [`file.dir_exists`](#dir_exists) +- [`file.file_exists`](#file_exists) +- [`file.find_files`](#find_files) + +## dir_exists + +`file.dir_exists(path)` + +Checks if the specified directory path exists on the disk. + +Argument: + +- `path`: Absolute path to a directory. + +### Returns + +`true` if the specified directory path exists on the disk, `false` otherwise. + +### Example + +#### Usage + +Check if directory exists + + +``` +exists = file.dir_exists('C:\Project\'); +exists = file.dir_exists('C:\Project'); +exists = file.dir_exists('C:/Project/'); +``` + +#### RepositoryAction sample + + + +```yaml +context: + +# create a variable to store the path to the Visual Studio Code executable +- type: evaluate-script@1 + content: |- + exe_vs_code = env.LocalAppData + "/Programs/Microsoft VS Code/code.exe"; + +# create a variable to store the path to the documentation directory +# based on the remote name +- type: render-variable@1 + name: repo_docs_directory + value: 'G:\\My Drive\\RepoDocs\\github.com\\{{ remote_name_origin }}' + +action-menu: + +# If the document directory exists .. +- type: folder@1 + name: Documentation + active: file.dir_exists(repo_docs_directory) + is-deferred: true + actions: + # .. show the menu item to open it in Visual Studio Code + - type: executable@1 + name: Open in Visual Studio Code + executable: '{{ exe_vs_code }}' + arguments: '"{{ repo_docs_directory }}"' + # .. and a menu item to open it in Windows File Explorer + - type: command@1 + name: Open in Windows File Explorer + command: '"{{ repo_docs_directory }}"' + +# if the directory does not exists, create a menu item to create it +- type: command@1 + name: Create Documentation directory + command: cmd + arguments: /k mkdir "{{ repo_docs_directory }}" + active: '!file.dir_exists(repo_docs_directory)' +``` +snippet source | anchor + + + +## file_exists + +`file.file_exists(path)` + +Checks if the specified file path exists on the disk. + +Argument: + +- `path`: Absolute path to a file. + +### Returns + +`true` if the specified file path exists on the disk, `false` otherwise. + +### Example + +#### Usage + +Check if file exists + + +``` +exists = file.file_exists('C:\Project\my-solution.sln'); +``` + +#### RepositoryAction sample + + + +```yaml +action-menu: +# Show menu item to edit the .editorconfig file if it exists. +- type: executable@1 + name: Edit .editorconfig in Visual Studio Code + executable: '{{ env.LocalAppData }}/Programs/Microsoft VS Code/code.exe' + arguments: '"{{ repository.linux_path }}/.editorconfig"' + active: 'file.file_exists(repository.linux_path + "/.editorconfig")' +``` +snippet source | anchor + + + +## find_files + +`file.find_files(rootPath,searchPattern)` + +Find files in a given directory based on the search pattern. Resulting filenames are absolute path based. + +Arguments: + +- `rootPath`: The root folder. +- `searchPattern`: The search string to match against the names of directories. This parameter can contain a combination of valid literal path and wildcard (`*` and `?`) characters, but it doesn't support regular expressions. + +### Returns + +Returns an enumerable collection of full paths of the files or directories that matches the specified search pattern. + +### Example + +#### Usage + +Locate all solution files in the given directory. + + +``` +solution_files = file.find_files('C:\Project\', '*.sln'); +``` + +#### Result + +As a result, the variable `solution_files` is an enumerable of strings, for example: + + +```yaml +- C:\Project\My Repositories\my-solution.sln +- C:\Project\My Repositories\src\test solution.sln +``` + +#### RepositoryAction sample + + + +```yaml +context: +- type: evaluate-script@1 + content: |- + func get_filename(path) + ret path | string.split("\\") | array.last + end + + solution_files = file.find_files(repository.path, "*.sln"); + +action-menu: +# Open in visual studio when only one sln file was found in the repo. +- type: command@1 + name: Open in Visual Studio + command: '{{ array.first(solution_files) }}' + active: 'array.size(solution_files) == 1' + +# Use folder to choose sln file when multiple sln files found. +- type: folder@1 + name: Open in Visual Studio + active: 'array.size(solution_files) > 1' + actions: + - type: foreach@1 + enumerable: solution_files + variable: sln + actions: + - type: command@1 + name: '{{ get_filename(sln) }}' + command: '{{ sln }}' +``` +snippet source | anchor + + diff --git a/docs_new/script_variables_heidi.generated.md b/docs_new/script_variables_heidi.generated.md new file mode 100644 index 00000000..b4b48a29 --- /dev/null +++ b/docs_new/script_variables_heidi.generated.md @@ -0,0 +1,91 @@ +# `heidi` + +Provides variables provided by the Heidi module. The variables are accessable through `heidi`. + +This module contains the following methods, variables and/or constants: + +- [`heidi.databases`](#databases) + +## databases + +`heidi.databases` + +Gets all known databases configured in the Heidi configuration related to the selected repository. + +### Returns + +An enumerable of database configuration objects as shown in the example below. + +### Example + +Get all database configurations for the current repository: + + +``` +databases = heidi.databases; +``` + +#### Result + +As a result, the variable `databases` could contain the following dummy database configuration: + + + +```yaml +- metadata: + name: heidi-key + order: 1 + tags: + - Test + - Dev + database: + key: MyDomainDb1 + host: database.my-domain.com + user: coenm + password: myS3cr3t! + port: 2345 + uses-windows-authentication: false + database-type: + name: MariaDB/MySQL + protocol: named pipe + library: MSOLEDBSQL + comment: HeidiSQL Comment + databases: + - database1 + - database2 +``` +snippet source | anchor + + +#### RepositoryAction sample + + + +```yaml +context: +- type: evaluate-script@1 + content: |- + databases = heidi.databases; + exe_heidi_sql = "C:/Program Files/HeidiSQL/heidisql.exe"; + exe_ssms ="C:/Program Files (x86)/Microsoft SQL Server Management Studio 18/Common7/IDE/Ssms.exe"; + +action-menu: +- type: foreach@1 + active: 'array.size(databases) > 0' + enumerable: databases + variable: db + actions: + # open in Heidi Sql + - type: executable@1 + name: Open {{ db.metadata.name }} in HeidiSQL + executable: '{{ exe_heidi_sql }}' + arguments: --description "{{ db.database.key }}" + # open in SQL Server Management Studio + - type: executable@1 + name: Open {{ db.metadata.name }} in SQL Server Management Studio + executable: '{{ exe_ssms }}' + arguments: -S "{{ db.database.host }}" -d "{{ array.first db.database.databases }}" -U "{{ db.database.user }}" +``` +snippet source | anchor + + diff --git a/docs_new/script_variables_repository.generated.md b/docs_new/script_variables_repository.generated.md new file mode 100644 index 00000000..166f3eb4 --- /dev/null +++ b/docs_new/script_variables_repository.generated.md @@ -0,0 +1,115 @@ +# `repository` + +Provides action menu functions and variables for the current repository through `repository`. + +This module contains the following methods, variables and/or constants: + +- [`repository.branch`](#branch) +- [`repository.branches`](#branches) +- [`repository.linux_path`](#linux_path) +- [`repository.local_branches`](#local_branches) +- [`repository.location`](#location) +- [`repository.name`](#name) +- [`repository.path`](#path) +- [`repository.remotes`](#remotes) +- [`repository.windows_path`](#windows_path) + +## branch + +`repository.branch` + +Gets the current branch of the repository + +### Returns + +The name of the current branch. + +## branches + +`repository.branches` + +Gets the current branch of the repository + +### Returns + +The name of the current branch. + +## linux_path + +`repository.linux_path` + +Gets the path of the repository in linux style (i.e. use `\`). The path does NOT end with a backslash. + +### Returns + +The backslash based path of the repository without the last backslash. + +## local_branches + +`repository.local_branches` + +Gets the local branches + +### Returns + +All local branches. + +## location + +`repository.location` + +Gets the Location of the repository. + +### Returns + +The path of the repository. + +## name + +`repository.name` + +Gets the name of the repository. + +### Returns + +The name of the repository. + +### Example + +#### Usage + + +``` +repository.name +``` + + +## path + +`repository.path` + +Gets the path of the repository. The path is windows or linux based (depending on the running OS) and does NOT end with a (back)slash. + +### Returns + +The repository path. + +## remotes + +`repository.remotes` + +Gets the remotes. + +### Returns + +Remotes. + +## windows_path + +`repository.windows_path` + +Gets the path of the repository in windows style (i.e. use `/`). The path does NOT end with a slash. + +### Returns + +The path of the repository. diff --git a/docs_new/script_variables_sonarcloud.generated.md b/docs_new/script_variables_sonarcloud.generated.md new file mode 100644 index 00000000..da3903a6 --- /dev/null +++ b/docs_new/script_variables_sonarcloud.generated.md @@ -0,0 +1,58 @@ +# `sonarcloud` + +Provides a sonar cloud method providing the favorite status of the current repository. + +This module contains the following methods, variables and/or constants: + +- [`sonarcloud.is_favorite`](#is_favorite) + +## is_favorite + +`sonarcloud.is_favorite(id)` + +Get favorite status of repository related to the id. + +Argument: + +- `id`: The sonarcloud id related to the repository. + +### Returns + +`true` when the repository is set as favorite in SonarCloud, `false`, otherwise. + +### Example + +#### Usage + +Gets SonarClouds favorite status of the repository: + + +``` +sonarcloud_repository_id = "RepoM"; +is_favorite = sonarcloud.is_favorite(sonarcloud_repository_id); +``` + +#### Result + +As a result, the boolean variable `is_favorite` is set. + +#### RepositoryAction sample + + + +```yaml +context: +- type: evaluate-script@1 + content: |- + sonarcloud_repository_id = "RepoM"; + is_favorite = sonarcloud.is_favorite(sonarcloud_repository_id); + +action-menu: +- type: url@1 + name: 'Open SonarClouds favorite in browser' + url: 'https://sonarcloud.io/project/overview?id={{ sonarcloud_repository_id }}' + active: is_favorite +``` +snippet source | anchor + + diff --git a/docs_new/script_variables_statistics.generated.md b/docs_new/script_variables_statistics.generated.md new file mode 100644 index 00000000..d2fa9c4f --- /dev/null +++ b/docs_new/script_variables_statistics.generated.md @@ -0,0 +1,48 @@ +# `statistics` + +Provides statistical information accessible through `statistics`. + +This module contains the following methods, variables and/or constants: + +- [`statistics.count`](#count) +- [`statistics.overall_count`](#overall_count) + +## count + +`statistics.count` + +Gets the number of actions performed on the current repository. + +### Returns + +Number of actions performed on the current repository. + +### Example + +#### Usage + + +``` +repo_call_count = statistics.count; +``` + + +## overall_count + +`statistics.overall_count` + +Gets the number of actions performed on all repositories known in RepoM. + +### Returns + +Number of actions performed on any known repository. + +### Example + +#### Usage + + +``` +repo_call_count = statistics.overall_count; +``` + diff --git a/global.json b/global.json new file mode 100644 index 00000000..501e79a8 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.100", + "rollForward": "latestFeature" + } +} \ No newline at end of file diff --git a/mdsnippets.json b/mdsnippets.json index 8abb9067..fb0ced03 100644 --- a/mdsnippets.json +++ b/mdsnippets.json @@ -3,7 +3,7 @@ "LinkFormat": "GitHub", "TocLevel": 3, "ExcludeDirectories": [ "_ReSharper.Caches", "packages", "build"], - "MaxWidth": 120, + "MaxWidth": 160, "TreatMissingAsWarning": true, "WriteHeader": false, "ReadOnly": false, diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 964fd760..70624792 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,10 +5,6 @@ true - - - - diff --git a/src/RepoM.ActionMenu.CodeGen/.editorconfig b/src/RepoM.ActionMenu.CodeGen/.editorconfig new file mode 100644 index 00000000..a95b88e2 --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/.editorconfig @@ -0,0 +1,16 @@ +root = false + +[*.cs] + +############################### +# Diagnostics # +############################### + +# CS8321: Local function '...' is declared but never used +dotnet_diagnostic.CS8321.severity = none + +############################### +# ReSharper # +############################### + + diff --git a/src/RepoM.ActionMenu.CodeGen/DocsClassVisitor.cs b/src/RepoM.ActionMenu.CodeGen/DocsClassVisitor.cs new file mode 100644 index 00000000..1dfbb780 --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/DocsClassVisitor.cs @@ -0,0 +1,32 @@ +namespace RepoM.ActionMenu.CodeGen; + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using RepoM.ActionMenu.CodeGen.Models; + +public class DocsClassVisitor : IClassDescriptorVisitor +{ + private readonly ITypeSymbol _typeSymbol; + private readonly IDictionary _files; + + public DocsClassVisitor(ITypeSymbol typeSymbol, IDictionary files) + { + _typeSymbol = typeSymbol; + _files = files; + } + + public void Visit(ActionMenuContextClassDescriptor descriptor) + { + Misc.XmlDocsParser.ExtractDocumentation(_typeSymbol, descriptor, _files); + } + + public void Visit(ActionMenuClassDescriptor descriptor) + { + Misc.XmlDocsParser.ExtractDocumentation(_typeSymbol, descriptor, _files); + } + + public void Visit(ClassDescriptor descriptor) + { + Misc.XmlDocsParser.ExtractDocumentation(_typeSymbol, descriptor, _files); + } +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/DocumentationGenerator.cs b/src/RepoM.ActionMenu.CodeGen/DocumentationGenerator.cs new file mode 100644 index 00000000..99b8ed1d --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/DocumentationGenerator.cs @@ -0,0 +1,84 @@ +namespace RepoM.ActionMenu.CodeGen; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using RepoM.ActionMenu.CodeGen.Models; +using Scriban; +using Scriban.Runtime; + +internal static class DocumentationGenerator +{ + public static async Task GetPluginDocsContentAsync(ProjectDescriptor plugin, Template template) + { + plugin.ActionMenus.Sort((left, right) => string.Compare(left.Name, right.Name, StringComparison.Ordinal)); + + var context = new TemplateContext + { + LoopLimit = 0, + MemberRenamer = x => x.Name, + }; + + var scriptObject = new ScriptObject() + { + { "plugin", plugin }, + }; + scriptObject.Import(typeof(MyStringFunctions)); + + context.PushGlobal(scriptObject); + + return await template.RenderAsync(context); + } + + public static async Task GetDocsContentAsync(ActionMenuContextClassDescriptor module, Template template) + { + module.Members.Sort((left, right) => string.Compare(left.Name, right.Name, StringComparison.Ordinal)); + + var context = new TemplateContext + { + LoopLimit = 0, + MemberRenamer = x => x.Name, + }; + + var scriptObject = new ScriptObject() + { + { "module", module }, + }; + scriptObject.Import(typeof(MyStringFunctions)); + + context.PushGlobal(scriptObject); + + return await template.RenderAsync(context); + } + + public static async Task GetScribanInitializersCSharpCodeAsync(IEnumerable actionContextMenus, Template templateModule) + { + var modules = actionContextMenus.OrderBy(x => x.ClassName).ToList(); + + var context = new TemplateContext + { + LoopLimit = 0, + MemberRenamer = x => x.Name, + EnableRelaxedMemberAccess = false, + }; + + var scriptObject = new ScriptObject() + { + { "modules", modules }, + }; + scriptObject.Import(typeof(MyStringFunctions)); + + context.PushGlobal(scriptObject); + + return await templateModule.RenderAsync(context).ConfigureAwait(false); + } +} + +public static class MyStringFunctions +{ + public static string Hyphenated(string input) + { + return string.Concat(input.Select((x, i) => i > 0 && char.IsUpper(x) ? "-" + x : x.ToString())).ToLowerInvariant(); + } +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/IClassDescriptorVisitor.cs b/src/RepoM.ActionMenu.CodeGen/IClassDescriptorVisitor.cs new file mode 100644 index 00000000..d8ba62c3 --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/IClassDescriptorVisitor.cs @@ -0,0 +1,12 @@ +namespace RepoM.ActionMenu.CodeGen; + +using RepoM.ActionMenu.CodeGen.Models; + +public interface IClassDescriptorVisitor +{ + void Visit(ActionMenuContextClassDescriptor descriptor); + + void Visit(ActionMenuClassDescriptor descriptor); + + void Visit(ClassDescriptor descriptor); +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/Misc/CompilationExtensions.cs b/src/RepoM.ActionMenu.CodeGen/Misc/CompilationExtensions.cs new file mode 100644 index 00000000..92d9707d --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/Misc/CompilationExtensions.cs @@ -0,0 +1,18 @@ +namespace RepoM.ActionMenu.CodeGen.Misc; + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +public static class CompilationExtensions +{ + public static IEnumerable GetTypes(this Compilation compilation) + { + foreach (ISymbol type in compilation.GetSymbolsWithName(_ => true, SymbolFilter.Type)) + { + if (type is ITypeSymbol typeSymbol) + { + yield return typeSymbol; + } + } + } +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/Misc/CompileRepoM.cs b/src/RepoM.ActionMenu.CodeGen/Misc/CompileRepoM.cs new file mode 100644 index 00000000..56556d28 --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/Misc/CompileRepoM.cs @@ -0,0 +1,93 @@ +namespace RepoM.ActionMenu.CodeGen.Misc; + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Broslyn; +using Microsoft.CodeAnalysis; + +public class CompileRepoM +{ + private readonly ConcurrentDictionary> _compilations = new(); + + public Dictionary> Store => _compilations.ToDictionary(x => x.Key, x => x.Value); + + public async Task CompileAsync(string pathToSolution, string projectName) + { + CSharpCompilationCaptureResult compilationCaptureResult = CSharpCompilationCapture.Build(pathToSolution); + Solution solution = compilationCaptureResult.Workspace.CurrentSolution; + + Project[] projects = solution.Projects.ToArray(); + + foreach (Project p in projects) + { + if (_compilations.ContainsKey(p.Name)) + { + continue; + } + + Project project = p.WithParseOptions(p.ParseOptions!.WithDocumentationMode(DocumentationMode.Parse)); + + // Compile the project + Compilation compilation = await project.GetCompilationAsync() ?? throw new Exception("Compilation failed"); + ValidateCompilation(compilation); + + _compilations.AddOrUpdate(p.Name, _ => new Tuple(project, compilation), (_, __) => new Tuple(project, compilation)); + } + + if (_compilations.TryGetValue(projectName, out Tuple? tuple)) + { + return tuple.Item2; + } + + throw new Exception("Not found"); + } + + private static void ValidateCompilation(Compilation compilation) + { + ImmutableArray diagnostics = compilation.GetDiagnostics(); + Diagnostic[] errors = diagnostics.Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error).ToArray(); + + if (errors.Length <= 0) + { + return; + } + + if ("RepoM.Api".Equals(compilation.AssemblyName)) + { + if (errors is [{ Id: "CS8795", },]) + { + return; + } + } + + if ("RepoM.Plugin.AzureDevOps".Equals(compilation.AssemblyName)) + { + if (errors is [{ Id: "CS8795", },]) + { + return; + } + } + + if ("RepoM.Plugin.EverythingFileSearch".Equals(compilation.AssemblyName)) + { + if (errors.Length == 8) + { + return; + } + } + + Console.WriteLine("Compilation errors:"); + foreach (Diagnostic error in errors) + { + Console.WriteLine(error); + } + + Console.WriteLine("Error, Exiting."); + Environment.Exit(1); + throw new Exception("Compilation error"); + } +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/Misc/FileSystemHelper.cs b/src/RepoM.ActionMenu.CodeGen/Misc/FileSystemHelper.cs new file mode 100644 index 00000000..fd823197 --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/Misc/FileSystemHelper.cs @@ -0,0 +1,41 @@ +namespace RepoM.ActionMenu.CodeGen.Misc; + +using System; +using System.IO; + +public static class FileSystemHelper +{ + public static void DeleteFileIsExist(string pathToGeneratedCode) + { + if (!File.Exists(pathToGeneratedCode)) + { + return; + } + + try + { + File.Delete(pathToGeneratedCode); + } + catch (Exception e) + { + Console.WriteLine($"Could not delete generated file '{pathToGeneratedCode}'. {e.Message}"); + throw; + } + } + + public static void CheckDirectory(string path) + { + if (!Directory.Exists(path)) + { + throw new Exception($"Folder '{path}' does not exist"); + } + } + + public static void CheckFile(string path) + { + if (!File.Exists(path)) + { + throw new Exception($"File '{path}' does not exist"); + } + } +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/Misc/SymbolExtensions.cs b/src/RepoM.ActionMenu.CodeGen/Misc/SymbolExtensions.cs new file mode 100644 index 00000000..be584063 --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/Misc/SymbolExtensions.cs @@ -0,0 +1,12 @@ +namespace RepoM.ActionMenu.CodeGen.Misc; + +using System.Linq; +using Microsoft.CodeAnalysis; + +public static class SymbolExtensions +{ + public static AttributeData? FindAttribute(this ISymbol symbol) + { + return symbol.GetAttributes().FirstOrDefault(x => x.AttributeClass!.Name == typeof(T).Name); + } +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/Misc/TypeMatcher.cs b/src/RepoM.ActionMenu.CodeGen/Misc/TypeMatcher.cs new file mode 100644 index 00000000..fbef989f --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/Misc/TypeMatcher.cs @@ -0,0 +1,29 @@ +namespace RepoM.ActionMenu.CodeGen.Misc; + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; + +public static class TypeMatcher +{ + public static bool TypeSymbolMatchesType(ITypeSymbol typeSymbol, Type type, Compilation compilation) + { + return SymbolEqualityComparer.IncludeNullability.Equals(GetTypeSymbolForType(type, compilation), typeSymbol); + } + + public static INamedTypeSymbol GetTypeSymbolForType(Type type, Compilation compilation) + { + if (!type.IsConstructedGenericType) + { + return compilation.GetTypeByMetadataName(type.FullName!)!; + } + + // get all typeInfo's for the Type arguments + IEnumerable typeArgumentsTypeInfos = type.GenericTypeArguments.Select(a => GetTypeSymbolForType(a, compilation)); + + Type openType = type.GetGenericTypeDefinition(); + INamedTypeSymbol? typeSymbol = compilation.GetTypeByMetadataName(openType.FullName!); + return typeSymbol!.Construct(typeArgumentsTypeInfos.ToArray()); + } +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/Misc/XmlDocsParser.cs b/src/RepoM.ActionMenu.CodeGen/Misc/XmlDocsParser.cs new file mode 100644 index 00000000..53e8a7a4 --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/Misc/XmlDocsParser.cs @@ -0,0 +1,341 @@ +namespace RepoM.ActionMenu.CodeGen.Misc; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Web; +using System.Xml; +using System.Xml.Linq; +using Microsoft.CodeAnalysis; +using RepoM.ActionMenu.CodeGen.Models; + +internal static partial class XmlDocsParser +{ + public static void ExtractDocumentation(ISymbol symbol, IXmlDocsExtended desc, IDictionary files) + { + var xmlStr = symbol.GetDocumentationCommentXml(); + ExtractDocumentation(xmlStr, symbol, desc, files); + } + + internal static void ExtractDocumentation(string? xmlStr, ISymbol symbol, IXmlDocsExtended desc, IDictionary files) + { + if (string.IsNullOrEmpty(xmlStr)) + { + return; + } + + try + { + var xmlDoc = XElement.Parse(xmlStr); + var elements = xmlDoc.Elements().ToList(); + + foreach (XElement element in elements) + { + var text = GetCleanedString(element).Trim(); + if (element.Name == "summary") + { + desc.Description = SanitizeMultilineText(text); + } + else if (element.Name == "param") + { + var argName = element.Attribute("name")?.Value; + if (argName == null || symbol is not IMethodSymbol method) + { + continue; + } + IParameterSymbol? parameterSymbol = method.Parameters.FirstOrDefault(x => x.Name == argName); + var isOptional = false; + if (parameterSymbol == null) + { + Console.WriteLine($"Invalid XML doc parameter name {argName} not found on method {method}"); + } + else + { + isOptional = parameterSymbol.IsOptional; + + var displayString = parameterSymbol.Type.ToDisplayString(); + if (displayString.Equals("RepoM.ActionMenu.Core.Model.ActionMenuGenerationContext")) + { + // also check index? should be 0 or 1?! + if (parameterSymbol.Name.Equals("context")) + { + continue; + } + + Console.WriteLine($"ActionMenuGenerationContext with wrong name {parameterSymbol.Name}"); + } + else if (displayString.Equals("RepoM.ActionMenu.Interface.ActionMenuFactory.IActionMenuGenerationContext")) + { + // also check index? should be 0 or 1?! + if (parameterSymbol.Name.Equals("context")) + { + continue; + } + + Console.WriteLine($"IActionMenuGenerationContext with wrong name {parameterSymbol.Name}"); + } + else if (displayString.EndsWith("ActionMenuGenerationContext")) + { + Console.WriteLine(">> ActionMenuGenerationContext with invalid namespace found."); + } + else if (displayString.Equals("Scriban.Parsing.SourceSpan")) + { + // also check index? should be 0 or 1?! + if (parameterSymbol.Name.Equals("span")) + { + continue; + } + + Console.WriteLine($"SourceSpan with wrong name {parameterSymbol.Name}"); + } + } + + desc.Params.Add(new ParamDescriptor(argName, text) { IsOptional = isOptional, }); + } + else if (element.Name == "returns") + { + desc.Returns = text; + } + else if (element.Name == "remarks") + { + desc.Remarks = text; + } + else if (element.Name == "example") + { + ExamplesDescriptor examplesDescriptor = GetExampleData(element, files); + desc.Examples = examplesDescriptor; + } + else if (element.Name == "test") + { + // todo? + _ = _removeCode.Replace(text, string.Empty); + } + else if (element.Name == "inheritdoc") + { + // expect text to be empty + if (!string.IsNullOrWhiteSpace(text)) + { + throw new Exception("Text should be empty in inheritdoc"); + } + + // we need cref + var cref = element.Attribute("cref")?.Value; + + if (string.IsNullOrWhiteSpace(cref)) + { + throw new Exception("Cref should not be empty in inheritdoc"); + } + + if (!cref.StartsWith("P:") && !cref.StartsWith("T:")) + { + throw new Exception("Cref should start with P: or T:"); + } + + desc.InheritDocs = cref[2..]; + } + } + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error while processing `{symbol}` with XML doc `{xmlStr}", ex); + } + } + + private static string SanitizeMultilineText(string text) + { + return text.Replace("\n ", "\n"); + } + + public static ExamplesDescriptor GetExampleData(XNode node, IDictionary files) + { + var result = new ExamplesDescriptor(); + + if (node.NodeType != XmlNodeType.Element) + { + return result; + // expect example node + } + + var element = (XElement)node; + if (element.Name != "example") + { + return result; + // expect example node + } + + XNode[] nodes = element.Nodes().ToArray(); + if (nodes.Length == 0) + { + result.Description = element.Value.Trim(); + return result; + } + + foreach (XNode item in nodes) + { + if (item is XText xText) + { + result.Items.Add(new Text() { Content = xText.Value.Trim(), }); + + } + else if (item is XElement xElement) + { + if (xElement.Name == "para") + { + result.Items.Add(new Paragraph { Text = xElement.Value.Trim(), }); + } + else if (xElement.Name == "usage") + { + result.Items.Add(new Header { Text = "Usage", }); + } + else if (xElement.Name == "result") + { + result.Items.Add(new Header { Text = "Result", }); + } + else if (xElement.Name == "repository-action-sample") + { + result.Items.Add(new Header { Text = "RepositoryAction sample", }); + } + else if (xElement.Name == "code") + { + result.Items.Add(new Code() { Content = xElement.Value.Trim(), Language = null, UseRaw = false, }); + } + else if (xElement.Name == "snippet") + { + // markdown snippet + XAttribute? customAttribute = xElement.Attributes().SingleOrDefault(x => x.Name == "name"); + if (customAttribute == null) + { + throw new Exception("name attribute should exist"); + } + var name = customAttribute.Value.Trim(); + + if (string.IsNullOrEmpty(name)) + { + throw new Exception("Name should not be null"); + } + + var snippet = new Snippet { Name = name, }; + + customAttribute = xElement.Attributes().SingleOrDefault(x => x.Name == "mode"); + if (customAttribute != null) + { + if (Enum.TryParse(customAttribute.Value.Trim().AsSpan(), true, out SnippetMode mode)) + { + snippet.Mode = mode; + } + else + { + throw new Exception("mode attribute should not be empty"); + } + } + + customAttribute = xElement.Attributes().SingleOrDefault(x => x.Name == "language"); + if (customAttribute != null) + { + if (!string.IsNullOrWhiteSpace(customAttribute.Value)) + { + snippet.Language = customAttribute.Value.Trim(); + } + else + { + throw new Exception("language attribute should not be empty"); + } + } + + result.Items.Add(snippet); + } + else if (xElement.Name == "code-file") + { + XAttribute? customAttribute = xElement.Attributes().SingleOrDefault(x => x.Name == "filename"); + if (customAttribute == null) + { + throw new Exception("filename attribute should exist"); + } + var filename = customAttribute.Value.Trim(); + + if (!files.TryGetValue(filename, out var content)) + { + throw new Exception($"File '{filename}' not found"); + } + + var code = new Code { Content = content, UseRaw = true, }; + + customAttribute = xElement.Attributes().SingleOrDefault(x => x.Name == "language"); + if (customAttribute != null) + { + if (!string.IsNullOrWhiteSpace(customAttribute.Value)) + { + code.Language = customAttribute.Value.Trim(); + } + else + { + throw new Exception("language attribute should not be empty"); + } + } + + // check if file exists, load sample + result.Items.Add(code); + } + else if (xElement.Name == "md-snippet") + { + Console.WriteLine("WARNING md-snippet"); + var snippetName = xElement.Value.Trim(); + result.Items.Add(new Text() { Content = "snippet: " + snippetName, }); + } + else if (xElement.Name == "md-include") + { + Console.WriteLine("WARNING md-include"); + var includeName = xElement.Value.Trim(); + result.Items.Add(new Text() { Content = "include: " + includeName, }); + } + else + { + throw new Exception($"'{xElement.Name}' Not expected"); + } + } + } + + return result; + } + + public static string GetCleanedString(XNode node) + { + if (node.NodeType == XmlNodeType.Text) + { + // return node.ToString(); + var s = node.ToString(); + return HttpUtility.HtmlDecode(s); + } + + var element = (XElement)node; + string text; + if (element.Name == "paramref") + { + text = element.Attribute("name")?.Value ?? string.Empty; + } + else + { + var builder = new StringBuilder(); + foreach (var subElement in element.Nodes()) + { + builder.Append(GetCleanedString(subElement)); + } + + text = builder.ToString(); + } + + if (element.Name == "para") + { + text += "\n"; + } + return HttpUtility.HtmlDecode(text); + } + + + private static readonly Regex _removeCode = RemoveCodeRegex(); + + [GeneratedRegex("^\\s*```\\w*[ \\t]*[\\r\\n]*", RegexOptions.Multiline)] + private static partial Regex RemoveCodeRegex(); +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/Models/ActionMenuClassDescriptor.cs b/src/RepoM.ActionMenu.CodeGen/Models/ActionMenuClassDescriptor.cs new file mode 100644 index 00000000..0fe65a27 --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/Models/ActionMenuClassDescriptor.cs @@ -0,0 +1,20 @@ +namespace RepoM.ActionMenu.CodeGen.Models; + +using System.Collections.Generic; +using System.Diagnostics; + +[DebuggerDisplay($"{{{nameof(ClassName)},nq}}")] +public class ActionMenuClassDescriptor : ClassDescriptor +{ + /// + /// Properties + /// + public List ActionMenuProperties { get; set; } = new List(); + + public string RepositoryActionName => Name; + + public override void Accept(IClassDescriptorVisitor visitor) + { + visitor.Visit(this); + } +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/Models/ActionMenuContextClassDescriptor.cs b/src/RepoM.ActionMenu.CodeGen/Models/ActionMenuContextClassDescriptor.cs new file mode 100644 index 00000000..8efe8549 --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/Models/ActionMenuContextClassDescriptor.cs @@ -0,0 +1,14 @@ +namespace RepoM.ActionMenu.CodeGen.Models; + +using System.Diagnostics; + +[DebuggerDisplay($"{{{nameof(ActionMenuContextObjectName)},nq}}")] +public class ActionMenuContextClassDescriptor : ClassDescriptor +{ + public string ActionMenuContextObjectName => Name; + + public override void Accept(IClassDescriptorVisitor visitor) + { + visitor.Visit(this); + } +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/Models/ClassDescriptor.cs b/src/RepoM.ActionMenu.CodeGen/Models/ClassDescriptor.cs new file mode 100644 index 00000000..060f69e5 --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/Models/ClassDescriptor.cs @@ -0,0 +1,65 @@ +namespace RepoM.ActionMenu.CodeGen.Models; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using RepoM.ActionMenu.CodeGen; + +public enum SymbolType +{ + Class, + + Enum, +} + +[DebuggerDisplay($"{{{nameof(ClassName)},nq}}")] +public class ClassDescriptor : IXmlDocsExtended +{ + private SymbolType _symbolType = SymbolType.Class; + + /// + /// Properties, Functions, fields etc. etc. + /// + public List Members { get; set; } = []; + + /// + /// Friendly name + /// + public string Name { get; init; } = null!; + + public string ClassName { get; set; } = null!; + + public string Namespace { get; set; } = null!; + + public bool IsEnum => _symbolType == SymbolType.Enum; + + public bool IsClass => _symbolType == SymbolType.Class; + + public void SetType(SymbolType type) + { + _symbolType = type; + } + + // interface: + + public string Description { get; set; } = null!; + + public string? InheritDocs { get; set; } + + string IXmlDocsExtended.Returns + { + get => throw new NotSupportedException("no returns for class."); + set => throw new NotSupportedException("no returns for class."); + } + + public string Remarks { get; set; } = null!; + + public ExamplesDescriptor? Examples { get; set; } + + List IXmlDocsExtended.Params => throw new NotSupportedException("no params for class."); + + public virtual void Accept(IClassDescriptorVisitor visitor) + { + visitor.Visit(this); + } +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/Models/ExamplesDescriptor.cs b/src/RepoM.ActionMenu.CodeGen/Models/ExamplesDescriptor.cs new file mode 100644 index 00000000..52f13b68 --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/Models/ExamplesDescriptor.cs @@ -0,0 +1,65 @@ +namespace RepoM.ActionMenu.CodeGen.Models; + +using System.Collections.Generic; + +public class ExamplesDescriptor +{ + public string? Description { get; set; } + + public List Items { get; } = []; +} + +public abstract class ExampleItemBase +{ + public abstract string TypeName { get; } +} + +public sealed class Code : ExampleItemBase +{ + public override string TypeName { get; } = nameof(Code); + + public string? Language { get; set; } = null; + + public required string Content { get; init; } + + public bool UseRaw { get; set; } +} + +public sealed class Paragraph : ExampleItemBase +{ + public override string TypeName { get; } = nameof(Paragraph); + + public required string Text { get; init; } +} + +public sealed class Text : ExampleItemBase +{ + public override string TypeName { get; } = nameof(Text); + + public string Content { get; init; } = null!; +} + +public sealed class Header : ExampleItemBase +{ + public override string TypeName { get; } = nameof(Header); + + public required string Text { get; init; } +} + +public sealed class Snippet : ExampleItemBase +{ + public override string TypeName { get; } = nameof(Snippet); + + public SnippetMode Mode { get; set; } = SnippetMode.Include; + + public string? Language { get; set; } + + public required string Name { get; init; } +} + +public enum SnippetMode +{ + Include, + + Snippet, +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/Models/IXmlDocsExtended.cs b/src/RepoM.ActionMenu.CodeGen/Models/IXmlDocsExtended.cs new file mode 100644 index 00000000..73b3c067 --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/Models/IXmlDocsExtended.cs @@ -0,0 +1,18 @@ +namespace RepoM.ActionMenu.CodeGen.Models; + +using System.Collections.Generic; + +public interface IXmlDocsExtended +{ + string Description { get; set; } + + string? InheritDocs { get; set; } + + string Returns { get; set; } + + string Remarks { get; set; } + + ExamplesDescriptor? Examples { get; set; } + + List Params { get; } +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/Models/MemberDescriptor.cs b/src/RepoM.ActionMenu.CodeGen/Models/MemberDescriptor.cs new file mode 100644 index 00000000..c8a4d5c9 --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/Models/MemberDescriptor.cs @@ -0,0 +1,134 @@ +namespace RepoM.ActionMenu.CodeGen.Models; + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +public static class TypeInfoDescriptorFactory +{ + public static TypeInfoDescriptor Create(ITypeSymbol typeSymbol) + { + var displayString = typeSymbol.ToDisplayString(); + if (Program.TypeInfos.TryGetValue(displayString, out TypeInfoDescriptor? typeInfoDescriptor)) + { + return typeInfoDescriptor; + } + + var result = new TypeInfoDescriptor(typeSymbol); + Program.TypeInfos.Add(displayString, result); + return result; + } +} + +public class TypeInfoDescriptor +{ + public TypeInfoDescriptor(ITypeSymbol typeSymbol) + : this (typeSymbol.Name, typeSymbol.ToDisplayString()) + { + Nullable = IsNullableType(typeSymbol); + + if (Name.Contains("AutoCompleteOptionsV1")) + { + // do nothing intentionally, just for debugging coenm + } + } + + public TypeInfoDescriptor(string name, string csharpTypeName) + { + CSharpTypeName = csharpTypeName; + Name = name; + + if (CSharpTypeName.Contains("RepoM")) + { + // Name = CSharpTypeName.Split('.').Last(); + Name = name; + } + + if (!csharpTypeName.Contains('.')) + { + // primitive? + Name = CSharpTypeName; + } + + if ("System.Collections.Generic.List".Equals(CSharpTypeName)) + { + Name = "List"; + } + } + + public string CSharpTypeName { get; } + + public string Name { get; } + + public string? Link { get; init; } + + public bool Nullable { get; set; } + + private static bool IsNullableType(ITypeSymbol typeSymbol) + { + return typeSymbol is INamedTypeSymbol { NullableAnnotation: NullableAnnotation.Annotated, }; + } +} + +/// +/// Property, Function, field etc. etc. +/// +public class MemberDescriptor : IXmlDocsExtended +{ + /// + /// Friendly Name + /// + public string Name { get; init; } = null!; + + public string CSharpName { get; set; } = null!; + + public TypeInfoDescriptor? ReturnType { get; set; } + + public string XmlId { get; set; } = null!; + + public bool IsCommand { get; set; } + + public bool IsAction { get; set; } + + public bool IsFunc { get; set; } + + public bool IsConst { get; set; } + + /// + /// Used for C# code generation + /// + public string? Cast { get; set; } + + public string Description { get; set; } = null!; + + public string? InheritDocs { get; set; } + + public string Returns { get; set; } = null!; + + public string Remarks { get; set; } = null!; + + public ExamplesDescriptor? Examples { get; set; } + + public List Params { get; } = []; +} + +public class ActionMenuMemberDescriptor : MemberDescriptor +{ + // public RepositoryActionAttribute RepositoryActionAttribute { get; init; } + + public bool IsTemplate { get; set; } + + public bool IsPredicate { get; set; } + + public bool IsContext { get; set; } + + public object DefaultValue { get; set; } = null!; + + public bool IsReturnEnumerable { get; set; } + + public string? RefType { get; set; } +} + +public class ActionMenuContextMemberDescriptor : MemberDescriptor +{ + public string ActionMenuContextMemberName => Name; +} diff --git a/src/RepoM.ActionMenu.CodeGen/Models/ParamDescriptor.cs b/src/RepoM.ActionMenu.CodeGen/Models/ParamDescriptor.cs new file mode 100644 index 00000000..6c7a67a3 --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/Models/ParamDescriptor.cs @@ -0,0 +1,17 @@ +namespace RepoM.ActionMenu.CodeGen.Models +{ + public class ParamDescriptor + { + public ParamDescriptor(string name, string description) + { + Name = name; + Description = description; + } + + public string Name { get; set; } + + public string Description { get; set; } + + public bool IsOptional { get; set; } + } +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/Models/ProjectDescriptor.cs b/src/RepoM.ActionMenu.CodeGen/Models/ProjectDescriptor.cs new file mode 100644 index 00000000..d1251d6c --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/Models/ProjectDescriptor.cs @@ -0,0 +1,64 @@ +namespace RepoM.ActionMenu.CodeGen.Models; + +using System.Collections.Generic; +using System.Diagnostics; +using RepoM.Core.Plugin.AssemblyInformation; +using Scriban.Runtime; + +[DebuggerDisplay("{ProjectName,nq}")] +public sealed class ProjectDescriptor +{ + /// + /// Assembly Name + /// + public string AssemblyName { get; set; } = null!; + + /// + /// Project name + /// + public string ProjectName { get; set; } = null!; + + /// + /// List of class descriptors for repository actions. + /// + public List ActionMenus { get; } = new(); + + /// + /// List of class descriptors for context (ie scriban methods, properties) + /// + public List ActionContextMenus { get; } = new(); + + /// + /// Regular types (to be used when action type has sub type property) + /// + public List Types { get; } = new(); + + /// + /// when project is plugin, the pluginname. + /// + public string? PluginName { get; private set; } + + /// + /// when project is plugin, the plugin description. + /// + public string? PluginDescription { get; private set; } + + /// + /// when project is plugin, the plugin markdown description. + /// + public string? PluginMarkdownDescription { get; private set; } + + /// + /// is plugin or not. + /// + public bool IsPlugin { get; private set; } = false; + + [ScriptMemberIgnore] + public void SetPackageInformation(PackageAttribute attribute) + { + PluginName = attribute.Name; + PluginDescription = attribute.ToolTip; + PluginMarkdownDescription = attribute.Description; + IsPlugin = true; + } +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/ProcessMembersVisitor.cs b/src/RepoM.ActionMenu.CodeGen/ProcessMembersVisitor.cs new file mode 100644 index 00000000..969bfaed --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/ProcessMembersVisitor.cs @@ -0,0 +1,390 @@ +namespace RepoM.ActionMenu.CodeGen; + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using RepoM.ActionMenu.CodeGen.Misc; +using RepoM.ActionMenu.CodeGen.Models; +using RepoM.ActionMenu.Interface.Attributes; +using RepoM.ActionMenu.Interface.YamlModel.ActionMenus; +using RepoM.ActionMenu.Interface.YamlModel.Templating; +using Text = RepoM.ActionMenu.Interface.YamlModel.Templating.Text; + +public class ProcessMembersVisitor : IClassDescriptorVisitor +{ + private readonly ITypeSymbol _typeSymbol; + private readonly IDictionary _files; + + // todo extend. + private static readonly string[] _collectionTypes = + { + "System.Collections.Generic.List", + "System.Collections.Generic.IList", + "System.Collections.Generic.IEnumerable", + }; + + public ProcessMembersVisitor(ITypeSymbol typeSymbol, IDictionary files) + { + _typeSymbol = typeSymbol; + _files = files; + } + + public void Visit(ActionMenuContextClassDescriptor descriptor) + { + foreach (ISymbol member in _typeSymbol.GetMembers()) + { + AttributeData? attr = member.FindAttribute(); + if (attr == null) + { + // normal member -> skip and continue. + continue; + } + + var actionMenuContextMemberAttribute = new ActionMenuContextMemberAttribute((attr.ConstructorArguments[0].Value as string)!); + // action menu context member. + + var className = member.ContainingSymbol.Name; + + var memberDescriptor = new ActionMenuContextMemberDescriptor + { + Name = actionMenuContextMemberAttribute.Alias, + CSharpName = member.Name, + //ReturnType = propertyMember.Type.ToDisplayString(), // (member as IPropertySymbol)?.Type; + IsCommand = false, + XmlId = member.GetDocumentationCommentId() ?? string.Empty, + }; + + if (member is IMethodSymbol method) + { + memberDescriptor.ReturnType = TypeInfoDescriptorFactory.Create(method.ReturnType); + memberDescriptor.IsCommand = method.ReturnsVoid; + + memberDescriptor.CSharpName = method.Name; + + memberDescriptor.IsAction = method.ReturnsVoid; + memberDescriptor.IsFunc = !memberDescriptor.IsAction; + + var builder = new StringBuilder(); + builder.Append(memberDescriptor.IsAction ? "Action" : "Func"); + + if (method.Parameters.Length > 0 || memberDescriptor.IsFunc) + { + builder.Append('<'); + } + + for (var i = 0; i < method.Parameters.Length; i++) + { + IParameterSymbol parameter = method.Parameters[i]; + if (i > 0) + { + builder.Append(", "); + } + + builder.Append(parameter.Type.ToDisplayString()); + } + + if (memberDescriptor.IsFunc) + { + if (method.Parameters.Length > 0) + { + builder.Append(", "); + } + builder.Append(method.ReturnType.ToDisplayString()); + } + + if (method.Parameters.Length > 0 || memberDescriptor.IsFunc) + { + builder.Append('>'); + } + + memberDescriptor.Cast = $"({builder})"; + } + + if (member is IPropertySymbol property) // or field IFieldSymbol + { + memberDescriptor.ReturnType = TypeInfoDescriptorFactory.Create(property.Type); + memberDescriptor.IsConst = true; + } + + descriptor.Members.Add(memberDescriptor); + + Misc.XmlDocsParser.ExtractDocumentation(member, memberDescriptor, _files); + } + } + + public void Visit(ActionMenuClassDescriptor descriptor) + { + foreach (ISymbol member in _typeSymbol.GetMembers()) + { + if (member is not IPropertySymbol propertyMember) + { + continue; + } + + if (propertyMember.SetMethod == null) + { + // property is readonly + continue; + } + + if (propertyMember.GetMethod == null) + { + // property is writeonly + continue; + } + + // Name = name, + // XmlId = member.GetDocumentationCommentId() ?? string.Empty, + // Category = string.Empty, + // IsCommand = method?.ReturnsVoid ?? false, + // Module = moduleToGenerate, + + SymbolDisplayFormat symbolDisplayFormat = SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining).WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.None); + + void JustSomeCode(ITypeSymbol symbol) + { + var Name = symbol.ToDisplayString(symbolDisplayFormat); + var IsEnumerable = symbol.AllInterfaces.Any(x => x.ToString() == "System.Collections.IEnumerable"); + var IsVoid = symbol.SpecialType == SpecialType.System_Void; + if (IsEnumerable && (symbol is INamedTypeSymbol namedSymbol)) + { + var firstTypeArgumentName = namedSymbol.TypeArguments.FirstOrDefault()?.ToDisplayString(symbolDisplayFormat); + } + } + + static bool IsSystemType(IPropertySymbol symbol) + { + var typeFullName = symbol.Type.ToString(); + return typeFullName?.StartsWith("System.") ?? false; + } + + // single type in collection (array, list, ..) (no tuple or whatsoever) + static bool IsCollection(IPropertySymbol symbol, [NotNullWhen(true)] out ITypeSymbol? genericType) + { + if (symbol.Type is IArrayTypeSymbol ats) + { + genericType = ats.ElementType; + return true; + } + + var originalDefinitionDisplayName = symbol.Type.OriginalDefinition.ToDisplayString(); + + var displayString = symbol.Type.ToDisplayString(); + + if (_collectionTypes.Contains(originalDefinitionDisplayName)) + { + // must be singe due to + genericType = ((INamedTypeSymbol)symbol.Type).TypeArguments.Single(); + return true; + } + + genericType = null; + return false; + } + + var propertyReturnType = propertyMember.Type.ToDisplayString(); + + bool IsTypeOrNullableType() + { + var typeFullName = typeof(T).FullName ?? string.Empty; + return propertyReturnType.Equals(typeFullName) || propertyReturnType.Equals(typeFullName + "?"); + } + + var memberDescriptor = new ActionMenuMemberDescriptor + { + CSharpName = propertyMember.Name, + ReturnType = TypeInfoDescriptorFactory.Create(propertyMember.Type), // (member as IPropertySymbol)?.Type; + XmlId = member.GetDocumentationCommentId() ?? string.Empty, + }; + + if (IsTypeOrNullableType()) + { + memberDescriptor.IsTemplate = true; + + AttributeData? attr = propertyMember.FindAttribute(); + if (attr?.ConstructorArguments.Length == 1) + { + var textAttribute = new TextAttribute((attr.ConstructorArguments[0].Value as string)!); + memberDescriptor.DefaultValue = textAttribute.DefaultValue; + } + } + else if (IsTypeOrNullableType()) + { + memberDescriptor.IsPredicate = true; + + AttributeData? attr = propertyMember.FindAttribute(); + if (attr != null) + { + var predicateAttribute = new PredicateAttribute((bool)attr.ConstructorArguments[0].Value!); + memberDescriptor.DefaultValue = predicateAttribute.DefaultValue; + } + } + else if (IsTypeOrNullableType()) + { + memberDescriptor.IsContext = true; + } + else if (IsCollection(propertyMember, out ITypeSymbol? genericType)) + { + // todo + // is is a collection of x. process x + memberDescriptor.IsReturnEnumerable = true; + } + else if (IsSystemType(propertyMember)) + { + // ie string, int, bool, .. + Console.WriteLine("d"); + } + else if (propertyReturnType.Contains("RepoM.ActionMenu.CodeGenDummyLibrary.ActionMenu.Model.ActionMenus.AutoCompleteOptionsV1")) + { + // aditional checks? + // todo, name + memberDescriptor.RefType = $"{propertyMember.ContainingModule.Name}; {propertyReturnType}"; + } + + // if (!typeSymbol.Interfaces.Any(namedTypeSymbol => namedTypeSymbol.Equals(actionMenuInterface, SymbolEqualityComparer.Default))) + // { + // continue; + // } + + XmlDocsParser.ExtractDocumentation(member, memberDescriptor, _files); + + if (string.IsNullOrWhiteSpace(memberDescriptor.Description) && string.IsNullOrWhiteSpace(memberDescriptor.InheritDocs)) + { + if (!memberDescriptor.CSharpName.Equals("Type")) + { + Console.WriteLine($"Skip property '{_typeSymbol.Name}.{memberDescriptor.CSharpName}' due to missing description"); + } + + continue; + } + + descriptor.ActionMenuProperties.Add(memberDescriptor); + } + } + + public void Visit(ClassDescriptor descriptor) + { + if (_typeSymbol is INamedTypeSymbol { TypeKind: TypeKind.Enum, } symbol) + { + descriptor.SetType(SymbolType.Enum); + + // INamedTypeSymbol? underlyingType = symbol.EnumUnderlyingType; + + var memberNames = _typeSymbol + .GetMembers() + .Where(static member => member.Kind is SymbolKind.Field) + // .Select(static symbol => symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)) + ; + + foreach (ISymbol member in memberNames) + { + var memberDescriptor = new MemberDescriptor + { + CSharpName = member.Name, + IsCommand = false, + XmlId = member.GetDocumentationCommentId() ?? string.Empty, + }; + + Misc.XmlDocsParser.ExtractDocumentation(member, memberDescriptor, _files); + descriptor.Members.Add(memberDescriptor); + } + + return; + } + + foreach (ISymbol member in _typeSymbol.GetMembers()) + { + if (member is not IPropertySymbol propertyMember) + { + // only interested in properties. + continue; + } + + if (!member.CanBeReferencedByName) + { + continue; + } + + if (member.DeclaredAccessibility == Accessibility.Private) + { + continue; + } + + if (member.IsStatic) + { + continue; + } + + + // only normal members. + var className = member.ContainingSymbol.Name; + + var memberDescriptor = new MemberDescriptor + { + CSharpName = member.Name, + //ReturnType = propertyMember.Type.ToDisplayString(), // (member as IPropertySymbol)?.Type; + IsCommand = false, + XmlId = member.GetDocumentationCommentId() ?? string.Empty, + }; + + if (member is IMethodSymbol method) + { + memberDescriptor.ReturnType = TypeInfoDescriptorFactory.Create(method.ReturnType); + memberDescriptor.IsCommand = method.ReturnsVoid; + + memberDescriptor.CSharpName = method.Name; + + memberDescriptor.IsAction = method.ReturnsVoid; + memberDescriptor.IsFunc = !memberDescriptor.IsAction; + + var builder = new StringBuilder(); + builder.Append(memberDescriptor.IsAction ? "Action" : "Func"); + + if (method.Parameters.Length > 0 || memberDescriptor.IsFunc) + { + builder.Append('<'); + } + + for (var i = 0; i < method.Parameters.Length; i++) + { + IParameterSymbol parameter = method.Parameters[i]; + if (i > 0) + { + builder.Append(", "); + } + + builder.Append(parameter.Type.ToDisplayString()); + } + + if (memberDescriptor.IsFunc) + { + if (method.Parameters.Length > 0) + { + builder.Append(", "); + } + builder.Append(method.ReturnType.ToDisplayString()); + } + + if (method.Parameters.Length > 0 || memberDescriptor.IsFunc) + { + builder.Append('>'); + } + + memberDescriptor.Cast = $"({builder})"; + } + + if (member is IPropertySymbol property) // or field IFieldSymbol + { + memberDescriptor.ReturnType = TypeInfoDescriptorFactory.Create(property.Type); + memberDescriptor.IsConst = true; + } + + descriptor.Members.Add(memberDescriptor); + + Misc.XmlDocsParser.ExtractDocumentation(member, memberDescriptor, _files); + } + } +} \ No newline at end of file diff --git a/src/RepoM.ActionMenu.CodeGen/Program.cs b/src/RepoM.ActionMenu.CodeGen/Program.cs new file mode 100644 index 00000000..f8518cd4 --- /dev/null +++ b/src/RepoM.ActionMenu.CodeGen/Program.cs @@ -0,0 +1,348 @@ +namespace RepoM.ActionMenu.CodeGen; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using RepoM.ActionMenu.CodeGen.Misc; +using RepoM.ActionMenu.CodeGen.Models; +using RepoM.ActionMenu.Core.TestLib.Utils; +using RepoM.ActionMenu.Interface.Attributes; +using RepoM.ActionMenu.Interface.YamlModel; +using RepoM.Core.Plugin.AssemblyInformation; +using Scriban; + +public static class Program +{ + internal static readonly Dictionary TypeInfos = new() + { + { + typeof(Interface.YamlModel.Templating.Text).FullName!, + new TypeInfoDescriptor(nameof(Text), typeof(Interface.YamlModel.Templating.Text).FullName!) + { + Link = "repository_action_types.md#text", + } + }, + { + typeof(Interface.YamlModel.Templating.Predicate).FullName!, + new TypeInfoDescriptor(nameof(Interface.YamlModel.Templating.Predicate), typeof(Interface.YamlModel.Templating.Predicate).FullName!) + { + Link = "repository_action_types.md#predicate", + } + }, + { + typeof(Interface.YamlModel.ActionMenus.Context).FullName!, + new TypeInfoDescriptor(nameof(Interface.YamlModel.ActionMenus.Context), typeof(Interface.YamlModel.ActionMenus.Context).FullName!) + { + Link = "repository_action_types.md#context", + } + }, + { + typeof(Interface.YamlModel.ActionMenus.Context).FullName! + "?", + new TypeInfoDescriptor(nameof(Interface.YamlModel.ActionMenus.Context), typeof(Interface.YamlModel.ActionMenus.Context).FullName! + "?") + { + Link = "repository_action_types.md#context", + } + }, + }; + + public static async Task Main() + { + // var ns = typeSymbol.ContainingNamespace.ToDisplayString(); + // var fullClassName = $"{ns}.{className}"; + var compile = new CompileRepoM(); + var rootFolder = ThisProjectAssembly.Info.GetSolutionDirectory(); + var srcFolder = Path.Combine(rootFolder, "src"); + var docsFolderSource = Path.Combine(rootFolder, "docs_new", "mdsource"); + var docsFolder = Path.Combine(rootFolder, "docs_new"); + + FileSystemHelper.CheckDirectory(srcFolder); + FileSystemHelper.CheckDirectory(docsFolder); + FileSystemHelper.CheckDirectory(Path.Combine(rootFolder, ".git")); + + var projects = new List + { + "RepoM.ActionMenu.Interface", // this is for the description of the interface types and its members. + "RepoM.ActionMenu.Core", + + "RepoM.Plugin.AzureDevOps", + "RepoM.Plugin.Clipboard", + "RepoM.Plugin.EverythingFileSearch", + "RepoM.Plugin.Heidi", + "RepoM.Plugin.LuceneQueryParser", + "RepoM.Plugin.SonarCloud", + "RepoM.Plugin.Statistics", + "RepoM.Plugin.WebBrowser", + "RepoM.Plugin.WindowsExplorerGitInfo", + }; + + Template templateModule = await LoadTemplateAsync("Templates/ScribanModuleRegistration.scriban-cs"); + Template templateDocs = await LoadTemplateAsync("Templates/DocsScriptVariables.scriban-txt"); + Template templatePluginDocs = await LoadTemplateAsync("Templates/DocsPlugin.scriban-txt"); + + Dictionary files = await LoadFiles(); + + var processedProjects = new Dictionary(); + + foreach (var project in projects) + { + var fullCsProjectFilename = Path.Combine(srcFolder, project, $"{project}.csproj"); + FileSystemHelper.CheckFile(fullCsProjectFilename); + + ProjectDescriptor projectDescriptor = await CompileAndExtractProjectDescription(compile, fullCsProjectFilename, project, files); + processedProjects.Add(project, projectDescriptor); + } + + Dictionary> _allTypes2 = new(); + + foreach ((var projectName, ProjectDescriptor project) in processedProjects) + { + foreach (var classDescriptor in project.ActionMenus) + { + if (!_allTypes2.ContainsKey(classDescriptor.Namespace + "." + classDescriptor.ClassName)) + { + _allTypes2.Add(classDescriptor.Namespace + "." + classDescriptor.ClassName, new List()); + } + + foreach (var memberDescriptor in classDescriptor.ActionMenuProperties) + { + _allTypes2[classDescriptor.Namespace + "." + classDescriptor.ClassName].Add(memberDescriptor); + } + foreach (var memberDescriptor in classDescriptor.Members) + { + _allTypes2[classDescriptor.Namespace + "." + classDescriptor.ClassName].Add(memberDescriptor); + } + } + + foreach (var classDescriptor in project.ActionContextMenus) + { + if (!_allTypes2.ContainsKey(classDescriptor.Namespace + "." + classDescriptor.ClassName)) + { + _allTypes2.Add(classDescriptor.Namespace + "." + classDescriptor.ClassName, new List()); + } + foreach (var memberDescriptor in classDescriptor.Members) + { + _allTypes2[classDescriptor.Namespace + "." + classDescriptor.ClassName].Add(memberDescriptor); + } + } + + foreach (var classDescriptor in project.Types) + { + if (!_allTypes2.ContainsKey(classDescriptor.Namespace + "." + classDescriptor.ClassName)) + { + _allTypes2.Add(classDescriptor.Namespace + "." + classDescriptor.ClassName, new List()); + } + foreach (var memberDescriptor in classDescriptor.Members) + { + _allTypes2[classDescriptor.Namespace + "." + classDescriptor.ClassName].Add(memberDescriptor); + } + } + } + + processedProjects.Remove("RepoM.ActionMenu.Interface"); + + // Copy descriptions from if (string.IsNullOrWhiteSpace(memberDescriptor.Description) && string.IsNullOrWhiteSpace(memberDescriptor.InheritDocs)) + foreach ((var projectName, ProjectDescriptor project) in processedProjects) + { + foreach (ActionMenuClassDescriptor classDescriptor in project.ActionMenus) + { + foreach (ActionMenuMemberDescriptor memberDescriptor in classDescriptor.ActionMenuProperties) + { + if (!string.IsNullOrWhiteSpace(memberDescriptor.Description) || string.IsNullOrWhiteSpace(memberDescriptor.InheritDocs)) + { + continue; + } + + var index = memberDescriptor.InheritDocs.LastIndexOf('.'); + var className = memberDescriptor.InheritDocs[..index]; + var typeName = memberDescriptor.InheritDocs[(index + 1)..]; + + if (_allTypes2.TryGetValue(className, out List? xxx)) + { + MemberDescriptor? matchMemberDescriptor = xxx.SingleOrDefault(x => x.CSharpName == typeName); + if (matchMemberDescriptor != null) + { + memberDescriptor.Description = matchMemberDescriptor.Description; + } + else + { + Console.WriteLine("InheritDocs not found"); + } + } + else + { + Console.WriteLine("InheritDocs not found"); + } + } + } + } + + + // Generate plugin documentation + foreach ((var projectName, ProjectDescriptor? project) in processedProjects) + { + if (project.IsPlugin) + { + var name = project.ProjectName.ToLowerInvariant(); + var fileName = Path.Combine(docsFolderSource, $"plugin_{name}.generated.source.md"); + var content = await DocumentationGenerator.GetPluginDocsContentAsync(project, templatePluginDocs).ConfigureAwait(false); + await File.WriteAllTextAsync(fileName, content).ConfigureAwait(false); + + fileName = Path.Combine(docsFolder, $"plugin_{name}.generated.md"); + if (!File.Exists(fileName)) + { + await File.WriteAllTextAsync(fileName, content).ConfigureAwait(false); + } + } + else + { + // core + var fileName = Path.Combine(docsFolderSource, "repom.generated.source.md"); + var content = await DocumentationGenerator.GetPluginDocsContentAsync(project, templatePluginDocs).ConfigureAwait(false); + await File.WriteAllTextAsync(fileName, content).ConfigureAwait(false); + + fileName = Path.Combine(docsFolder, "repom.generated.md"); + if (!File.Exists(fileName)) + { + await File.WriteAllTextAsync(fileName, content).ConfigureAwait(false); + } + } + } + + // Generate module site documentation + foreach ((var projectName, ProjectDescriptor? project) in processedProjects) + { + foreach (ActionMenuContextClassDescriptor actionContextMenu in project.ActionContextMenus) + { + var name = actionContextMenu.Name.ToLowerInvariant(); + var fileName = Path.Combine(docsFolderSource, $"script_variables_{name}.generated.source.md"); + var content = await DocumentationGenerator.GetDocsContentAsync(actionContextMenu, templateDocs).ConfigureAwait(false); + await File.WriteAllTextAsync(fileName, content).ConfigureAwait(false); + + fileName = Path.Combine(docsFolder, $"script_variables_{name}.generated.md"); + if (!File.Exists(fileName)) + { + await File.WriteAllTextAsync(fileName, content).ConfigureAwait(false); + } + } + } + + // Generate module registration code in c#. + foreach ((var projectName, ProjectDescriptor? project) in processedProjects) + { + var fileName = Path.Combine(srcFolder, projectName, "RepoMCodeGen.generated.cs"); + + if (project.ActionContextMenus.Count == 0) + { + FileSystemHelper.DeleteFileIsExist(fileName); + continue; + } + + var content = await DocumentationGenerator.GetScribanInitializersCSharpCodeAsync(project.ActionContextMenus, templateModule).ConfigureAwait(false); + await File.WriteAllTextAsync(fileName, content).ConfigureAwait(false); + } + } + + public static async Task CompileAndExtractProjectDescription(CompileRepoM compile, string pathToSolution, string project, IDictionary files) + { + Compilation compilation = await compile.CompileAsync(pathToSolution, project).ConfigureAwait(false); + + var projectDescriptor = new ProjectDescriptor + { + AssemblyName = compilation.AssemblyName ?? throw new Exception("Could not determine AssemblyName"), + ProjectName = project, + }; + + AttributeData? assemblyAttribute = compilation.Assembly.GetAttributes().SingleOrDefault(x => x.AttributeClass?.Name == nameof(PackageAttribute)); + if (assemblyAttribute != null) + { + var pa = new PackageAttribute( + (assemblyAttribute.ConstructorArguments[0].Value as string)!, + (assemblyAttribute.ConstructorArguments[1].Value as string)!, + (assemblyAttribute.ConstructorArguments[2].Value as string)!); + projectDescriptor.SetPackageInformation(pa); + } + + ProcessProject(compilation, projectDescriptor, files); + + return projectDescriptor; + } + + private static void ProcessProject(Compilation compilation, ProjectDescriptor projectDescriptor, IDictionary files) + { + foreach (ITypeSymbol typeSymbol in compilation.GetTypes()) + { + ProcessMembersVisitor memberVisitor = new(typeSymbol, files); + DocsClassVisitor docsClassVisitor = new(typeSymbol, files); + + ClassDescriptor classDescriptor; + + AttributeData? obsoleteAttribute = typeSymbol.FindAttribute(); + AttributeData? actionMenuContextAttribute = typeSymbol.FindAttribute(); + AttributeData? repositoryActionAttribute = typeSymbol.FindAttribute(); + + if (actionMenuContextAttribute != null && obsoleteAttribute == null) + { + var actionMenuContextClassDescriptor = new ActionMenuContextClassDescriptor + { + Name = new ActionMenuContextAttribute((string)actionMenuContextAttribute.ConstructorArguments[0].Value!).Name!, + }; + + projectDescriptor.ActionContextMenus.Add(actionMenuContextClassDescriptor); + + classDescriptor = actionMenuContextClassDescriptor; + } + else if (repositoryActionAttribute != null && obsoleteAttribute == null) + { + var actionMenuClassDescriptor = new ActionMenuClassDescriptor + { + Name = new RepositoryActionAttribute((string)repositoryActionAttribute.ConstructorArguments[0].Value!).Type, + }; + projectDescriptor.ActionMenus.Add(actionMenuClassDescriptor); + + classDescriptor = actionMenuClassDescriptor; + } + else + { + classDescriptor = new ClassDescriptor(); + projectDescriptor.Types.Add(classDescriptor); + } + + classDescriptor.ClassName = typeSymbol.Name; + classDescriptor.Namespace = typeSymbol.ContainingNamespace.ToDisplayString(); + + classDescriptor.Accept(docsClassVisitor); + classDescriptor.Accept(memberVisitor); + } + } + + public static async Task