Skip to content

Commit ce6b76f

Browse files
committed
Version 1.0.4.3
1 parent 43a8b21 commit ce6b76f

File tree

68 files changed

+1408
-473
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+1408
-473
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ You could see PowerCommands as your CLI application starter kit. It is a structu
33

44
[Follow progress on twitter](https://twitter.com/PowerCommands) <img src="https://github.com/PowerCommands/PowerCommands2022/blob/main/Docs/images/Twitter.png?raw=true" alt="drawing" width="20"/>
55

6-
6+
## Version 1.0.4.3
7+
**Released 2025-01-27**
8+
- New basic functionality for translation added.
9+
- Improved documentation functionality.
10+
- Added dialog for file and folder select to DialogService.
711
## Version 1.0.4.2
812
**Released 2025-01-27**
913
- New feature InfoPanel

Templates/PowerCommands.zip

9.64 KB
Binary file not shown.

Templates/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ I recommend you to use the option ```Place solution in the same directory``` som
1818
![Alt text](../Docs/images/VS_solution_option.png?raw=true "Command Base")
1919

2020
# What is new?
21+
## Version 1.0.4.3
22+
**Released 2025-01-27**
23+
- New basic functionality for translation added.
24+
- Improved documentation functionality.
25+
- Added dialog for file and folder select to DialogService.
2126
## Version 1.0.4.2
2227
**Released 2025-01-27**
2328
- New feature InfoPanel

Templates/src/Core/PainKiller.PowerCommands.Configuration/DOMAINOBJECTS/ConfigurationGlobals.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ public static class ConfigurationGlobals
99
public const char ArraySplitter = '|';
1010
public const string SetupConfigurationFile = "setup.yaml";
1111
public const string EncryptionEnvironmentVariableName = "_encryptionManager";
12+
public const string UserNamePlaceholder = "%USERNAME%";
13+
public const string RoamingDirectoryPlaceholder = "$ROAMING$";
14+
public const string QueryPlaceholder = "$QUERY$";
15+
public const string DocsDirectoryName = "Docs";
1216

1317
public static readonly string ApplicationDataFolder = Path.Combine($"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}\\{nameof(PowerCommands)}", ApplicationName);
1418
public static readonly string EnvironmentVariableName = $"{nameof(PowerCommands)}_{ApplicationName}";

Templates/src/Core/PainKiller.PowerCommands.Configuration/PainKiller.PowerCommands.Configuration.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<LangVersion>12.0</LangVersion>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
8-
<Version></Version>
8+
<Version>1.0.4.3</Version>
99
</PropertyGroup>
1010

1111
<ItemGroup>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace $safeprojectname$.BaseClasses;
2+
3+
public class CommandWithTranslationsBase<TConfig>(string identifier, TConfig configuration, bool autoShowToolbar = true, ConsoleColor[]? colors = null) : CommandWithToolbarBase<TConfig>(identifier, configuration, autoShowToolbar, colors) where TConfig : new()
4+
{
5+
public override bool InitializeAndValidateInput(ICommandLineInput input, PowerCommandDesignAttribute? designAttribute = null)
6+
{
7+
TranslationManager.Instance.LoadTranslations();
8+
return base.InitializeAndValidateInput(input, designAttribute);
9+
}
10+
public override RunResult Run() => Ok();
11+
protected void AddTranslations(List<TranslatedLabel> labels) => TranslationManager.Instance.AddTranslations(labels);
12+
protected void AddTranslation(string name, string translation) => TranslationManager.Instance.AddTranslation(name, translation);
13+
protected virtual TranslatedLabel? GetTranslationByName(string translationName) => TranslationManager.Instance.GetTranslationByName(translationName);
14+
protected virtual TranslatedLabel? GetTranslationByDisplayName(string displayName) => TranslationManager.Instance.GetTranslationByDisplayName(displayName);
15+
protected void SaveTranslations() => TranslationManager.Instance.SaveTranslations();
16+
protected void ShowTranslation()
17+
{
18+
WriteHeadLine("Global translations:");
19+
foreach (var translatedLabel in TranslationManager.Instance.Translations.TranslatedLabels.OrderBy(t => t.Name).ThenBy(t => t.DisplayName)) WriteCodeExample(translatedLabel.Name.PadRight(60), translatedLabel.DisplayName);
20+
}
21+
}

Templates/src/Core/PainKiller.PowerCommands.Core/COMMANDS/CdCommand.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ public override RunResult Run()
105105
if (!string.IsNullOrEmpty(dir)) paths.Add(dir);
106106
path = string.Join(Path.DirectorySeparatorChar, paths);
107107
}
108-
path = $"{path}".Replace("%USERNAME%", Environment.UserName, StringComparison.CurrentCultureIgnoreCase);
109-
if (path.Contains("$ROAMING$")) path = path.Replace("$ROAMING$", ConfigurationGlobals.ApplicationDataFolder);
108+
path = $"{path}".Replace(ConfigurationGlobals.UserNamePlaceholder, Environment.UserName, StringComparison.CurrentCultureIgnoreCase);
109+
if (path.Contains(ConfigurationGlobals.RoamingDirectoryPlaceholder)) path = path.Replace(ConfigurationGlobals.RoamingDirectoryPlaceholder, ConfigurationGlobals.ApplicationDataFolder);
110110
if (Directory.Exists(path)) Environment.CurrentDirectory = $"{Path.GetFullPath(path)}";
111111
else WriteFailureLine($"[{path}] does not exist");
112112
ShowDirectories();

Templates/src/Core/PainKiller.PowerCommands.Core/COMMANDS/DescribeCommand.cs

Lines changed: 145 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,54 +3,177 @@
33
[PowerCommandTest(tests: "exit|start --docs")]
44
[PowerCommandDesign(description: "With help command you will be shown the provided description or online documentation of the command or a PowerCommand feature.",
55
arguments: "<command name or feature you are interested of knowing more>",
6-
options: "docs|clear",
6+
options: "new|all",
77
disableProxyOutput: true,
88
example: "describe exit|describe cls|describe log|//Open documentation about options (if any)|describe options --doc")]
99
public class DescribeCommand(string identifier, CommandsConfiguration configuration) : CommandBase<CommandsConfiguration>(identifier, configuration)
1010
{
11+
protected static List<Doc> Items = new();
12+
protected static Doc SelectedItem = new();
1113
public override RunResult Run()
1214
{
13-
if (Input.HasOption("docs")) ShowDoc();
14-
else ShowCommand();
15+
if (HasOption("new"))
16+
{
17+
SelectedItem = new Doc();
18+
AddOrEdit();
19+
return Ok();
20+
}
21+
if (HasOption("all"))
22+
{
23+
Items = StorageService<DocsDB>.Service.GetObject().Docs;
24+
ShowResult("All created descriptions...");
25+
if(Items.Count == 0) WriteLine("No description documents added...");
26+
return Ok();
27+
}
28+
ShowDoc();
1529
return Ok();
1630
}
17-
1831
public void ShowDoc()
1932
{
20-
var docSearch = Input.HasOption("docs") ? Input.GetOptionValue("docs").ToLower() : Input.SingleArgument.ToLower();
33+
var docSearch = Input.SingleArgument.ToLower();
2134
var docs = StorageService<DocsDB>.Service.GetObject().Docs;
2235
var matchDocs = docs.Where(d => d.DocID.ToString().PadLeft(4, '0') == docSearch || d.Name.ToLower().Contains(docSearch) || d.Tags.ToLower().Contains(docSearch)).ToArray();
23-
if (matchDocs.Length == 1 || matchDocs.Select(d => d.DocID).Distinct().Count() == 1)
36+
if (Input.Arguments.Length > 1)
2437
{
25-
ShellService.Service.OpenWithDefaultProgram(matchDocs.First().Uri);
26-
return;
38+
var arguments = Input.Arguments.ToList();
39+
//Add filters as many as the user have given, but limit the filter count to 10, higher value must some kind of abuse.
40+
var iterations = arguments.Count < 100 ? arguments.Count : 10;
41+
for (var i = 1; i < iterations; i++) matchDocs = matchDocs.Where(d => d.Name.ToLower().Contains(arguments[i].ToLower()) || d.Tags.ToLower().Contains(arguments[i].ToLower())).ToArray();;
2742
}
28-
if (matchDocs.Length > 1)
43+
if (matchDocs.Length > 0)
2944
{
30-
WriteHeadLine($"Found {matchDocs.Length} number of documents, you can use the docID the for digit number to the right to choose document to show.");
31-
foreach (var matchDoc in matchDocs)
32-
{
33-
WriteLine($"{matchDoc.DocID.ToString().PadLeft(4, '0')} {matchDoc.Name} {matchDoc.Uri.Split('/').Last()} {matchDoc.Tags}");
34-
}
45+
Items = matchDocs.ToList();
46+
if (ShowResult(docSearch)) ShellService.Service.OpenWithDefaultProgram(matchDocs.First().Uri);
3547
return;
3648
}
37-
WriteHeadLine("Could not find any command or documentation to describe");
38-
WriteHeadLine("Documentation");
39-
foreach (var doc in docs) WriteLine($"{doc.Name} {doc.Uri.Split('/').Last()} {doc.Tags}");
49+
if(ShowCommand()) return;
50+
WriteHeadLine("Could not find any command or documentation to describe, configured AI services will try to get you an answer.");
51+
ShellService.Service.OpenWithDefaultProgram(Configuration.DefaultAIBotUri.Replace(ConfigurationGlobals.QueryPlaceholder, string.Join(" ", Input.Arguments)));
4052
}
41-
42-
private void ShowCommand()
53+
private bool ShowCommand()
4354
{
4455
var commandIdentifier = string.IsNullOrEmpty(Input.SingleArgument) ? "describe" : Input.SingleArgument;
4556
var command = IPowerCommandsRuntime.DefaultInstance?.Commands.FirstOrDefault(c => c.Identifier == commandIdentifier);
4657
if (command == null)
4758
{
4859
if (Input.Identifier != nameof(DescribeCommand).ToLower().Replace("command", "")) WriteLine($"Command with identifier:{Input.Identifier} not found");
49-
ShowDoc();
50-
return;
60+
return false;
5161
}
52-
HelpService.Service.ShowHelp(command, Input.HasOption("clear"));
62+
HelpService.Service.ShowHelp(command, clearConsole: true);
5363
Console.WriteLine();
64+
return true;
65+
}
66+
protected bool ShowResult(string headLine)
67+
{
68+
var selected = ListService.ListDialog($"{headLine}\nSearch phrase(s): {Input.Raw.Replace("find ","")}", Items.Select(i => $"{i.Name} {i.Uri} {i.Tags}").ToList());
69+
if (selected.Count == 0) return false;
70+
71+
SelectedItem = Items[selected.Keys.First()];
72+
var navigateOption = ToolbarService.NavigateToolbar<DescribeDialogAlternatives>();
73+
if(navigateOption == DescribeDialogAlternatives.Continue) return false;
74+
if(navigateOption == DescribeDialogAlternatives.Open) return OpenDocument();
75+
if (navigateOption == DescribeDialogAlternatives.Edit) return AddOrEdit();
76+
if (navigateOption == DescribeDialogAlternatives.Delete) return Delete();
77+
if (navigateOption == DescribeDialogAlternatives.CreateMarkdownFile) return CreateMarkdownFile(SelectedItem.Name);
78+
if (navigateOption == DescribeDialogAlternatives.UseYourAIService) return OpenAIService();
79+
return false;
80+
}
81+
public bool OpenAIService()
82+
{
83+
var arguments = new List<string> { $"{SelectedItem.Name}" };
84+
arguments.AddRange(SelectedItem.Tags.Split(","));
85+
ShellService.Service.OpenWithDefaultProgram(Configuration.DefaultAIBotUri.Replace(ConfigurationGlobals.QueryPlaceholder, string.Join(" ", arguments)));
86+
return true;
87+
}
88+
public bool OpenDocument()
89+
{
90+
ShellService.Service.OpenWithDefaultProgram(SelectedItem.Uri);
91+
WriteHeadLine(SelectedItem.Name);
92+
WriteCodeExample("Tags:", SelectedItem.Tags);
93+
WriteCodeExample("Uri:", SelectedItem.Uri);
94+
WriteCodeExample("Last updated:", SelectedItem.Updated.ToShortDateString());
95+
return true;
96+
}
97+
public bool AddOrEdit()
98+
{
99+
if(string.IsNullOrEmpty(SelectedItem.Name)) SelectedItem.Name = string.IsNullOrEmpty(Input.SingleArgument) ? DialogService.QuestionAnswerDialog("Name your decription:") : Input.SingleArgument;
100+
SelectedItem.Tags = DialogService.QuestionAnswerDialog("Add tags separated with ,");
101+
var attachFile = DialogService.YesNoDialog("Attach a file? (or just add a uri in next step)");
102+
if (attachFile)
103+
{
104+
var selectedFile = DialogService.SelectFileDialog(Environment.CurrentDirectory, onlyTextFiles: true);
105+
if (!string.IsNullOrEmpty(selectedFile))
106+
{
107+
var targetDirPath = Path.Combine(ConfigurationGlobals.ApplicationDataFolder, ConfigurationGlobals.DocsDirectoryName);
108+
var selectedFileInfo = new FileInfo(selectedFile);
109+
if(!Directory.Exists(targetDirPath)) Directory.CreateDirectory(targetDirPath);
110+
if (File.Exists(Path.Combine(Environment.CurrentDirectory, selectedFile)) && !File.Exists(Path.Combine(targetDirPath, selectedFileInfo.Name)))
111+
{
112+
var sourceFile = Path.Combine(Environment.CurrentDirectory, selectedFile);
113+
var destinationFile = Path.Combine(targetDirPath, selectedFileInfo.Name);
114+
File.Copy(sourceFile, destinationFile);
115+
WriteSuccessLine($"File [{sourceFile}] copied to [{destinationFile}].");
116+
}
117+
SelectedItem.Uri = $"{ConfigurationGlobals.RoamingDirectoryPlaceholder}\\{ConfigurationGlobals.DocsDirectoryName}\\{selectedFileInfo.Name}";
118+
}
119+
}
120+
else SelectedItem.Uri = DialogService.QuestionAnswerDialog("Add uri (could be left blank)");
121+
var db = StorageService<DocsDB>.Service.GetObject();
122+
if (SelectedItem.DocID > -1)
123+
{
124+
var existingDoc = db.Docs.FirstOrDefault(d => d.DocID == SelectedItem.DocID);
125+
if (existingDoc != null) db.Docs.Remove(existingDoc);
126+
}
127+
else
128+
{
129+
if (db.Docs.Count == 0) SelectedItem.DocID = 1;
130+
else SelectedItem.DocID = db.Docs.Max(d => d.DocID) + 1;
131+
if (string.IsNullOrEmpty(SelectedItem.Uri)) if ( DialogService.YesNoDialog("Do you want to add a markdown file to this description?")) CreateMarkdownFile(SelectedItem.Name);
132+
}
133+
db.Docs.Add(SelectedItem);
134+
StorageService<DocsDB>.Service.StoreObject(db);
135+
WriteSuccessLine($"\nDocument [{SelectedItem.Name}] saved.");
136+
return true;
137+
}
138+
public bool Delete()
139+
{
140+
var db = StorageService<DocsDB>.Service.GetObject();
141+
var existingDoc = db.Docs.FirstOrDefault(d => d.DocID == SelectedItem.DocID);
142+
if (existingDoc == null) return false;
143+
var confirmDeletion = DialogService.YesNoDialog($"Delete [{SelectedItem.Name}] are you sure?");
144+
if (!confirmDeletion) return false;
145+
db.Docs.Remove(existingDoc);
146+
StorageService<DocsDB>.Service.StoreObject(db);
147+
WriteSuccessLine($"\nDocument [{SelectedItem.Name}] deleted.");
148+
if (SelectedItem.Uri.StartsWith(ConfigurationGlobals.RoamingDirectoryPlaceholder))
149+
{
150+
var localFileExist = db.Docs.Any(d => d.Uri == SelectedItem.Uri);
151+
if (!localFileExist)
152+
{
153+
var localFilePath = SelectedItem.Uri.Replace(ConfigurationGlobals.RoamingDirectoryPlaceholder, ConfigurationGlobals.ApplicationDataFolder);
154+
if(File.Exists(localFilePath)) File.Delete(localFilePath);
155+
WriteSuccessLine($"\nFile [{localFilePath}] deleted.");
156+
}
157+
}
158+
return true;
159+
}
160+
public bool CreateMarkdownFile(string name)
161+
{
162+
var targetDirPath = Path.Combine(ConfigurationGlobals.ApplicationDataFolder, ConfigurationGlobals.DocsDirectoryName);
163+
var fileName = Path.Combine(targetDirPath, $"{name}.md");
164+
var markdownEditor = new MarkdownEditorManager(fileName, ConsoleService.Service, Configuration.Prompt);
165+
markdownEditor.Run();
166+
SelectedItem.Uri = $"{ConfigurationGlobals.RoamingDirectoryPlaceholder}\\{ConfigurationGlobals.DocsDirectoryName}\\{name}.md";
167+
168+
var db = StorageService<DocsDB>.Service.GetObject();
169+
var existingDoc = db.Docs.FirstOrDefault(d => d.DocID == SelectedItem.DocID);
170+
ShellService.Service.OpenWithDefaultProgram(fileName);
171+
if (existingDoc == null) return false;
172+
db.Docs.Remove(existingDoc);
173+
db.Docs.Add(SelectedItem);
174+
StorageService<DocsDB>.Service.StoreObject(db);
175+
WriteSuccessLine($"\nDocument [{fileName}] created.");
176+
return true;
54177
}
55178
}
56179
}

Templates/src/Core/PainKiller.PowerCommands.Core/EXTENSIONS/FileHandlingExtensions.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace $safeprojectname$.Extensions
1+
using System.Globalization;
2+
3+
namespace $safeprojectname$.Extensions
24
{
35
public static class FileHandlingExtensions
46
{
@@ -15,5 +17,6 @@ public static ProxyResult GetLatestOutput(this string identifier)
1517
var fileName = Path.Combine(ConfigurationGlobals.ApplicationDataFolder, $"proxy_{identifier}.data");
1618
return StorageService<ProxyResult>.Service.GetObject(fileName);
1719
}
20+
public static string GetGlobalTranslationsFileName(this ITranslations translations) => Path.Combine(AppContext.BaseDirectory, $"gloooobal_translation_{CultureInfo.CurrentUICulture}.yaml");
1821
}
1922
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using PainKiller.PowerCommands.Shared.Extensions;
2+
3+
namespace $safeprojectname$.Managers;
4+
5+
public class MarkdownEditorManager
6+
{
7+
private const string Exit = "/XT";
8+
private readonly MarkdownFileService _markdownService = MarkdownFileService.Instance;
9+
private readonly string _filePath;
10+
private readonly IConsoleService _consoleService;
11+
private readonly string _prompt;
12+
13+
private readonly List<string> _commands =
14+
[
15+
Exit,
16+
"#", "##", "###",
17+
"---", // Horizontal rule
18+
"`code`", // Code
19+
"*italic*", "**bold**", "***bold italic***"
20+
];
21+
22+
public MarkdownEditorManager(string filePath, IConsoleService consoleService, string prompt)
23+
{
24+
_filePath = filePath;
25+
_consoleService = consoleService;
26+
_prompt = prompt;
27+
Console.WriteLine($"Markdown Editor - Start writing your Markdown line by line. Use '{Exit}' to save the markdown file.");
28+
Console.WriteLine("Use tabs to get suggestion of what you can add to the document, just the simplest stuff supported.");
29+
Console.WriteLine("The document will be opened in your editor of choice when you are done here.");
30+
}
31+
public void Run()
32+
{
33+
while (true)
34+
{
35+
Console.Write($"{_prompt} ");
36+
var input = _consoleService.ReadInputWithCompletion(_commands);
37+
38+
if (input.Equals(Exit, StringComparison.OrdinalIgnoreCase))
39+
{
40+
SaveToFile();
41+
Console.WriteLine($"Markdown file saved to {_filePath}");
42+
break;
43+
}
44+
45+
ProcessInput(input);
46+
}
47+
}
48+
private void ProcessInput(string input)
49+
{
50+
if (input.StartsWith("#"))
51+
{
52+
int level = input.TakeWhile(c => c == '#').Count();
53+
string text = input.TrimStart('#').Trim();
54+
_markdownService.AddHeader(text, level);
55+
}
56+
else if (input.StartsWith("---"))
57+
{
58+
_markdownService.AddHorizontalRule();
59+
}
60+
else if (input.StartsWith("`code`"))
61+
{
62+
_markdownService.AddCode(string.Join("\n", input.Replace("`code`", "")));
63+
}
64+
else if (input.StartsWith("*italic*"))
65+
{
66+
_markdownService.AddParagraph($"*{input.Replace("*italic*","")}*");
67+
}
68+
else if (input.StartsWith("**bold**"))
69+
{
70+
_markdownService.AddParagraph($"**{input.Replace("**bold**","")}**");
71+
}
72+
else if (input.StartsWith("***bold italic***"))
73+
{
74+
_markdownService.AddParagraph($"***{input.Replace("***bold italic***","")}***");
75+
}
76+
else
77+
{
78+
_markdownService.AddParagraph(input);
79+
}
80+
}
81+
private void SaveToFile() => File.WriteAllText(_filePath, _markdownService.ToString());
82+
}

0 commit comments

Comments
 (0)