// Original behavior/reference: sed (Lee E. McMahon) // Ported to .NET by Timothy J. Bruce namespace Icod.CoreUtils.Sed; using System; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using System.Text; /// /// sed: stream editor (simplified). /// Supported features: /// -n suppress automatic printing /// -e script add script to the commands to run /// Script forms supported (simple subset): /// s/old/new/g substitute (supports regex) with optional g flag /// Multiple -e accepted; first script applied if none specified. /// This is a small, portable subset for common use cases. /// public static class Command { public static int Run(string[] args, TextReader? stdin = null, TextWriter? stdout = null, TextWriter? stderr = null) { stdin ??= Console.In; stdout ??= Console.Out; stderr ??= Console.Error; var scripts = new List(); var suppress = false; var files = new List(); var i = 0; for (; i < args.Length; i++) { var a = args[i]; if (!a.StartsWith("-")) { break; } if (a == "-n") { suppress = true; } else if (a == "-e") { if (i + 1 < args.Length) { i++; scripts.Add(args[i]); } } else { // ignore other options } } for (; i < args.Length; i++) { files.Add(args[i]); } if (scripts.Count == 0) { stderr.WriteLine("sed: no scripts provided"); return 2; } if (files.Count == 0) { files.Add("-"); } try { foreach (var path in files) { TextReader reader; if (path == "-") { reader = stdin; } else { reader = new StreamReader(path, Encoding.UTF8, detectEncodingFromByteOrderMarks: true); } using (reader) { string? line; while ((line = reader.ReadLine()) is not null) { var outLine = line; foreach (var script in scripts) { if (script.StartsWith("s/")) { // parse s/old/new/flags var m = Regex.Match(script, @"^s/(?.*?)/(?.*?)/(?.*)$"); if (!m.Success) { m = Regex.Match(script, @"^s/(?.*?)/(?.*)$"); } if (m.Success) { var oldPat = m.Groups["old"].Value; var newText = m.Groups["new"].Value; var flags = m.Groups["flags"].Success ? m.Groups["flags"].Value : string.Empty; var regexOptions = RegexOptions.None; var replaceCount = flags.Contains("g") ? -1 : 1; outLine = RegexReplace(outLine, oldPat, newText, regexOptions, replaceCount); } } else { // unsupported script: ignore } } if (!suppress) { stdout.WriteLine(outLine); } } } } return 0; } catch (Exception ex) { stderr.WriteLine($"sed: {ex.Message}"); return 1; } } private static string RegexReplace(string input, string pattern, string replacement, RegexOptions options, int maxReplacements) { try { if (maxReplacements == -1) { return Regex.Replace(input, pattern, replacement, options); } var regex = new Regex(pattern, options); var count = 0; return regex.Replace(input, m => { count++; if (count <= maxReplacements) { return replacement; } return m.Value; }); } catch { return input; } } }