Skip to content

Commit

Permalink
New command line syntax.
Browse files Browse the repository at this point in the history
Using a third party library to parse command line arguments.
Breaking change: output, previously the last argument, must now be prefixed with --output
  • Loading branch information
Peter Hultqvist committed Aug 7, 2013
1 parent c45e022 commit 9d68874
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 102 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "commandline"]
path = commandline
url = https://github.com/gsscoder/commandline.git
6 changes: 6 additions & 0 deletions CodeGenerator.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeGenerator", "CodeGenera
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeWriter", "CodeWriter\CodeWriter.csproj", "{FFFA67D7-6E0B-4C5C-8B3C-9DDED536AC4A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandLine", "commandline\src\CommandLine\CommandLine.csproj", "{E1BD3C65-49C3-49E7-BABA-C60980CB3F20}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -15,6 +17,10 @@ Global
{108DF716-5E86-4515-836F-1AC211CA648B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{108DF716-5E86-4515-836F-1AC211CA648B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{108DF716-5E86-4515-836F-1AC211CA648B}.Release|Any CPU.Build.0 = Release|Any CPU
{E1BD3C65-49C3-49E7-BABA-C60980CB3F20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E1BD3C65-49C3-49E7-BABA-C60980CB3F20}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1BD3C65-49C3-49E7-BABA-C60980CB3F20}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1BD3C65-49C3-49E7-BABA-C60980CB3F20}.Release|Any CPU.Build.0 = Release|Any CPU
{FFFA67D7-6E0B-4C5C-8B3C-9DDED536AC4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FFFA67D7-6E0B-4C5C-8B3C-9DDED536AC4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFFA67D7-6E0B-4C5C-8B3C-9DDED536AC4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down
9 changes: 7 additions & 2 deletions CodeGenerator/CodeGenerator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>False</ConsolePause>
<Commandlineparameters>--fix-nameclash ../../../Test/ProtoSpec/AddressBook.proto ../../../Test/ProtoSpec/descriptor.proto ../../../Test/ProtoSpec/LocalFeatures.proto ../../../Test/ProtoSpec/ProtoFeatures.proto ../../../Test/Generated/Generated.cs</Commandlineparameters>
<Commandlineparameters>--fix-nameclash ../../../Test/ProtoSpec/AddressBook.proto ../../../Test/ProtoSpec/descriptor.proto ../../../Test/ProtoSpec/LocalFeatures.proto ../../../Test/ProtoSpec/ProtoFeatures.proto --output ../../../Test/Generated/Generated.cs</Commandlineparameters>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>full</DebugType>
Expand All @@ -28,7 +28,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>False</ConsolePause>
<Commandlineparameters>--fix-nameclash ../../../Test/ProtoSpec/AddressBook.proto ../../../Test/ProtoSpec/LocalFeatures.proto ../../../Test/ProtoSpec/ProtoFeatures.proto ../../../Test/Generated/Generated.cs</Commandlineparameters>
<Commandlineparameters>--fix-nameclash ../../../Test/ProtoSpec/AddressBook.proto ../../../Test/ProtoSpec/LocalFeatures.proto ../../../Test/ProtoSpec/ProtoFeatures.proto --output ../../../Test/Generated/Generated.cs</Commandlineparameters>
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<ItemGroup>
Expand Down Expand Up @@ -57,6 +57,7 @@
<Compile Include="LocalParser.cs" />
<Compile Include="Proto\IComment.cs" />
<Compile Include="Proto\Search.cs" />
<Compile Include="Options.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
Expand Down Expand Up @@ -95,5 +96,9 @@
<Project>{FFFA67D7-6E0B-4C5C-8B3C-9DDED536AC4A}</Project>
<Name>CodeWriter</Name>
</ProjectReference>
<ProjectReference Include="..\commandline\src\CommandLine\CommandLine.csproj">
<Project>{E1BD3C65-49C3-49E7-BABA-C60980CB3F20}</Project>
<Name>CommandLine</Name>
</ProjectReference>
</ItemGroup>
</Project>
7 changes: 5 additions & 2 deletions CodeGenerator/CodeGenerator/ProtoCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ static class ProtoCode
/// <summary>
/// Generate code for reading and writing protocol buffer messages
/// </summary>
public static void Save(ProtoCollection file, string csPath)
public static void Save(ProtoCollection file, Options options)
{
string csPath = options.OutputPath;
CodeWriter.DefaultIndentPrefix = options.UseTabs ? "\t" : " ";

string ext = Path.GetExtension(csPath);
string prefix = csPath.Substring(0, csPath.Length - ext.Length);

Expand Down Expand Up @@ -130,7 +133,7 @@ private static void ReadCode(TextWriter code, string name, bool includeUsing)
if (includeUsing == false && line.StartsWith("using"))
continue;

if (CodeWriter.IndentPrefix == "\t")
if (CodeWriter.DefaultIndentPrefix == "\t")
line = line.Replace(" ", "\t");
code.WriteLine(line);
}
Expand Down
78 changes: 9 additions & 69 deletions CodeGenerator/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,81 +8,20 @@ class MainClass
{
public static int Main(string[] args)
{
if (args.Length == 0)
{
Console.Error.WriteLine("Usage:\n\tCodeGenerator.exe [--preserve-names] [--use-tabs] [--fix-nameclash] path-to.proto [path-to-second.proto [...]] [output.cs]");
var options = Options.Parse(args);
if(options == null)
return -1;
}

ProtoCollection collection = new ProtoCollection();
string outputPath = null;

int argIndex = 0;

while (args.Length > argIndex && args[argIndex].StartsWith("--"))
foreach(string protoPath in options.InputProto)
{
switch (args[argIndex])
{
case "--preserve-names":
ProtoPrepare.ConvertToCamelCase = false;
break;
case ProtoPrepare.FixNameclashArgument:
ProtoPrepare.FixNameclash = true;
break;
case "--use-tabs":
CodeWriter.IndentPrefix = "\t";
break;
default:
Console.Error.WriteLine("Unknown option: " + args[argIndex]);
return -1;
}
argIndex++;
}

while (argIndex < args.Length)
{
string protoPath = Path.GetFullPath(args[argIndex]);
string protoBase = Path.Combine(
Path.GetDirectoryName(protoPath),
Path.GetFileNameWithoutExtension(protoPath));
argIndex += 1;

//First .proto filename is used as output unless specified later
if (outputPath == null)
outputPath = protoBase + ".cs";

//Handle last argument as the output .cs path
if (argIndex == args.Length && protoPath.EndsWith(".cs"))
{
outputPath = protoPath;
break;
}

//Handle last argument as the output path
//Filename is taken from first .proto argument
if (argIndex == args.Length && protoPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
Directory.CreateDirectory(protoPath);

// Replace the original output directory with the custom one
outputPath = Path.Combine(protoPath, Path.GetFileName(outputPath));
break;
}

if (File.Exists(protoPath) == false)
{
Console.Error.WriteLine("File not found: " + protoPath);
return -1;
}

try
{
var proto = new ProtoCollection();

//Parse .proto
Console.WriteLine("Parsing " + protoPath);
ProtoParser.Parse(protoPath, proto);

var proto = new ProtoCollection();
ProtoParser.Parse(protoPath, proto);
collection.Merge(proto);
}
catch (ProtoFormatException pfe)
Expand All @@ -98,7 +37,8 @@ public static int Main(string[] args)
//Interpret and reformat
try
{
ProtoPrepare.Prepare(collection);
var pp = new ProtoPrepare(options);
pp.Prepare(collection);
}
catch (ProtoFormatException pfe)
{
Expand All @@ -108,8 +48,8 @@ public static int Main(string[] args)
}

//Generate code
ProtoCode.Save(collection, outputPath);
Console.WriteLine("Saved: " + outputPath);
ProtoCode.Save(collection, options);
Console.WriteLine("Saved: " + options.OutputPath);
return 0;
}
}
Expand Down
90 changes: 90 additions & 0 deletions CodeGenerator/Options.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using CommandLine;
using System.Linq;
using System.Collections.Generic;
using System.IO;

namespace SilentOrbit.ProtocolBuffers
{
/// <summary>
/// Options set using Command Line arguments
/// </summary>
public class Options
{
/// <summary>
/// Convert message/class and field/propery names to CamelCase
/// </summary>
[Option("preserve-names", HelpText = "Keep names as written in .proto, otherwise class and field names by default are converted to CamelCase")]
public bool PreserveNames { get; set; }

/// <summary>
/// If false, an error will occur.
/// </summary>
[Option("fix-nameclash", HelpText = "If a property name is the same as its class name or any subclass the property will be renamed. if the name clash occurs and this flag is not set, an error will occur and the code generation is aborted.")]
public bool FixNameclash { get; set; }

/// <summary>
/// Generated code indent using tabs
/// </summary>
[Option('t', "use-tabs", HelpText = "If set generated code will use tabs rather than 4 spaces.")]
public bool UseTabs { get; set; }

[Value(0, Required = true)]
public IEnumerable<string> InputProto { get; set; }

/// <summary>
/// Path to the generated cs files
/// </summary>
[Option('o', "output", Required = false, HelpText = "Path to the generated .cs file.")]
public string OutputPath { get; set; }

public static Options Parse(string[] args)
{
var result = Parser.Default.ParseArguments<Options>(args);
var options = result.Value;
if (result.Errors.Any())
return null;

bool error = false;

//Do any extra option checking/cleanup here
var inputs = new List<string>(options.InputProto);
options.InputProto = inputs;
for (int n = 0; n < inputs.Count; n++)
{
inputs[n] = Path.GetFullPath(inputs[n]);
if (File.Exists(inputs[n]) == false)
{
Console.Error.WriteLine("File not found: " + inputs[n]);
error = true;
}
}

//Backwards compatibility
string firstPathCs = inputs[0];
firstPathCs = Path.Combine(
Path.GetDirectoryName(firstPathCs),
Path.GetFileNameWithoutExtension(firstPathCs)) + ".cs";

if (options.OutputPath == null)
{
//Use first .proto as base for output
options.OutputPath = firstPathCs;
Console.Error.WriteLine("Warning: Please use the new syntax: --output \"" + options.OutputPath + "\"");
}
//If output is a directory then the first input filename will be used.
if (options.OutputPath.EndsWith(Path.DirectorySeparatorChar.ToString()) || Directory.Exists(options.OutputPath))
{
Directory.CreateDirectory(options.OutputPath);
options.OutputPath = Path.Combine(options.OutputPath, Path.GetFileName(firstPathCs));
}
options.OutputPath = Path.GetFullPath(options.OutputPath);

if(error)
return null;
else
return options;
}
}
}

38 changes: 21 additions & 17 deletions CodeGenerator/ProtoPrepare.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,24 @@

namespace SilentOrbit.ProtocolBuffers
{
static class ProtoPrepare
class ProtoPrepare
{
readonly Options options;

/// <summary>
/// Convert message/class and field/propery names to CamelCase
/// </summary>
public static bool ConvertToCamelCase = true;
/// <summary>
/// If the name clashes between a property and subclass, the property will be renamed.
/// If false, an error will occur.
/// </summary>
public static bool FixNameclash = false;
public const string FixNameclashArgument = "--fix-nameclash";
bool ConvertToCamelCase = true;

public ProtoPrepare(Options options)
{
if (options.PreserveNames)
ConvertToCamelCase = false;

this.options = options;
}

static public void Prepare(ProtoCollection file)
public void Prepare(ProtoCollection file)
{
foreach (ProtoMessage m in file.Messages.Values)
{
Expand All @@ -34,10 +38,10 @@ static public void Prepare(ProtoCollection file)
}
}

static void PrepareMessage(ProtoMessage m)
void PrepareMessage(ProtoMessage m)
{
//Name of message and enums
m.CsType = ProtoPrepare.GetCamelCase(m.ProtoName);
m.CsType = GetCamelCase(m.ProtoName);
foreach (ProtoEnum e in m.Enums.Values)
{
e.CsType = GetCamelCase(e.ProtoName);
Expand Down Expand Up @@ -69,7 +73,7 @@ static void PrepareMessage(ProtoMessage m)
/// </summary>
/// <param name="m">Parent message</param>
/// <param name="f">Field to check</param>
static void DetectNameClash(ProtoMessage m, Field f)
void DetectNameClash(ProtoMessage m, Field f)
{
bool nameclash = false;
if (m.CsType == f.CsName)
Expand All @@ -91,7 +95,7 @@ static void DetectNameClash(ProtoMessage m, Field f)
return;

//Name clash
if (FixNameclash)
if (options.FixNameclash)
{
if (ConvertToCamelCase)
f.CsName += "Field";
Expand All @@ -107,13 +111,13 @@ static void DetectNameClash(ProtoMessage m, Field f)
else
throw new ProtoFormatException("The field: " + m.FullCsType + "." + f.CsName +
" has the same name as a sibling class/enum type which is not allowed in C#. " +
"Use " + FixNameclashArgument + " to automatically rename the field.", f.Source);
"Use --fix-nameclash to automatically rename the field.", f.Source);
}

/// <summary>
/// Prepare: ProtoType, WireType and CSType
/// </summary>
static void PrepareProtoType(ProtoMessage m, Field f)
void PrepareProtoType(ProtoMessage m, Field f)
{
//Change property name to C# style, CamelCase.
f.CsName = GetCSPropertyName(m, f.ProtoName);
Expand Down Expand Up @@ -184,7 +188,7 @@ static ProtoBuiltin GetBuiltinProtoType(string type)
/// Gets the C# CamelCase version of a given name.
/// Name collisions with enums are avoided.
/// </summary>
static string GetCSPropertyName(ProtoMessage m, string name)
string GetCSPropertyName(ProtoMessage m, string name)
{
string csname = GetCamelCase(name);

Expand All @@ -198,7 +202,7 @@ static string GetCSPropertyName(ProtoMessage m, string name)
/// <summary>
/// Gets the CamelCase version of a given name.
/// </summary>
static string GetCamelCase(string name)
string GetCamelCase(string name)
{
if (ConvertToCamelCase == false)
return name;
Expand Down
Loading

0 comments on commit 9d68874

Please sign in to comment.