summarylogtreecommitdiffstats
path: root/RGX.cs
blob: 002eca88c5de346f7ded24cda67d55d02ed84ef4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
using System.Diagnostics;
using System.Globalization;
using System.Text.RegularExpressions;
using CommandLine;
using comroid.common;
using comroid.common.csapi;

namespace rgx;

public static class RGX
{
    private static readonly Log log = new(typeof(RGX));

    static RGX()
    {
        ILog.Detail = DetailLevel.None;
    }
    
    public static void
#if TEST
        Exec
#else
        Main
#endif
        (params string[] args)
    {
        new Parser(cfg =>
            {
                cfg.CaseSensitive = false;
                cfg.CaseInsensitiveEnumValues = true;
                cfg.HelpWriter = Console.Out;
                cfg.IgnoreUnknownArguments = true;
                cfg.AutoHelp = true;
                cfg.AutoVersion = true;
                cfg.ParsingCulture = CultureInfo.InvariantCulture;
                cfg.EnableDashDash = false;
                cfg.MaximumDisplayWidth = log.RunWithExceptionLogger(() => Console.WindowWidth, "Could not get Console Width", _=>1024,LogLevel.Debug);
            }).ParseArguments<MatchCmd, ExpandCmd, SplitCmd, CutCmd>(args)
            .WithParsed(Run<MatchCmd>(Match))
            .WithParsed(Run<ExpandCmd>(Expand))
            .WithParsed(Run<SplitCmd>(Split))
            .WithParsed(Run<CutCmd>(Cut))
            .WithNotParsed(Error);
    }

    #region Command Methods

    private static IEnumerable<string> Match(MatchCmd cmd, string line, Match match)
    {
        yield return match.ToString();
    }

    private static IEnumerable<string> Expand(ExpandCmd cmd, string line, Match match)
    {
        var replacement = File.Exists(cmd.expander) ? File.ReadAllText(cmd.expander) : cmd.expander;
        yield return match.Result(replacement);
    }

    private static IEnumerable<string> Split(SplitCmd cmd, string line, Match match)
    {
        var matches = new List<Match>();
        do
        {
            matches.Add(match);
        } while ((match = match.NextMatch()) is { Success: true });

        for (var i = 0; i < matches.Count; i++)
        {
            var each = matches[i];
            var next = matches.Count>i+1?matches[i+1]:null;
            if (next == null)
                break;
            var l = each.Index + each.Length;
            var r = (next?.Index ?? each.Index) - l;
            if (l + r > line.Length)
                break;
            yield return line.Substring(l, r);
        }
    }

    private static IEnumerable<string> Cut(CutCmd cmd, string line, Match match)
    {
        var matches = new List<Match>();
        do
        {
            matches.Add(match);
        } while ((match = match.NextMatch()) is { Success: true });

        var lastEnd = 0;
        foreach (var each in matches)
            line = line.Substring(lastEnd, lastEnd += each.Length);
        yield return line;
    }

    private static void Error(IEnumerable<Error> errors)
    {
        foreach (var error in errors)
            log.At(LogLevel.Debug, error);
    }

    #endregion

    #region Utility Methods

    private static Action<CMD> Run<CMD>(Func<CMD, string, Match, IEnumerable<string>> handler) where CMD : ICmd
    {
        return cmd =>
        {
            if (cmd.flags.Contains(RegexOptions.Multiline))
                Console.Error.WriteLine("Warning: The multiline flag is not supported since we're only ever parsing line by line");
            var regexOptions = cmd.flags.Aggregate((RegexOptions)0, (x, y) => x | y);
            Regex BuildRegex(Streamable from) => new(from.AsString(), regexOptions);
            var pattern = Streamable.Get(cmd.pattern).Use(BuildRegex).NonNull();
            var input = Streamable.Get(cmd.input).OrStdIO().AsReader();
            var output = Streamable.Get(cmd.output).OrStdIO().AsWriter();
            var start = Streamable.Get(cmd.start).Use(BuildRegex);
            var stop = Streamable.Get(cmd.stop).Use(BuildRegex);
            bool started = start == null, stopped = false;

            while (!stopped && input.ReadLine() is { } line)
            {
                if (!started && start != null)
                    started = start.IsMatch(line);
                else if (stop != null)
                    stopped = stop.IsMatch(line);
                else
                {
                    var match = pattern.Match(line);
                    var success = match.Success;

                    if (!success && cmd.unmatched == ICmd.IncludeMode.Prepend)
                        output.WriteLine(line);
                    else if (success)
                    {
                        if (cmd.untreated == ICmd.IncludeMode.Prepend)
                            output.WriteLine(line);
                        foreach (var str in handler(cmd, line, match))
                            output.WriteLine(str);
                        if (cmd.untreated == ICmd.IncludeMode.Append)
                            output.WriteLine(line);
                    }
                    else if (cmd.unmatched == ICmd.IncludeMode.Append)
                        output.WriteLine(line);
                }
            }

            foreach (var res in new IDisposable[] { input, output })
                res.Dispose();
        };
    }

    #endregion
}