Skip to content

Commit 711e052

Browse files
committed
refactored command line parsing. fixed a lot of issues with a complex scenarios
1 parent 0e60d38 commit 711e052

File tree

9 files changed

+288
-145
lines changed

9 files changed

+288
-145
lines changed

Blazer.Exe/Blazer.Exe.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@
5353
</ItemGroup>
5454
<ItemGroup>
5555
<Compile Include="CommandLine\BlazerCommandLineOptions.cs" />
56+
<Compile Include="CommandLine\CommandLineDescriptionAttribute.cs" />
5657
<Compile Include="CommandLine\CommandLineParser.cs" />
5758
<Compile Include="CommandLine\CommandLineOptionAttribute.cs" />
59+
<Compile Include="FileNameHelper.cs" />
5860
<Compile Include="NullStream.cs" />
5961
<Compile Include="Program.cs" />
6062
<Compile Include="Properties\AssemblyInfo.cs" />

Blazer.Exe/CommandLine/BlazerCommandLineOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace Force.Blazer.Exe.CommandLine
22
{
3+
[CommandLineDescription("Usage: Blazer.exe [options] [archiveName.blz] sourceFile|@fileList")]
34
public class BlazerCommandLineOptions
45
{
56
[CommandLineOption('h', "help", "Display this help")]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
3+
namespace Force.Blazer.Exe.CommandLine
4+
{
5+
[AttributeUsage(AttributeTargets.Class)]
6+
public class CommandLineDescriptionAttribute : Attribute
7+
{
8+
public string Description { get; set; }
9+
10+
public CommandLineDescriptionAttribute(string description)
11+
{
12+
Description = description;
13+
}
14+
}
15+
}

Blazer.Exe/CommandLine/CommandLineParser.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ namespace Force.Blazer.Exe.CommandLine
1414

1515
private List<string> _nonKeyOptions;
1616

17+
private bool _hasAnyOptions;
18+
1719
public CommandLineParser(string[] args)
1820
{
1921
ParseArgumentsInternal(args);
@@ -23,6 +25,8 @@ private void ParseArgumentsInternal(string[] args)
2325
{
2426
var t = new T();
2527

28+
_hasAnyOptions = args.Length > 0;
29+
2630
var knownOptions =
2731
typeof(T).GetProperties()
2832
.Select(x => new Tuple<PropertyInfo, CommandLineOptionAttribute>(x, (CommandLineOptionAttribute)x.GetCustomAttributes(typeof(CommandLineOptionAttribute), true).FirstOrDefault()))
@@ -113,6 +117,8 @@ public string GetNonParamOptions(int idx)
113117

114118
public string GenerateHelp()
115119
{
120+
var headerAttribute = (CommandLineDescriptionAttribute)typeof(T).GetCustomAttributes(typeof(CommandLineDescriptionAttribute), true).FirstOrDefault();
121+
116122
var options = typeof(T).GetProperties()
117123
.Select(x => (CommandLineOptionAttribute)x.GetCustomAttributes(typeof(CommandLineOptionAttribute), true).FirstOrDefault())
118124
.Where(x => x != null)
@@ -130,6 +136,9 @@ public string GenerateHelp()
130136
}
131137

132138
var b = new StringBuilder();
139+
140+
if (headerAttribute != null) b.Append(headerAttribute.Description).AppendLine();
141+
133142
foreach (var option in options)
134143
{
135144
b.Append("\t");
@@ -165,5 +174,10 @@ public string GenerateHeader(string additionalVersion)
165174

166175
return string.Format("{0} {2} ({3}) {1}", title.Title, copy.Copyright, version.Version, additionalVersion);
167176
}
177+
178+
public bool HasAnyOptions()
179+
{
180+
return _hasAnyOptions;
181+
}
168182
}
169183
}

Blazer.Exe/FileNameHelper.cs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
6+
using Force.Blazer.Exe.CommandLine;
7+
8+
namespace Force.Blazer.Exe
9+
{
10+
public class FileNameHelper
11+
{
12+
public class FileOptions
13+
{
14+
public string ArchiveName { get; set; }
15+
16+
public string[] SourceFiles { get; set; }
17+
}
18+
19+
public static FileOptions ParseCompressOptions(CommandLineParser<BlazerCommandLineOptions> options)
20+
{
21+
var opt = options.Get();
22+
var nonParamOptions = options.GetNonParamOptions();
23+
if (nonParamOptions.Length == 0 && !opt.Stdout)
24+
{
25+
Console.WriteLine(options.GenerateHelp());
26+
return null;
27+
}
28+
29+
var listFile = options.GetNonParamOptions().FirstOrDefault(x => x[0] == '@');
30+
if (listFile != null && nonParamOptions.Length != 2 - (opt.Stdout ? 1 : 0))
31+
{
32+
Console.Error.WriteLine("When list file is provided, only archive name is allowed");
33+
return null;
34+
}
35+
36+
if (listFile != null && opt.Stdin)
37+
{
38+
Console.Error.WriteLine("Stdin is not compatible with list file");
39+
return null;
40+
}
41+
42+
if (opt.Stdin && nonParamOptions.Length > 1)
43+
{
44+
Console.Error.WriteLine("Stdin is not compatible with multiple files");
45+
return null;
46+
}
47+
48+
var archiveName = opt.Stdout ? null : nonParamOptions[0];
49+
string[] filesToCompress;
50+
51+
if (listFile != null)
52+
{
53+
listFile = listFile.Remove(0, 1);
54+
if (!File.Exists(listFile))
55+
{
56+
Console.Error.WriteLine("Invalid list file");
57+
return null;
58+
}
59+
60+
filesToCompress =
61+
File.ReadAllLines(listFile).Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()).ToArray();
62+
}
63+
else
64+
{
65+
filesToCompress = opt.Stdin ? new string[0] : nonParamOptions.Skip(1).ToArray();
66+
67+
if (filesToCompress.Length == 0 && archiveName != null && !opt.Stdin) filesToCompress = new[] { archiveName };
68+
}
69+
70+
bool hasMissingFiles;
71+
filesToCompress = ExpandFilesInList(filesToCompress, out hasMissingFiles);
72+
73+
if (hasMissingFiles)
74+
{
75+
Console.Error.WriteLine("One or more of files to compress does not exist");
76+
return null;
77+
}
78+
79+
if (archiveName != null && !archiveName.EndsWith(".blz")) archiveName += ".blz";
80+
81+
return new FileOptions
82+
{
83+
ArchiveName = archiveName,
84+
SourceFiles = filesToCompress
85+
};
86+
}
87+
88+
public static FileOptions ParseDecompressOptions(CommandLineParser<BlazerCommandLineOptions> options)
89+
{
90+
var opt = options.Get();
91+
92+
var nonParamOptions = options.GetNonParamOptions();
93+
94+
if (!opt.Stdin && nonParamOptions.Length == 0)
95+
{
96+
Console.Error.WriteLine("Archive name was not specified");
97+
return null;
98+
}
99+
100+
var archiveName = opt.Stdin ? null : nonParamOptions[0];
101+
102+
if (archiveName != null && !File.Exists(archiveName))
103+
{
104+
Console.Error.WriteLine("Archive file " + archiveName + " does not exist");
105+
return null;
106+
}
107+
108+
var listFile = options.GetNonParamOptions().FirstOrDefault(x => x[0] == '@');
109+
if (listFile != null && nonParamOptions.Length != 2 - (opt.Stdin ? 1 : 0))
110+
{
111+
Console.Error.WriteLine("When list file is provided, only archive name is allowed");
112+
return null;
113+
}
114+
115+
string[] customOutFileNames = null;
116+
117+
if (listFile != null)
118+
{
119+
listFile = listFile.Remove(0, 1);
120+
if (!File.Exists(listFile))
121+
{
122+
Console.Error.WriteLine("Invalid list file");
123+
return null;
124+
}
125+
126+
customOutFileNames = File.ReadAllLines(listFile).Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()).ToArray();
127+
}
128+
else
129+
{
130+
customOutFileNames = nonParamOptions.Skip(opt.Stdin ? 0 : 1).ToArray();
131+
}
132+
133+
return new FileOptions
134+
{
135+
ArchiveName = archiveName,
136+
SourceFiles = customOutFileNames
137+
};
138+
}
139+
140+
private static string[] ExpandFilesInList(string[] initialFiles, out bool hasMissingFiles)
141+
{
142+
hasMissingFiles = false;
143+
var l = new List<string>();
144+
// todo: better search + unit tests
145+
foreach (var s in initialFiles)
146+
{
147+
if (File.Exists(s))
148+
l.Add(s);
149+
else if (Directory.Exists(s)) l.Add(s);
150+
else
151+
{
152+
var asteriskIdx = s.IndexOf("*", StringComparison.InvariantCulture);
153+
if (asteriskIdx < 0) hasMissingFiles = true;
154+
else
155+
{
156+
var slashIdx = s.LastIndexOfAny(
157+
new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, asteriskIdx - 1, asteriskIdx - 1);
158+
var dirToSearch = string.Empty;
159+
if (slashIdx >= 0) dirToSearch = s.Substring(0, slashIdx);
160+
l.AddRange(Directory.GetFiles(dirToSearch, s.Remove(0, slashIdx + 1), SearchOption.AllDirectories));
161+
}
162+
}
163+
}
164+
165+
return l.ToArray();
166+
}
167+
}
168+
}

0 commit comments

Comments
 (0)