protected override void GenerateStyleSheet(OutputGroup outputGroup, IList <InputGroup> inputGroups, UglifyCommandParser uglifyCommandParser, string outputPath, Encoding outputEncoding) { if (!FileWriteOperation(outputPath, uglifyCommandParser.IfNotNull(p => p.Clobber), () => { // create the output file, clobbering any existing content using (var writer = new StreamWriter(outputPath, false, outputEncoding)) { if (inputGroups != null && inputGroups.Count > 0) { // for each input file, copy to the output, separating them with a newline (don't need a semicolon like JavaScript does) var addSeparator = false; foreach (var inputGroup in inputGroups) { if (addSeparator) { writer.WriteLine(); } else { addSeparator = true; } writer.Write(inputGroup.Source); } } } return(true); })) { // could not write file Log.LogError(Strings.CouldNotWriteOutputFile, outputPath); } }
protected override void GenerateJavaScript(OutputGroup outputGroup, IList <InputGroup> inputGroups, UglifyCommandParser uglifyCommandParser, string outputPath, Encoding outputEncoding) { if (uglifyCommandParser == null) { throw new ArgumentNullException("uglifyCommandParser"); } try { var settings = uglifyCommandParser.JSSettings; // process the resources for this output group into the settings list // if there are any to be processed if (outputGroup != null && settings != null && outputGroup.Resources.IfNotNull(rs => rs.Count > 0)) { outputGroup.ProcessResourceStrings(settings.ResourceStrings, null); } // then process the javascript output group ProcessJavaScript( inputGroups, uglifyCommandParser, outputPath, outputGroup.IfNotNull(og => og.SymbolMap), outputEncoding); } catch (ArgumentException ex) { // processing the resource strings could throw this exception Log.LogError(ex.Message); } }
/// <summary> /// Process an output group by deleting the output files if they exist. /// </summary> /// <param name="outputGroup">the OutputGroup being processed</param> /// <param name="outputFileInfo">FileInfo for the desired output file</param> /// <param name="symbolsFileInfo">FileInfo for the optional desired symbol file</param> /// <param name="defaultSettings">default settings for this output group</param> /// <param name="manifestModifiedTime">modified time for the manifest</param> protected override void ProcessOutputGroup(OutputGroup outputGroup, FileInfo outputFileInfo, FileInfo symbolsFileInfo, UglifyCommandParser defaultSettings, DateTime manifestModifiedTime) { // get the settings to use -- take the configuration for this output group // and apply them over the default settings var settings = ParseConfigSettings(outputGroup.GetConfigArguments(this.Configuration), defaultSettings); // we really only care about the clobber setting -- if the file is read-only, don't bother deleting it // unless we have the clobber setting. If the current setting is Preserve, then we want to change it to // Auto because it makes no sense to not delete a non-readonly file during a "clean" var clobber = settings.Clobber == ExistingFileTreatment.Preserve ? ExistingFileTreatment.Auto : settings.Clobber; // we don't care about the inputs, we just want to delete the outputs and be done if (outputFileInfo != null) { if (!FileWriteOperation(outputFileInfo.FullName, clobber, () => { outputFileInfo.IfNotNull(fi => { if (fi.Exists) { Log.LogMessage(MessageImportance.Normal, Strings.DeletingFile, fi.FullName); fi.Delete(); } }); return(true); })) { // can't delete the file - not an error; just informational Log.LogMessage(MessageImportance.Normal, Strings.CouldNotDeleteOutputFile, outputFileInfo.FullName); } } if (symbolsFileInfo != null) { if (!FileWriteOperation(symbolsFileInfo.FullName, clobber, () => { symbolsFileInfo.IfNotNull(fi => { if (fi.Exists) { Log.LogMessage(MessageImportance.Normal, Strings.DeletingFile, fi.FullName); fi.Delete(); } }); return(true); })) { // can't delete the file - not an error; just informational Log.LogMessage(MessageImportance.Normal, Strings.CouldNotDeleteOutputFile, symbolsFileInfo.FullName); } } }
protected override void GenerateStyleSheet(OutputGroup outputGroup, IList <InputGroup> inputGroups, UglifyCommandParser uglifyCommandParser, string outputPath, Encoding outputEncoding) { if (uglifyCommandParser == null) { throw new ArgumentNullException("uglifyCommandParser"); } ProcessStylesheet( inputGroups, uglifyCommandParser, outputPath, outputEncoding); }
/// <summary> /// Get an encoding to use for the given input file /// </summary> /// <param name="outputGroup">output file</param> /// <param name="defaultEncodingName">default encoding name to use if none specified</param> /// <returns>encoding; UTF8 WITHOUT THE BOM is the default if nothing else specified</returns> public static Encoding GetEncoding(this OutputGroup outputGroup, string defaultEncodingName) { Encoding encoding = null; if (outputGroup != null) { // if none specified on the output group, use the default var encodingName = outputGroup.EncodingName.IfNullOrWhiteSpace(defaultEncodingName); if (!encodingName.IsNullOrWhiteSpace()) { try { // try to create an encoding from the encoding name // using special encoder fallback determined earlier, or a default // encoder fallback that uses the UNICODE "replace character" for // things it doesn't understand, and a decoder replacement fallback // that also uses the UNICODE "replacement character" for things it doesn't understand. encoding = Encoding.GetEncoding( encodingName, GetEncoderFallback(outputGroup.CodeType), new DecoderReplacementFallback("\uFFFD")); } catch (ArgumentException e) { // eat the exception and just go with UTF-8 System.Diagnostics.Debug.WriteLine(e.ToString()); } } } // the default output is UTF-8 WITHOUT the BOM if we are outputting to a file, // or ASCII if we are outputting to STDOUT if (encoding == null) { if (outputGroup == null || outputGroup.Path.IsNullOrWhiteSpace()) { // no output group or outputting to stdout (no output path) encoding = (Encoding)Encoding.ASCII.Clone(); encoding.EncoderFallback = GetEncoderFallback(outputGroup.IfNotNull(g => g.CodeType)); } else { // outputting to file, use UTF-8 WITHOUT the BOM. // don't need a fallback encoder for UTF-8. encoding = new UTF8Encoding(false); } } return(encoding); }
/// <summary> /// Create ResourceString objects from all the input resources for an output group and add them to a list /// </summary> /// <param name="outputGroup">output group</param> /// <param name="resourceStringList">resource strings list</param> /// <param name="defaultResourceObjectName">optional default resource object name</param> public static void ProcessResourceStrings(this OutputGroup outputGroup, IList <ResourceStrings> resourceStringList, string defaultResourceObjectName) { if (outputGroup != null && resourceStringList != null) { foreach (var resource in outputGroup.Resources) { // create the resource strings object from the resources file. var resourceStrings = ProcessResourceFile(resource.Path); // if there is no name specified in the resource element, use the default. resourceStrings.Name = resource.Name.IfNullOrWhiteSpace(defaultResourceObjectName); // add it to the given list. resourceStringList.Add(resourceStrings); } } }
/// <summary> /// Process an output group. Override this method if the task doesn't want to check the input file times against /// the output file times (or existence) and call the GenerateOutput methods. /// </summary> /// <param name="outputGroup">the OutputGroup being processed</param> /// <param name="outputFileInfo">FileInfo for the desired output file</param> /// <param name="symbolsFileInfo">FileInfo for the optional desired symbol file</param> /// <param name="defaultSettings">default settings for this output group</param> /// <param name="manifestModifiedTime">modified time for the manifest</param> protected virtual void ProcessOutputGroup(OutputGroup outputGroup, FileInfo outputFileInfo, FileInfo symbolsFileInfo, UglifyCommandParser defaultSettings, DateTime manifestModifiedTime) { // check the file times -- if any of the inputs are newer than any output (or if any outputs don't exist), // then generate the output files if (AnyInputsAreNewerThanOutputs(outputGroup, outputFileInfo, symbolsFileInfo, manifestModifiedTime)) { // get the settings to use -- take the configuration for this output group // and apply them over the default settings var settings = ParseConfigSettings(outputGroup.GetConfigArguments(this.Configuration), defaultSettings); GenerateOutputFiles(outputGroup, outputFileInfo, settings); } else { // none of the inputs are newer than the output -- we're good. Log.LogMessage(Strings.SkippedOutputFile, outputFileInfo.IfNotNull(fi => fi.Name) ?? string.Empty); } }
void GenerateOutputFiles(OutputGroup outputGroup, FileInfo outputFileInfo, UglifyCommandParser uglifyCommandParser) { // create combined input source var inputGroups = outputGroup.ReadInputGroups(uglifyCommandParser.EncodingInputName); if (inputGroups.Count > 0) { switch (outputGroup.CodeType) { case CodeType.JavaScript: // call the virtual function to generate the JavaScript output file from the inputs GenerateJavaScript(outputGroup, inputGroups, uglifyCommandParser, outputFileInfo.FullName, outputGroup.GetEncoding(uglifyCommandParser.EncodingOutputName)); break; case CodeType.StyleSheet: // call the virtual function to generate the stylesheet output file from the inputs GenerateStyleSheet(outputGroup, inputGroups, uglifyCommandParser, outputFileInfo.FullName, outputGroup.GetEncoding(uglifyCommandParser.EncodingOutputName)); break; case CodeType.Unknown: Log.LogError(Strings.UnknownCodeType); break; } } else { // no input! write an empty output file if (!FileWriteOperation(outputFileInfo.FullName, uglifyCommandParser.Clobber, () => { using (var stream = outputFileInfo.Create()) { // write nothing; just create the empty file return(true); } })) { // could not write file Log.LogError(Strings.CouldNotWriteOutputFile, outputFileInfo.FullName); } } }
protected override void GenerateStyleSheet(OutputGroup outputGroup, IList <InputGroup> inputGroups, UglifyCommandParser uglifyCommandParser, string outputPath, Encoding outputEncoding) { // shouldn't get called because we override the ProcessOutputGroup method throw new NotImplementedException(); }
/// <summary> /// Check the file times of the input files and the output files. If the output files don't exist, or if any of the input files are /// newer than the already-existing output files, generate the output files again by calling the virtual method GenerateOutputFiles. /// </summary> /// <param name="outputGroup">the OutputGroup being processed</param> /// <param name="outputFileInfo">FileInfo for the desired output file</param> /// <param name="symbolsFileInfo">FileInfo for the optional desired symbol file</param> /// <param name="manifestModifiedTime">modified time for the manifest</param> bool AnyInputsAreNewerThanOutputs(OutputGroup outputGroup, FileInfo outputFileInfo, FileInfo symbolsFileInfo, DateTime manifestModifiedTime) { // build the output files var processGroup = false; var codeType = outputGroup.CodeType; if (!outputFileInfo.Exists || (!IgnoreSourceMapOutput && symbolsFileInfo != null && !symbolsFileInfo.Exists)) { // one or more outputs don't exist, so we need to process this group processGroup = true; } else { // output exists. we need to check to see if it's older than // any of its input files, and if not, there's no need to process // this group. get the filetime of the output file. var outputFileTime = outputFileInfo.LastWriteTimeUtc; // if we don't want a symbol map, then ignore that output. But if we // do and it doesn't exist, then we want to process the group. If we // do and it does, then check its filetime and set out output filetime // to be the earliest of the two (output or symbols) if (!IgnoreSourceMapOutput && symbolsFileInfo != null) { var symbolsFileTime = symbolsFileInfo.LastWriteTimeUtc; if (symbolsFileTime < outputFileTime) { outputFileTime = symbolsFileTime; } } // first check the time of the manifest file itself. If it's newer than the output // time, then we need to process. Otherwise we need to check each input source file. // also rebuild if they're equal, just in case. if (manifestModifiedTime >= outputFileTime) { // the manifest itself has been changed after the last output that was generated, // so yes: we need to process this group. processGroup = true; } else { // check filetime of each input file, and if ANY one is newer, // then we will want to set the process-group flag and stop checking foreach (var input in outputGroup.Inputs) { var fileInfo = new FileInfo(input.Path); if (fileInfo.Exists) { // equal time also means process the output, because that is just SO close to // the input being newer. Plus, someone might have the input be the output, so // the times will be equal and we will want to process it. if (fileInfo.LastWriteTimeUtc >= outputFileTime) { processGroup = true; break; } } else { // file doesn't exist -- check to see if it's a directory var folderInfo = new DirectoryInfo(fileInfo.FullName); if (folderInfo.Exists) { // not a FILE, it's a FOLDER of files. // in order to specify an input folder, we need to have had the right type attribute // on the output group so we know what kind of files to look for if (codeType == CodeType.Unknown) { // log an error, then bail because we won't be able to do anything anyway // since we don't know what kind of code we are processing and we don't know which // files to include from this folder. Log.LogError(Strings.DirectorySourceRequiresCodeType); return(false); } else { // recursively check all the files in the folder with the proper extension for the code type. // if anything pops positive, we know we want to process the group so bail early. processGroup = CheckFolderInputFileTimes(folderInfo, ExtensionsFromCodeType(codeType), outputFileTime); if (processGroup) { break; } } } } } } // do the same to any resource file, there are any (and we don't already know we // want to process this group) if (!processGroup && outputGroup.Resources.Count > 0) { foreach (var resource in outputGroup.Resources) { var fileInfo = new FileInfo(resource.Path); if (fileInfo.Exists && fileInfo.LastWriteTimeUtc >= outputFileTime) { processGroup = true; break; } } } } return(processGroup); }
protected abstract void GenerateStyleSheet(OutputGroup outputGroup, IList <InputGroup> inputGroups, UglifyCommandParser uglifyCommandParser, string outputPath, Encoding outputEncoding);
/// <summary> /// Walk the list of inputs for a given output group, and create a list of input groups /// consisting of the source and origin. Consecutive project inputs are concatenated together, /// and external inputs are kept separate. /// </summary> /// <param name="outputGroup">output group</param> /// <param name="defaultEncodingName">default encoding name to use if none specified</param> /// <param name="rawInputBuilder">output the raw source to this builder, no added comments</param> /// <param name="sourceLength">length of the raw source</param> /// <returns>list of input groups consisting of source code and origin</returns> public static IList <InputGroup> ReadInputGroups(this OutputGroup outputGroup, string defaultEncodingName) { var inputGroups = new List <InputGroup>(); if (outputGroup != null) { if (outputGroup.Inputs.Count == 0) { try { // try setting the input encoding - sometimes this fails! Console.InputEncoding = GetInputEncoding(defaultEncodingName); } catch (IOException e) { // error setting the encoding input; just use whatever the default is System.Diagnostics.Debug.WriteLine(e.ToString()); } var sourceComment = outputGroup.CodeType == CodeType.StyleSheet ? "/*/#SOURCE 1 1 stdin */\r\n" : "///#SOURCE 1 1 stdin\r\n"; // read from stdin, append to the builder, then create a single project input group // from the build code. var sourceCode = Console.In.ReadToEnd(); inputGroups.Add(new InputGroup { RawSource = sourceCode, Source = sourceComment + sourceCode, Origin = SourceOrigin.Project }); } else { // start off saying the previous item ends in a semicolon so we don't add one before the first file var fileReadBuilder = new FileReadBuilder { EndsInSemicolon = true }; for (var ndx = 0; ndx < outputGroup.Inputs.Count; ++ndx) { var inputFile = outputGroup.Inputs[ndx]; if (inputFile.Origin == SourceOrigin.External) { // we found an external input file. // if we were building up project input files, dump what we have into a new group and clear it out if (fileReadBuilder.Length > 0) { inputGroups.Add(new InputGroup { Source = fileReadBuilder.ToString(), RawSource = fileReadBuilder.ToRawString(), Origin = SourceOrigin.Project }); } // read the content of the external file, passing in false for whether the previous file // ended in a semicolon so that one will ALWAYS get prepended. Add a new input group for the // content by itself, and then always reset the semicolon flag to false so that the NEXT code // will be separated from the external code with another semicolon. fileReadBuilder.Clear(); ReadFile(inputFile, fileReadBuilder, outputGroup.CodeType, defaultEncodingName); inputGroups.Add(new InputGroup { Source = fileReadBuilder.ToString(), RawSource = fileReadBuilder.ToRawString(), Origin = SourceOrigin.External }); fileReadBuilder.Clear(); fileReadBuilder.EndsInSemicolon = false; } else { // read the project input file into the group builder, with context. ReadFile(inputFile, fileReadBuilder, outputGroup.CodeType, defaultEncodingName); } } // if there is anything left, add it as a new input group now if (fileReadBuilder.Length > 0) { inputGroups.Add(new InputGroup { Source = fileReadBuilder.ToString(), RawSource = fileReadBuilder.ToRawString(), Origin = SourceOrigin.Project }); } } } return(inputGroups); }
/// <summary> /// Given the current configuration, return the appropriate argument string /// </summary> /// <param name="outputGroup">output group object</param> /// <param name="configuration">current configuration</param> /// <returns>argument string, or empty string if not found</returns> public static string GetConfigArguments(this OutputGroup outputGroup, string configuration) { return(outputGroup == null ? string.Empty : GetConfigArguments(outputGroup.Arguments, configuration)); }
private static OutputGroup ReadOutputElement(XmlReader reader) { var outputNode = new OutputGroup(); while (reader.Read()) { // get the attributes if (reader.Name == OutputElementName && reader.HasAttributes) { while (reader.MoveToNextAttribute()) { switch (reader.Name) { case PathAttributeName: outputNode.Path = reader.Value; break; case EncodingAttributeName: case EncodingAttributeShortName: outputNode.EncodingName = reader.Value; break; case TypeAttributeName: switch (reader.Value.ToUpperInvariant()) { case "JS": case "JAVASCRIPT": case "JSCRIPT": outputNode.CodeType = CodeType.JavaScript; break; case "CSS": case "STYLESHEET": case "STYLESHEETS": outputNode.CodeType = CodeType.StyleSheet; break; } break; case MapPathAttributeName: outputNode.SymbolMap = new SymbolMap() { Path = reader.Value }; break; } } // back to element reader.MoveToElement(); } // process child elements if (reader.NodeType == XmlNodeType.Element) { switch (reader.Name) { case ArgumentsElementName: ReadArgumentsElement(reader.ReadSubtree(), outputNode.Arguments); break; case RenameElementName: ReadRenameElement(reader.ReadSubtree(), outputNode.RenameIdentifiers); break; case NoRenameElementName: ReadNoRenameElement(reader.ReadSubtree(), outputNode.NoRenameIdentifiers); break; case SymbolMapElementName: outputNode.SymbolMap = ReadSymbolMapElement(reader.ReadSubtree()); break; case ResourceElementName: outputNode.Resources.Add(ReadResourceElement(reader.ReadSubtree())); break; case InputElementName: outputNode.Inputs.Add(ReadInputElement(reader.ReadSubtree())); break; } } } ((IDisposable)reader).Dispose(); return(outputNode); }