/// <summary> /// replaces all flavor statements with the correct flavor content in this string /// </summary> /// <param name="s">the string to flavor</param> /// <param name="activeFlavors">a list of active flavors</param> /// <param name="logSession">log session for logging</param> /// <returns>the flavored string</returns> public string FlavorString(string s, List <string> activeFlavors, Log.AsyncLogSession logSession) { //repeatedly replace statements with their flavor content Match statementMatch = null; Dictionary <string, string> flavors; do { //match string using regex, match the first statement statementMatch = Regex.Match(s, FLAVOR_STATEMENT_PATTERN); if (!statementMatch.Success) { continue; } //get full statement AND expression from match string statement = statementMatch.Value; string expression = statementMatch.Groups[1].Value; logSession?.vv($"process statement {statement}"); //skip if expression is empty if (string.IsNullOrWhiteSpace(expression)) { break; } //get flavors for expression, skip if there are none flavors = GetFlavorsFromExpression(expression, logSession); if (flavors == null || flavors.Count <= 0) { break; } //get the correct flavor to use, skip if none of the flavors is valid string flavor = GetFirstFlavor(flavors.Keys, activeFlavors); //get the right content for the flavor, default to string.Empty string flavorContent = string.Empty; if (string.IsNullOrWhiteSpace(flavor)) { logSession?.w($"no active flavor for {statement}, fallback to empty"); } else { flavorContent = flavors[flavor]; if (string.IsNullOrWhiteSpace(flavorContent)) { logSession?.w($"flavor content for flavor {flavor} is empty!"); } } //replace flavor statement with content logSession?.d($"replacing {statement} with {flavorContent}..."); s = s.ReplaceFirst(statement, flavorContent); }while (statementMatch.Success); return(s); }
/// <summary> /// Flavor a project directory /// </summary> /// <param name="inputDir">the directory to flavor</param> /// <param name="outputDir">where the project directory is mirrored to, flavored</param> /// <param name="activeFlavors">a list of all active flavors</param> /// <param name="parallelProcessing">should files be processed in parallel?</param> public void FlavorProject(DirectoryInfo inputDir, DirectoryInfo outputDir, List <string> activeFlavors, bool parallelProcessing = false) { //counters for flavoring results int flavoredChanged = 0, flavoredUnchanged = 0, copied = 0, skipped = 0; //setup function for each file (~= loop body) Action <FileInfo> processAction = (FileInfo input) => { using (Log.AsyncLogSession logSession = Log.StartAsync()) { //set tag of session logSession.PushTag(input.Name); //create output fileinfo FileInfo output = new FileInfo(Path.Combine(outputDir.FullName, Path.GetRelativePath(inputDir.FullName, input.FullName))); //flavor the file, count results switch (FlavorFile(input, output, activeFlavors, logSession)) { case FlavorResult.FlavoredReplace: flavoredChanged++; break; case FlavorResult.FlavoredSkipped: flavoredUnchanged++; break; case FlavorResult.Copied: copied++; break; case FlavorResult.Skipped: skipped++; break; } } }; //enumerate files if (parallelProcessing) { inputDir.EnumerateAllFilesParallel("*.*", true, processAction); } else { inputDir.EnumerateAllFiles("*.*", true, processAction); } //log results Log.i($"Finished processing. {flavoredChanged} flavored files changed, {flavoredUnchanged} unchanged, {copied} files copied and {skipped} files skipped entirely."); }
/// <summary> /// flavor a single file /// </summary> /// <param name="input">input file to flavor</param> /// <param name="output">where the file is mirrored to, flavored</param> /// <param name="activeFlavors">a list of all active flavors</param> /// <param name="logSession">logging session for this file</param> public FlavorResult FlavorFile(FileInfo input, FileInfo output, List <string> activeFlavors, Log.AsyncLogSession logSession) { //check filter first bool shouldFlavor = FlavorFilter.MatchesFilter(input); //check if file should be included if (!IncludeFilter.ShouldInclude(input, output, shouldFlavor)) { logSession?.v("Skipping file: does not match includeFilter!"); return(FlavorResult.Skipped); } //check if file should be flavored or just copied if (shouldFlavor) { //process into a temp file FileInfo temp = new FileInfo(Path.GetTempFileName()); using (StreamReader inputStream = input.OpenText()) using (StreamWriter outputStream = temp.CreateText()) { seasoner.FlavorStream(inputStream, outputStream, activeFlavors, logSession); } //check if old output and temp file are the same if (output.Exists && FileComparator.IsSameFile(output, temp)) { logSession.v("old processed file matches the new one, not replacing"); temp.Delete(); return(FlavorResult.FlavoredSkipped); } else { //not equal, move temp to output string delTemp = temp.FullName; temp.MoveTo(output.FullName, true); IncludeFilter.PostProcessed(input, output); //delete old temp file if (File.Exists(delTemp)) { File.Delete(delTemp); } return(FlavorResult.FlavoredReplace); } } else { //check if file in output is the same as in input if (output.Exists && FileComparator.IsSameFile(input, output)) { logSession.v("old copied file matches file in input, skipping"); return(FlavorResult.Skipped); } //copy file from input to output input.CopyTo(output.FullName, true); IncludeFilter.PostProcessed(input, output); return(FlavorResult.Copied); } }
/// <summary> /// flavors a stream of text /// </summary> /// <param name="input">the input stream (eg. source file)</param> /// <param name="output">the output file (eg. output file)</param> /// <param name="activeFlavors">a list of currently active flavors</param> /// <param name="logSession">log session for logging</param> public void FlavorStream(StreamReader input, StreamWriter output, List <string> activeFlavors, Log.AsyncLogSession logSession) { //log processing the file logSession?.v($"Start processing stream..."); //process line by line string ln; int lnCount = 0; while ((ln = input.ReadLine()) != null) { //set logging tag logSession?.PushTag($"{logSession?.GetTag()}:{lnCount++}"); //flavor string and write to output ln = FlavorString(ln, activeFlavors, logSession); output.WriteLine(ln); //pop back tag logSession?.PopTag(); } }
/// <summary> /// splits the expression list into flavor/content pairs /// </summary> /// <param name="expression">the expression to split into pairs</param> /// <returns>the flavor/content pairs</returns> Dictionary <string /*flavor name*/, string /*flavor content*/> GetFlavorsFromExpression(string expression, Log.AsyncLogSession logSession) { //init dictionary to hold results Dictionary <string, string> flavorDict = new Dictionary <string, string>(); //match all expressions MatchCollection expressionMatches = Regex.Matches(expression, FLAVOR_EXPRESSION_PATTERN); logSession?.d($"Processing expressions for {expression}, found {expressionMatches.Count} matches."); if (expressionMatches.Count <= 0) { return(null); } //enumerate all expressionss foreach (Match expressionMatch in expressionMatches) { //skip if no succcess or CG1 or CG2 are not valid if (!expressionMatch.Success || string.IsNullOrWhiteSpace(expressionMatch.Groups[1].Value) || expressionMatch.Groups[2].Value == null) { continue; } //get flavor name and flavor content string flavorName = expressionMatch.Groups[1].Value; string flavorContent = expressionMatch.Groups[2].Value; //do some processing with the strings flavorName = flavorName.ToLower().Trim(); flavorContent = flavorContent.Trim(); //skip if dict already contains this flavor name if (flavorDict.ContainsKey(flavorName)) { logSession?.w($"Duplicate flavor {flavorName} in expression {expression}!"); continue; } //add to dict logSession?.v($"add {flavorName} - {flavorContent} to dict"); flavorDict.Add(flavorName, flavorContent); } //return null if dict is empty logSession?.v($"flavorDict.Count is {flavorDict.Count}"); if (flavorDict.Count <= 0) { return(null); } //return filled dict return(flavorDict); }