Skip to content

Commit 5b0957e

Browse files
authored
Merge pull request #9 from MORYX-Industry/feature/state-machines
Add interface to add state machines
2 parents 20b849f + 81c9008 commit 5b0957e

39 files changed

+1052
-60
lines changed

Directory.Build.targets

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project>
3+
4+
<PropertyGroup>
5+
<MicrosoftCodeAnalysisVersion>4.10.0</MicrosoftCodeAnalysisVersion>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Update="Microsoft.CodeAnalysis.Common" Version="$(MicrosoftCodeAnalysisVersion)" />
10+
<PackageReference Update="Microsoft.CodeAnalysis.CSharp" Version="$(MicrosoftCodeAnalysisVersion)" />
11+
<PackageReference Update="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisVersion)" />
12+
<PackageReference Update="Microsoft.CodeAnalysis.Workspaces.Common" Version="$(MicrosoftCodeAnalysisVersion)" />
13+
</ItemGroup>
14+
</Project>

Moryx.Cli.sln

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moryx.Cli.Config", "src\Mor
1515
EndProject
1616
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{6FC4CD2F-8BFA-4EC1-84A9-D3670BA92E4D}"
1717
EndProject
18+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{81E6FB33-4958-4022-B93B-6B82B06AEE74}"
19+
ProjectSection(SolutionItems) = preProject
20+
Directory.Build.targets = Directory.Build.targets
21+
EndProjectSection
22+
EndProject
1823
Global
1924
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2025
Debug|Any CPU = Debug|Any CPU

src/Moryx.Cli.Commands/Add.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,28 @@ public static CommandResult Module(AddOptions options)
2727
return AddThing(options, (settings) => AddModule.Exec(settings, options.Name!));
2828
}
2929

30+
public static CommandResult StateMachine(AddStatesOptions options)
31+
{
32+
var addOptions = new AddOptions
33+
{
34+
Branch = options.Branch,
35+
Name = options.ResourceName,
36+
Pull = options.Pull,
37+
Template = options.Template,
38+
};
39+
var states = options.States?
40+
.Split(',')
41+
.Select(x => x.Trim())
42+
.ToList()
43+
?? new List<string>();
44+
var transitions = options.States?
45+
.Split(',')
46+
.Select(x => x.Trim())
47+
.ToList()
48+
?? new List<string>();
49+
return AddThing(addOptions, (settings) => AddStates.Exec(settings, options.ResourceName!, states, transitions));
50+
}
51+
3052
private static CommandResult AddThing(AddOptions options, Func<TemplateSettings, CommandResult> func)
3153
{
3254
var currentDir = Environment.CurrentDirectory;
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
using Moryx.Cli.Template;
2+
using Moryx.Cli.Template.Models;
3+
using Moryx.Cli.Commands.Extensions;
4+
using Moryx.Cli.Template.StateBaseTemplate;
5+
using Moryx.Cli.Template.Exceptions;
6+
using Moryx.Cli.Template.StateTemplate;
7+
8+
namespace Moryx.Cli.Commands
9+
{
10+
internal class AddStates
11+
{
12+
const string StateBase = "StateBase";
13+
const string SpecificState = "SpecificState";
14+
15+
internal static CommandResult Exec(TemplateSettings settings, string resource, IEnumerable<string> states, List<string> transitions)
16+
{
17+
return CommandBase.Exec(settings, (fileNames) => Add(settings, fileNames, resource, states));
18+
}
19+
20+
private static CommandResult Add(TemplateSettings settings, List<string> cleanedResourceNames, string resource, IEnumerable<string> states)
21+
{
22+
if (!ResourceExists(settings, resource))
23+
{
24+
return CommandResult.WithError($"Resource `{resource}` not found. Make sure that a `{resource}.cs` exists in the project.");
25+
}
26+
27+
var projectFileNames = cleanedResourceNames.InitialProjects();
28+
var StateBaseFileName = cleanedResourceNames.StateBaseFile();
29+
30+
var dictionary = Template.Template.PrepareFileStructure(settings.AppName, StateBaseFileName, projectFileNames);
31+
var targetPath = Path.Combine(settings.TargetDirectory, "src", $"{settings.AppName}.Resources", resource);
32+
var newStateBaseFileName = Path.Combine(targetPath, $"{resource}StateBase.cs");
33+
34+
if (!File.Exists(newStateBaseFileName))
35+
{
36+
var files = Template.Template.WriteFilesToDisk(
37+
dictionary,
38+
settings,
39+
_ => newStateBaseFileName);
40+
41+
Template.Template.ReplacePlaceHoldersInsideFiles(
42+
files,
43+
new Dictionary<string, string>
44+
{
45+
{ Template.Template.AppPlaceholder, settings.AppName },
46+
{ Template.Template.StateBasePlaceholder, $"{resource}StateBase" },
47+
{ Template.Template.ResourcePlaceholder, resource },
48+
});
49+
50+
if(!states.Any())
51+
{
52+
states =
53+
[
54+
"Idle",
55+
"ReadyToWork",
56+
"Running",
57+
"ProcessAborting",
58+
"SessionComplete",
59+
];
60+
}
61+
}
62+
63+
var msg = new List<string>();
64+
var warnings = new List<string>();
65+
var stateBaseTemplate = StateBaseTemplate.FromFile(newStateBaseFileName);
66+
67+
foreach (var state in states)
68+
{
69+
var stateType = state.ToLower().EndsWith("state")
70+
? state
71+
: state + "State";
72+
73+
stateType = stateType.Capitalize();
74+
75+
try
76+
{
77+
var stateFileName = cleanedResourceNames.StateFile();
78+
79+
dictionary = Template.Template.PrepareFileStructure(settings.AppName, stateFileName, projectFileNames);
80+
var filename = Path.Combine(targetPath, $"{stateType}.cs");
81+
if (!File.Exists(filename))
82+
{
83+
var files = Template.Template.WriteFilesToDisk(
84+
dictionary,
85+
settings,
86+
_ => filename);
87+
88+
Template.Template.ReplacePlaceHoldersInsideFiles(
89+
files,
90+
new Dictionary<string, string>
91+
{
92+
{ Template.Template.AppPlaceholder, settings.AppName },
93+
{ Template.Template.StatePlaceholder, stateType },
94+
{ Template.Template.ResourcePlaceholder, resource },
95+
{ Template.Template.StateBasePlaceholder, $"{resource}StateBase" },
96+
});
97+
98+
stateBaseTemplate = stateBaseTemplate.AddState(stateType);
99+
100+
msg.Add($"Successfully added {stateType} state");
101+
}
102+
else
103+
{
104+
warnings.Add($"Could not add {stateType}. `{Path.GetFileName(filename)}` already exists!");
105+
}
106+
}
107+
catch (Exception ex)
108+
{
109+
warnings.Add($"Failed to add state `{stateType}`! " + ex.Message);
110+
}
111+
}
112+
113+
stateBaseTemplate.SaveToFile(newStateBaseFileName);
114+
115+
UpdateResource(
116+
settings,
117+
resource,
118+
success => msg.Add(success),
119+
warning => warnings.Add(warning));
120+
121+
return CommandResult.IsOk(string.Join("\n", msg), string.Join("\n", warnings));
122+
}
123+
124+
private static void UpdateResource(TemplateSettings settings, string resource, Action<string> onSuccess, Action<string> onWarning)
125+
{
126+
var dir = Path.Combine(settings.TargetDirectory, "src", $"{settings.AppName}.Resources");
127+
var files = Directory.GetFiles(
128+
dir,
129+
$"{resource}.cs",
130+
new EnumerationOptions
131+
{
132+
ReturnSpecialDirectories = false,
133+
RecurseSubdirectories = true
134+
});
135+
if(files.Length == 0)
136+
{
137+
onWarning($"Filename `{resource}.cs` not found. Could not update resource.");
138+
return;
139+
}
140+
if (files.Length > 1)
141+
{
142+
onWarning($"Filename `{resource}.cs` is ambiguous. Could not update resource.");
143+
return;
144+
}
145+
var filename = files.Single();
146+
147+
try
148+
{
149+
var template = StateTemplate.FromFile(filename);
150+
template = template.ImplementIStateContext(resource);
151+
template.SaveToFile(filename);
152+
onSuccess($"Updated `{resource}.cs`");
153+
}
154+
catch (Exception)
155+
{
156+
onWarning($"Failed to update `{resource}.cs`");
157+
}
158+
}
159+
160+
private static bool ResourceExists(TemplateSettings settings, string resource)
161+
{
162+
var files = Directory.GetFiles(
163+
Path.Combine(settings.TargetDirectory),
164+
$"{resource}.cs",
165+
new EnumerationOptions
166+
{
167+
ReturnSpecialDirectories = false,
168+
RecurseSubdirectories = true
169+
});
170+
return files.Length > 0;
171+
}
172+
}
173+
}

src/Moryx.Cli.Commands/AddThing.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,21 @@ public class AddConfig
4646
/// <summary>
4747
/// Name of the solution file (<SolutionName>.sln)
4848
/// </summary>
49-
public string SolutionName { get; set; }
49+
public required string SolutionName { get; set; }
5050

5151
/// <summary>
5252
/// Type of this thing, like `module`. Used for displaying user outputs
5353
/// </summary>
54-
public string Thing { get; set; }
54+
public required string Thing { get; set; }
5555

5656
/// <summary>
5757
/// Actual name identifiere of the *thing* to be added
5858
/// </summary>
59-
public string ThingName { get; set; }
59+
public required string ThingName { get; set; }
6060

6161
/// <summary>
6262
/// *Thing*s placeholder
6363
/// </summary>
64-
public IEnumerable<string> ThingPlaceholders { get; set; }
64+
public required IEnumerable<string> ThingPlaceholders { get; set; }
6565
}
6666
}

src/Moryx.Cli.Commands/CommandResult.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,38 @@ public class CommandResult
44
{
55
private int ReturnCode { get; set; }
66
public string? Success { get; private set; }
7+
public string? Warning { get; private set; }
78
public string? Error { get; private set; }
89

910
public static CommandResult WithError(string error)
10-
=> new CommandResult
11+
=> new()
1112
{
1213
Success = null,
1314
Error = error,
15+
Warning = null,
1416
ReturnCode = 1,
1517
};
16-
17-
public static CommandResult IsOk(string message)
18-
=> new CommandResult
18+
19+
public static CommandResult IsOk(string message, string? warning = null)
20+
=> new()
1921
{
2022
Success = message,
23+
Warning = warning,
2124
Error = null,
2225
ReturnCode = 0,
2326
};
24-
27+
2528
public CommandResult OnError(Action<string> action)
2629
{
2730
if (Error != null)
2831
action(Error);
2932
return this;
3033
}
3134

32-
public CommandResult OnSuccess(Action<string> action)
35+
public CommandResult OnSuccess(Action<string, string?> action)
3336
{
3437
if(Success != null)
35-
action(Success);
38+
action(Success, Warning);
3639
return this;
3740
}
3841

src/Moryx.Cli.Commands/CreateNew.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,7 @@ private static void CreateBareSolution(TemplateSettings settings)
5050
{
5151
var cleanedResourceNames = Template.Template.GetCleanedResourceNames(settings);
5252
var projectFilenames = cleanedResourceNames.InitialProjects();
53-
var filteredResourceNames = cleanedResourceNames
54-
.WithoutStep()
55-
.WithoutProduct()
56-
.WithoutRecipe()
57-
.WithoutSetupTrigger()
58-
.WithoutCellSelector()
59-
.WithoutModule()
60-
;
53+
var filteredResourceNames = FilteredResourceNames(cleanedResourceNames);
6154

6255
var dictionary = Template.Template.PrepareFileStructure(settings.AppName, filteredResourceNames, projectFilenames);
6356

@@ -70,6 +63,20 @@ private static void CreateBareSolution(TemplateSettings settings)
7063
});
7164
}
7265

66+
public static List<string> FilteredResourceNames(List<string> resourceNames)
67+
{
68+
return resourceNames
69+
.WithoutStep()
70+
.WithoutProduct()
71+
.WithoutRecipe()
72+
.WithoutSetupTrigger()
73+
.WithoutCellSelector()
74+
.WithoutModule()
75+
.WithoutResource()
76+
.WithoutState()
77+
;
78+
}
79+
7380
private static void InitializeGitRepo(string solutionName, Action<string> onStatus)
7481
{
7582
var initialDirectory = Environment.CurrentDirectory;

src/Moryx.Cli.Commands/Extensions/CommandResultExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ internal static CommandResult Flatten(this CommandResult[] results) {
77
{
88
return CommandResult.WithError(string.Join("\n", results.Select(r => r.Error)));
99
}
10-
return CommandResult.IsOk(string.Join("\n", results.Select(r => r.Success)));
10+
return CommandResult.IsOk(string.Join("\n", results.Select(r => r.Success)), string.Join("\n", results.Select(r => r.Warning)));
1111
}
1212
}
1313
}

src/Moryx.Cli.Commands/Extensions/ConfigExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ public static TemplateSettings LoadSettings(string dir, string solutionName, str
2020
public static Config.Models.Configuration ToConfiguration(this NewOptions options, string profile = "default")
2121
{
2222
var result = Config.Models.Configuration.DefaultConfiguration();
23-
result.Profiles[profile].Repository = options.Template;
24-
result.Profiles[profile].Branch = options.Branch;
23+
result.Profiles[profile].Repository = options.Template ?? DefaultValues.DefaultTemplate;
24+
result.Profiles[profile].Branch = options.Branch ?? DefaultValues.DefaultBranch;
2525
return result;
2626
}
2727
}

src/Moryx.Cli.Commands/Extensions/StringExtensions.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using Moryx.Cli.Commands.Options;
2-
using Moryx.Cli.Template.Models;
1+
using System.Diagnostics.CodeAnalysis;
32

43
namespace Moryx.Cli.Commands.Extensions
54
{
@@ -13,5 +12,8 @@ public static string Replace(this string s, IEnumerable<string> oldStrings, stri
1312
}
1413
return s;
1514
}
15+
16+
public static string Capitalize([NotNull] this string s)
17+
=> s[0].ToString().ToUpper() + s.Substring(1);
1618
}
1719
}

0 commit comments

Comments
 (0)