// Nova.CLI - a command-line interface for Nova.CodeDOM.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Nova.CodeDOM;
using Nova.Utilities;
namespace Nova.CLI
{
/// <summary>
/// This program is a command-line interface for the Nova.CodeDOM C# object model library.
/// </summary>
class Program
{
static void Main(string[] arguments)
{
int exitCode = 0;
try
{
// Determine the application name and if we're running in VS
string appName = AppDomain.CurrentDomain.FriendlyName;
if (appName.EndsWith(".exe"))
appName = appName.Substring(0, appName.Length - 4);
if (appName.EndsWith(".vshost"))
appName = appName.Substring(0, appName.Length - 7);
bool waitBeforeExiting = Debugger.IsAttached;
bool spawned = StringUtil.Contains(arguments, "/spawned");
if (!spawned)
{
Version appVersion = typeof(Program).Assembly.GetName().Version;
string appDescription = appName + " " + appVersion.Major + "." + appVersion.Minor;
Console.WriteLine(appDescription + " - C# CodeDOM command-line utility.\nCopyright (C) 2011-2012 Inevitable Software, all rights reserved.");
}
// Process the command-line arguments
if (arguments == null || arguments.Length == 0 || (arguments.Length == 1 && arguments[0] == "/?"))
{
Console.WriteLine("\nUsage: " + appName + " <file> [<file> ...] [<option> ...]\n"
+ " ('<file>' can be a solution, project, or source file)\n"
+ " A file can be optionally suffixed with a configuration\n"
+ " and platform, such as: <file>|Release\n"
+ " Or: <file>|Debug|\"Any CPU\"\n"
+ "\n"
+ "\tGeneral options:\n"
+ "\t/donotresolve - Load and parse sources, but don't resolve\n"
+ "\t/loadonly - Load, but don't parse or resolve sources\n"
+ "\t/save - Save all files after loading is complete\n"
+ "\t/detail - Detailed log messages\n"
+ "\t/minimal - Minimal log messages, and no code messages\n"
+ "\t/spawn - Load each specified file using a child process\n"
+ "\t/wait - Wait for key press when done before exiting\n"
+ "\t/waitfail - Wait for key press when done if any check option failed\n"
+ "\t/config:\"C|P\" - Apply the configuration (Debug, Release, etc) and\n"
+ "\t optional platform (Any CPU, Mixed Platforms, x86, etc)\n"
+ "\t to ALL files. Such as: /config:Release\n"
+ "\t Or: /config:\"Debug|Any CPU\"\n"
+ "\t/donotparsebodies - Don't parse method bodies\n"
+ "\t/donotresolvebodies - Don't resolve method bodies\n"
+ "\n"
+ "\tOptions for the preceeding file only:\n"
+ "\t/check:E,W,C - Check message counts (E=errors, W=warnings, C=comments)\n"
);
}
else
exitCode = ProcessArguments(arguments, appName, ref waitBeforeExiting);
// Wait for a key press before exiting if requested
if (waitBeforeExiting)
{
Console.WriteLine("\nPress any key to exit.");
Console.ReadKey();
}
}
catch (Exception ex)
{
Console.WriteLine("EXCEPTION: " + ex);
}
Environment.Exit(exitCode);
}
private static int ProcessArguments(string[] arguments, string appName, ref bool waitBeforeExiting)
{
bool spawn = false;
bool spawned = false;
string spawnArguments = null;
bool save = false;
bool waitIfChecksFail = false;
LoadOptions loadOptions = LoadOptions.Complete;
string configuration = null;
string platform = null;
// Process any options first
foreach (string argument in arguments)
{
// Look only at options here
if (argument.StartsWith("/config:"))
GetConfigurationAndPlatform(argument.Substring(8), out configuration, out platform);
else if (argument.StartsWith("/") && !argument.StartsWith("/check:"))
{
switch (argument.Substring(1).ToLower())
{
case "donotresolve": loadOptions &= ~LoadOptions.ResolveSources; break;
case "loadonly": loadOptions &= ~(LoadOptions.ParseSources | LoadOptions.ResolveSources); break;
case "donotparsebodies": loadOptions |= LoadOptions.DoNotParseBodies; break;
case "donotresolvebodies": loadOptions |= LoadOptions.DoNotResolveBodies; break;
case "save": save = true; break;
case "detail": Log.LogLevel = Log.Level.Detailed; break;
case "minimal": Log.LogLevel = Log.Level.Minimal; break;
case "spawn":
spawn = true;
spawnArguments = Enumerable.Aggregate(Enumerable.Where(arguments,
delegate(string arg) { return arg.StartsWith("/") && arg != "/spawn" && arg != "/wait" && !arg.StartsWith("/check:"); }), spawnArguments,
delegate(string current, string arg) { return current + (arg + " "); }) + "/spawned";
Log.WriteLine("Spawning each load in a separate child process...");
break;
case "wait": waitBeforeExiting = true; break;
case "waitfail": waitIfChecksFail = true; break;
case "spawned": spawned = true; Configuration.LogSettings = false; break;
default:
Log.WriteLine("Option '" + argument + "' not recognized (use no arguments to get a list of valid ones)."); break;
}
}
}
// Process any specified solution, project, or source files
int fileCount = 0, checkCount = 0, failCount = 0;
int errorCount = 0, warningCount = 0, commentCount = 0;
for (int index = 0; index < arguments.Length; ++index)
{
string argument = arguments[index];
if (!argument.StartsWith("/")) // Ignore options at this point
{
if (spawn)
{
// If so requested, run each file load in a separate child process
Log.WriteLine("");
string spawnArgs = "\"" + argument + "\" ";
bool checkMessageCounts = false;
if (arguments[index + 1].StartsWith("/check:"))
{
++index;
spawnArgs += arguments[index] + " ";
checkMessageCounts = true;
}
spawnArgs += spawnArguments;
Process process = new Process { StartInfo = { FileName = appName + ".exe", Arguments = spawnArgs, UseShellExecute = false } };
process.Start();
process.WaitForExit();
if (checkMessageCounts)
{
++checkCount;
if (process.ExitCode != 0)
++failCount;
}
}
else
{
if (!spawned)
Log.WriteLine("");
if (Log.LogLevel == Log.Level.Detailed)
{
GC.Collect();
Log.DetailWriteLine("Before Heap Size = " + (int)(GC.GetTotalMemory(true) / (1024 * 1024)) + " MBs");
}
// Strip any trailing config
string[] parts = argument.Split('|');
string fileConfiguration = configuration;
string filePlatform = platform;
if (parts.Length > 1)
{
fileConfiguration = parts[1];
filePlatform = (parts.Length > 2 ? parts[2] : null);
}
// Get the file extension, defaulting to ".sln" if none
string fileName = parts[0];
string fileExtension = Path.GetExtension(fileName);
if (string.IsNullOrEmpty(fileExtension))
{
fileExtension = Solution.SolutionFileExtension;
fileName += fileExtension;
}
// Load the specified solution, project, or file
loadOptions |= LoadOptions.LogMessages;
if (fileExtension == Solution.SolutionFileExtension)
{
Solution solution = Solution.Load(fileName, fileConfiguration, filePlatform, loadOptions);
if (solution != null)
{
solution.GetMessageCounts(out errorCount, out warningCount, out commentCount);
if (save)
solution.SaveAll();
solution.Unload();
}
}
else if (fileExtension == Project.CSharpProjectFileExtension)
{
Project project = Project.Load(fileName, fileConfiguration, filePlatform, loadOptions);
if (project != null)
{
project.Solution.GetMessageCounts(out errorCount, out warningCount, out commentCount);
if (save)
project.SaveAll();
project.Unload();
}
}
else if (fileExtension == Project.CSharpFileExtension)
{
CodeUnit codeUnit = CodeUnit.Load(fileName, loadOptions);
if (codeUnit != null)
{
codeUnit.GetMessageCounts(out errorCount, out warningCount, out commentCount);
if (save)
codeUnit.Save();
}
}
if (Log.LogLevel == Log.Level.Detailed)
{
GC.Collect();
Log.DetailWriteLine("After Heap Size = " + (int)(GC.GetTotalMemory(true) / (1024 * 1024)) + " MBs");
}
}
++fileCount;
}
else if (argument.StartsWith("/check:"))
{
++checkCount;
bool pass = false;
int[] counts = StringUtil.ParseArray<int>(argument.Substring(7), null);
if (counts != null && counts.Length > 0)
{
pass = ((counts[0] == errorCount) && (counts.Length < 2 || counts[1] == warningCount)
&& (counts.Length < 3 || counts[2] == commentCount));
}
if (!pass)
++failCount;
Log.WriteLine("Check message counts: " + (pass ? "PASS" : "FAIL"));
}
}
Log.WriteLine("");
if (fileCount > 1)
{
GC.Collect();
Log.WriteLine("DONE! " + fileCount + " files loaded. Heap size = " + (int)(GC.GetTotalMemory(true) / (1024 * 1024)) + " MBs");
if (checkCount > 0)
{
if (!spawned)
Log.WriteLine("Checked " + checkCount + ", " + (failCount == 0 ? "all PASSED" : "FAILED " + failCount));
if (waitIfChecksFail && failCount > 0)
waitBeforeExiting = true;
}
}
return -failCount;
}
private static void GetConfigurationAndPlatform(string input, out string configuration, out string platform)
{
string[] parts = input.Split('|');
configuration = parts[0];
platform = (parts.Length > 1 ? parts[1] : null);
}
}
}