Skip to content

Commit 96e8b03

Browse files
committed
Added the capabillity to set option flags as MANDATORY.
1 parent d8bc7ff commit 96e8b03

File tree

25 files changed

+112
-65
lines changed

25 files changed

+112
-65
lines changed

Docs/Design_command.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Design your command
22

3-
Think of your command as an one line command with some parameters in a cmd prompt environment, it should do a small amount of isolated task in a specific context, for example lets say you want to convert yaml file to json or xml. A good name could be **ConvertCommand**, as parameters you have a path to the input file, and a O for the format and a value to that option. That is a pretty good design for one Command.
3+
Think of your command as an one line command with some parameters in a cmd prompt environment, it should do a small amount of isolated task in a specific context, for example lets say you want to convert yaml file to json or xml. A good name could be **ConvertCommand**, as parameters you have a path to the input file, and an option flag for the format and a value to that option. That is a pretty good design for one Command.
44

55
The usage of this command will look like this if I want to convert my yaml fil to json.
66

@@ -36,12 +36,17 @@ public class ConvertCommand : CommandBase<PowerCommandsConfiguration>
3636
}
3737
}
3838
```
39-
Notice that the documentation for how this command will be used is created with the [PowerCommandDesign](PowerCommandDesignAttribute.md) attribute. I realize that the path option should be required, I can of course code this check in the Command but I do not need to do that, instead I declare that with the [PowerCommandDesign](PowerCommandDesignAttribute.md) attribute, I just add a **!** before the path option, this will be validated before the execution of the run command. The new design look like this.
39+
## Validation of mandatory option and value
40+
Notice that the documentation for how this command will be used is created with the [PowerCommandDesign](PowerCommandDesignAttribute.md) attribute. I realize that the path option should be both **mandatory** and must have a value, I can of course add code to do this check in the Command but I do not need to do that, instead I declare that with the [PowerCommandDesign](PowerCommandDesignAttribute.md) attribute, I just add a **!** before the path option, this will be validated before the execution of the run command. I also spell the option name in UPPERCASE letters which does the option **mandatory**.
41+
42+
The new design look like this.
4043
```
4144
[PowerCommandDesign( description: "Converting yaml format to json or xml format",
42-
options: "!path|format",
45+
options: "!PATH|format",
4346
example: "//Convert to json format|convert --path \"c:\\temp\\test.yaml\" --format json|//Convert to xml format|convert --path \"c:\\temp\\test.yaml\" --format xml")]
4447
```
48+
Have a closer look at the !PATH option, it starts with a **!** and it consists of upperletter cases only **PATH**, with the use of the **!** sympol the option must have a value. With the use of uppercase letter the option it self is **mandatory**.
49+
4550
If I run the command empty I trigger a validation error.
4651

4752
![Alt text](images/convert_validation_error.png?raw=true "Describe convert command")

Docs/Options.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Options
22

33
Options is what the name implies, options for your command, lets have a look at this example below.
4+
45
![Alt text](images/power_command_attribute.png?raw=true "Attributes")
56

67
You could also se that this Command has two options separated with pipe **"path|format"**, first one named **path** and the other one is **format**. This will give the user autocomplete feedback when typing - and using the [Tab] tangent.
@@ -17,6 +18,18 @@ Sometimes you just want a option without a value, you can solve that like this:
1718

1819
``` var encrypt = input.HasOption("encrypt"); ```
1920

21+
## Validation of mandatory option and value
22+
The option can trigger validation of **required value** and/or **mandatory** meaning the option is not optional.
23+
24+
- A starting **!** means that the option must have a value if the user adds it to the input, but it is still optional.
25+
- UPPERCASE option name means that the option must be included by the user input, but value is not required unless the option starts with the **!** symbol.
26+
27+
The example below shows how the path option is both mandatory an requires a value.
28+
```
29+
[PowerCommandDesign( description: "Converting yaml format to json or xml format",
30+
options: "!PATH|format",
31+
example: "//Convert to json format|convert --path \"c:\\temp\\test.yaml\" --format json|//Convert to xml format|convert --path \"c:\\temp\\test.yaml\" --format xml")]
32+
```
2033
## The CommandBase class holds a list of the declared options with the input value
2134
That is another way to programatically work with the options.
2235

Templates/PowerCommands.zip

530 Bytes
Binary file not shown.

Templates/src/Core/PainKiller.PowerCommands.Core/BASECLASSES/CommandBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public virtual void RunCompleted()
7979

8080
#region Write helpers
8181
public void Write(string output, ConsoleColor? color = null) => _console.Write(GetType().Name, output, color);
82-
public void WriteLine(string output) => _console.WriteLine(GetType().Name, output, null);
82+
public void WriteLine(string output) => _console.WriteLine(GetType().Name, output);
8383
/// <summary>
8484
/// Could be use when passing the method to shell execute and you need to get back what was written and you do not want that in a logfile (a secret for example)
8585
/// </summary>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public void ShowDoc()
2222
var docSearch = Input.HasOption("docs") ? Input.GetOptionValue("docs").ToLower() : Input.SingleArgument.ToLower();
2323
var docs = StorageService<DocsDB>.Service.GetObject().Docs;
2424
var matchDocs = docs.Where(d => d.DocID.ToString().PadLeft(4, '0') == docSearch || d.Name.ToLower().Contains(docSearch) || d.Tags.ToLower().Contains(docSearch)).ToArray();
25-
if (matchDocs.Length == 1)
25+
if (matchDocs.Length == 1 || matchDocs.Select(d => d.DocID).Distinct().Count() == 1)
2626
{
2727
ShellService.Service.OpenWithDefaultProgram(matchDocs.First().Uri);
2828
return;

Templates/src/Core/PainKiller.PowerCommands.Core/MANAGERS/InputValidationManager.cs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,15 @@ public InputValidationResult ValidateAndInitialize()
3030
var optionInfos = attribute.Options.Split('|').Select(f => new PowerOption(f)).ToList();
3131
retVal.Options.AddRange(optionInfos);
3232

33-
foreach (var optionInfo in optionInfos.Where(f => f.IsRequired))
33+
foreach (var optionInfo in optionInfos.Where(f => f.ValueIsRequired || f.IsMandatory))
3434
{
35+
if (optionInfo.IsMandatory && !_input.HasOption(optionInfo.Name))
36+
{
37+
retVal.HasValidationError = true;
38+
_logger.Invoke($"Option [{optionInfo.Name}] is mandatory");
39+
}
3540
optionInfo.Value = _input.GetOptionValue(optionInfo.Name);
36-
37-
if (!string.IsNullOrEmpty(optionInfo.Value) || !_input.HasOption(optionInfo.Name)) continue;
41+
if (!string.IsNullOrEmpty(optionInfo.Value) || !_input.HasOption(optionInfo.Name) || !optionInfo.ValueIsRequired) continue;
3842
_logger.Invoke($"Option [{optionInfo.Name}] is required to have a value or not used at all.");
3943
retVal.HasValidationError = true;
4044
}
@@ -43,18 +47,10 @@ public InputValidationResult ValidateAndInitialize()
4347
var requiredSecrets = attribute.Secrets.Split('|').Where(s => s.StartsWith('!')).ToArray();
4448
foreach (var secretName in requiredSecrets)
4549
{
46-
if (IPowerCommandServices.DefaultInstance!.Configuration.Secret.Secrets == null)
47-
{
48-
_logger.Invoke($"Secret [{secretName}] is required");
49-
retVal.HasValidationError = true;
50-
break;
51-
}
5250
var secret = IPowerCommandServices.DefaultInstance!.Configuration.Secret.Secrets.FirstOrDefault(s => s.Name.ToLower() == secretName.Replace("!","").ToLower());
53-
if (secret == null)
54-
{
55-
_logger.Invoke($"Secret [{secretName.Replace("!","")}] is required");
56-
retVal.HasValidationError = true;
57-
}
51+
if (secret != null) continue;
52+
_logger.Invoke($"Secret [{secretName.Replace("!","")}] is required");
53+
retVal.HasValidationError = true;
5854
}
5955
return retVal;
6056
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,10 @@
1414
<ProjectReference Include="..\PainKiller.PowerCommands.Shared\PainKiller.PowerCommands.Shared.csproj" />
1515
</ItemGroup>
1616

17+
<ItemGroup>
18+
<None Update="DocsDB.data">
19+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
20+
</None>
21+
</ItemGroup>
22+
1723
</Project>
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
namespace $safeprojectname$.DomainObjects.Core;
1+
using $safeprojectname$.Extensions;
2+
3+
namespace $safeprojectname$.DomainObjects.Core;
24

35
public class PowerOption
46
{
57
public PowerOption(string attributeValue)
68
{
7-
IsRequired = attributeValue.StartsWith("!");
9+
IsMandatory = attributeValue.Replace("!", "").IsUppercaseOnly();
10+
ValueIsRequired = attributeValue.StartsWith("!");
811
Name = attributeValue.Replace("!", "");
912
}
1013
public string Name { get; set; }
1114
public string Value { get; set; } = "";
1215
public string Raw => $"--{Name}";
13-
public bool IsRequired { get; set; }
16+
public bool ValueIsRequired { get; set; }
17+
public bool IsMandatory { get; set; }
1418
}

Templates/src/Core/PainKiller.PowerCommands.Shared/EXTENSIONS/StringExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ public static string ToOptionDescription(this string option)
1818
}
1919
public static string RemoveHtml(this string content) => Regex.Replace($"{content}", @"<[^>]*>", String.Empty);
2020
public static List<string> ExtractLinks(this string content) => new Regex("http(s)?://([\\w+?\\.\\w+])+([a-zA-Z0-9\\~\\!\\@\\#\\$\\%\\^\\&amp;\\*\\(\\)_\\-\\=\\+\\\\\\/\\?\\.\\:\\;\\'\\,]*)?", RegexOptions.IgnoreCase).Matches(content).Select(m => m.Value).ToList();
21+
public static bool IsUppercaseOnly(this string input) => new Regex("^[A-Z]+$").IsMatch(input);
2122
}

Templates/src/PainKiller.PowerCommands.Bootstrap/PainKiller.PowerCommands.Bootstrap.csproj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
<ProjectReference Include="..\Core\PainKiller.PowerCommands.Shared\PainKiller.PowerCommands.Shared.csproj" />
1616
<ProjectReference Include="..\$ext_projectname$Commands\$ext_projectname$Commands.csproj" />
1717
</ItemGroup>
18-
1918
<ItemGroup>
2019
<Folder Include="Extensions\" />
2120
</ItemGroup>
22-
21+
<ItemGroup>
22+
<ProjectCapability Include="ConfigurableFileNesting" />
23+
<ProjectCapability Include="ConfigurableFileNestingFeatureEnabled" />
24+
</ItemGroup>
2325
</Project>

0 commit comments

Comments
 (0)