using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Script.Serialization;
///
/// Simple JavaScript minimizer (using Google's Closure Compiler) implementation.
///
public static class Program
{
//-------------------------------------------------
///
/// Main entry point for the JavaScript minimizer.
///
public static int Main(string[] args)
{
try
{
if (args.Length < 3)
{
throw new ArgumentException("Expected at least 3 arguments: outputFileName, versionRegex, file1, ...");
}
Minimize(args[0], args[1], args.Skip(2));
return 0;
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Failed");
Console.Write(ex.GetType().FullName);
Console.Write(": ");
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
return 1;
}
finally
{
Console.ResetColor();
}
}
//-------------------------------------------------
///
/// Helper extension method to query dictionary for
/// the specified key and cast the value (if found)
/// to the specified type in a single statement.
///
private static T GetValueOrDefault(this IDictionary dict, string key)
{
object value;
if (dict.TryGetValue(key, out value))
{
if (value == null || value is T)
{
return (T)value;
}
else
{
throw new InvalidCastException(string.Concat("Requested type \"", typeof(T).FullName, "\" is not compatible with actual type \"", value.GetType().FullName, "\""));
}
}
else
{
return default(T);
}
}
//-------------------------------------------------
///
/// Create a user-readable list of messages from a list
/// of JSON error/warning objects.
///
private static void GatherIssues(ArrayList issues, string prefix, ICollection messages)
{
if (issues != null)
{
foreach (var issue in issues)
{
var issueDict = (Dictionary)issue;
messages.Add(string.Concat(
prefix, " ", issueDict.GetValueOrDefault("type"),
" (", issueDict.GetValueOrDefault("lineno"), ", ", issueDict.GetValueOrDefault("charno"), "): ",
issueDict.GetValueOrDefault("error") ?? issueDict.GetValueOrDefault("warning")));
}
}
}
//-------------------------------------------------
///
/// Gather all the specified input files and call Google's
/// Closure Compiler REST API with the combined script.
///
private static void Minimize(string outputFileName, string versionRegex, IEnumerable files)
{
// gather all input files into one ".orig" file
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.Write("Gathering input files: ");
var source = files.Select(f => File.ReadAllText(f)).Aggregate((s1,s2) => string.Concat(s1, Environment.NewLine, s2));
source = source.Replace("\"use strict\";", string.Empty);
var versionMatch = Regex.Match(source, versionRegex, RegexOptions.CultureInvariant | RegexOptions.Multiline);
if (versionMatch.Success && versionMatch.Groups.Count > 1)
{
outputFileName = string.Format(CultureInfo.InvariantCulture, outputFileName, versionMatch.Groups[1].Value);
}
var outputFileNameDir = Path.GetDirectoryName(outputFileName);
if (!string.IsNullOrEmpty(outputFileNameDir) && !Directory.Exists(outputFileNameDir)) // ensure target dir exists
{
Directory.CreateDirectory(outputFileNameDir);
}
var outputFileNameOrig = Path.ChangeExtension(outputFileName, string.Concat(".orig", Path.GetExtension(outputFileName)));
if (File.Exists(outputFileNameOrig)) { File.SetAttributes(outputFileNameOrig, FileAttributes.Normal); } // ensure we can overwrite files marked as read-only
File.WriteAllText(outputFileNameOrig, source);
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("OK");
// run closure compiler
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.Write("Calling Closure Compiler: ");
var webClient = new WebClient();
webClient.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
var request = string.Concat("compilation_level=SIMPLE_OPTIMIZATIONS&output_format=json&output_info=errors&output_info=warnings&output_info=compiled_code&js_code=", HttpUtility.UrlEncode(source));
var response = new JavaScriptSerializer().Deserialize>(webClient.UploadString("http://closure-compiler.appspot.com/compile", request));
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("OK");
// check for compiler errors
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.Write("Processing Compiler Response: ");
var messages = new List();
GatherIssues(response.GetValueOrDefault("errors"), "Error", messages);
if (messages.Count > 0)
{
throw new InvalidOperationException(string.Concat("Compiler reported errors", Environment.NewLine, string.Join(Environment.NewLine, messages.ToArray())));
}
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("OK");
// check for compiler warnings
GatherIssues(response.GetValueOrDefault("warnings"), "Warning", messages);
if (messages.Count > 0)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(string.Concat(" (", messages.Count, " warning", messages.Count == 1 ? ")" : "s)"));
Console.WriteLine(string.Join(Environment.NewLine, messages.ToArray()));
}
else
{
Console.WriteLine(" (no errors or warnings)");
}
// write output file
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.Write("Writing Minimized File: ");
if (File.Exists(outputFileName)) { File.SetAttributes(outputFileName, FileAttributes.Normal); } // ensure we can overwrite files marked as read-only
File.WriteAllText(outputFileName, response.GetValueOrDefault("compiledCode"));
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("OK");
}
}