|
3 | 3 | using System.IO; |
4 | 4 | using System.Linq; |
5 | 5 | using System.Reflection; |
6 | | -using System.Threading.Tasks; |
| 6 | +using ColoredConsole; |
| 7 | +using Devlooped; |
7 | 8 | using DotNetConfig; |
8 | 9 | using Mono.Options; |
9 | 10 |
|
10 | | -namespace Devlooped; |
11 | | - |
12 | | -class Program |
13 | | -{ |
14 | | - static async Task<int> Main(string[] args) |
15 | | - { |
16 | | - var help = false; |
17 | | - string? changelog = null; |
18 | | - var options = new OptionSet |
| 11 | +var help = false; |
| 12 | +string? changelog = null; |
| 13 | +var options = new OptionSet |
19 | 14 | { |
20 | 15 | { "?|h|help", "Display this help", h => help = h != null }, |
21 | 16 | { "c|changelog:", "Write a changelog", c => changelog = string.IsNullOrEmpty(c) ? "dotnet-file.md" : c }, |
22 | 17 | }; |
23 | 18 |
|
24 | | - var extraArgs = options.Parse(args); |
25 | | - if ((args.Length == 1 && help) || extraArgs.Count == 0) |
26 | | - return ShowHelp(); |
| 19 | +var extraArgs = options.Parse(args); |
| 20 | +if ((args.Length == 1 && help) || extraArgs.Count == 0) |
| 21 | + return ShowHelp(); |
27 | 22 |
|
28 | | - var config = Config.Build(); |
29 | | - var command = extraArgs[0].ToLowerInvariant() switch |
30 | | - { |
31 | | - "add" => new AddCommand(config), |
32 | | - "changes" => new ChangesCommand(config), |
33 | | - "delete" => new DeleteCommand(config), |
34 | | - "init" => new InitCommand(config), |
35 | | - "list" => new ListCommand(config), |
36 | | - "sync" => new SyncCommand(config), |
37 | | - "update" => new UpdateCommand(config), |
38 | | - _ => Command.NullCommand, |
39 | | - }; |
| 23 | +var commandName = extraArgs[0].ToLowerInvariant(); |
| 24 | +extraArgs.RemoveAt(0); |
40 | 25 |
|
41 | | - if (command == Command.NullCommand) |
42 | | - return ShowHelp(); |
| 26 | +var config = Config.Build(); |
43 | 27 |
|
44 | | - // Remove first arg which is the command to use. |
45 | | - extraArgs.RemoveAt(0); |
| 28 | +// Handle --init [url] as a cross-cutting pre-processing step: download a remote |
| 29 | +// .netconfig and merge its skip=true entries into the local config before any command |
| 30 | +// runs, so upstream skips are honored regardless of which command is executed. |
| 31 | +string? initUrl = null; |
| 32 | +if (extraArgs.FindIndex(a => a == "--init") is var initIndex && initIndex >= 0 && |
| 33 | + initIndex + 1 < extraArgs.Count && !extraArgs[initIndex + 1].StartsWith('-')) |
| 34 | +{ |
| 35 | + initUrl = extraArgs[initIndex + 1]; |
| 36 | + extraArgs.RemoveRange(initIndex, 2); |
46 | 37 |
|
47 | | - // Handle --init [url] option for the sync command. |
48 | | - if (command is SyncCommand sync && |
49 | | - extraArgs.FindIndex(a => a == "--init") is var index && index >= 0) |
50 | | - { |
51 | | - if (index + 1 < extraArgs.Count && !extraArgs[index + 1].StartsWith('-')) |
52 | | - { |
53 | | - sync.InitUrl = extraArgs[index + 1]; |
54 | | - extraArgs.RemoveRange(index, 2); |
55 | | - } |
56 | | - else |
57 | | - { |
58 | | - extraArgs.RemoveAt(index); |
59 | | - } |
60 | | - } |
| 38 | + var tempConfig = Path.GetTempFileName(); |
| 39 | + var tempFile = Path.GetTempFileName(); |
| 40 | + try |
| 41 | + { |
| 42 | + var addCmd = new AddCommand(Config.Build(tempConfig)); |
| 43 | + addCmd.Files.Add(new FileSpec(tempFile, new Uri(initUrl))); |
61 | 44 |
|
62 | | - // Add remainder arguments as if they were just files or urls provided |
63 | | - // to the command. Allows skipping the -f|-u switches. |
64 | | - var skip = false; |
65 | | - var files = new List<FileSpec>(); |
66 | | - List<FileSpec>? configured = default; |
| 45 | + ColorConsole.WriteLine("Downloading init config file(s)...".Yellow()); |
| 46 | + await addCmd.ExecuteAsync(); |
67 | 47 |
|
68 | | - for (var i = 0; i < extraArgs.Count; i++) |
| 48 | + var remoteConfig = Config.Build(tempFile); |
| 49 | + foreach (var group in remoteConfig |
| 50 | + .Where(x => x.Level == null && x.Section == "file" && x.Subsection != null) |
| 51 | + .GroupBy(x => x.Subsection!)) |
69 | 52 | { |
70 | | - if (skip) |
71 | | - { |
72 | | - skip = false; |
| 53 | + var path = group.Key; |
| 54 | + if (remoteConfig.GetBoolean("file", path, "skip") != true) |
73 | 55 | continue; |
74 | | - } |
75 | | - |
76 | | - // Try to pair Uri+File to allow intuitive download>path mapping, such as |
77 | | - // https://gitub.com/org/repo/docs/file.md > docs/file.md |
78 | | - if (Uri.TryCreate(extraArgs[i], UriKind.Absolute, out var uri)) |
79 | | - { |
80 | | - var next = i + 1; |
81 | | - // If the next arg is not a URI, use that as the file path for the uri |
82 | | - if (next < extraArgs.Count && !Uri.TryCreate(extraArgs[next], UriKind.Absolute, out _)) |
83 | | - { |
84 | | - files.Add(FileSpec.WithPath(extraArgs[next], uri)); |
85 | | - skip = true; |
86 | | - } |
87 | | - else |
88 | | - { |
89 | | - // If the next file is a URI, then no path has been specified. |
90 | | - // We should attempt to recreate the same path structure locally, |
91 | | - // which is the most intuitive default. If users don't want that, |
92 | | - // they can specify '.' to get the old behavior. |
93 | | - files.Add(FileSpec.WithDefaultPath(uri)); |
94 | | - } |
95 | | - } |
96 | | - else |
97 | | - { |
98 | | - // Attempt to match a simple filename to a configured one |
99 | | - configured ??= [.. command.GetConfiguredFiles()]; |
100 | | - if (configured.FirstOrDefault(x => x.Path == extraArgs[i]) is { } spec) |
101 | | - files.Add(spec); |
102 | | - } |
103 | | - } |
104 | 56 |
|
105 | | - command.Files.AddRange(files); |
| 57 | + // Local entry takes precedence — only add skip if no local entry exists |
| 58 | + if (config.Where(x => x.Section == "file" && x.Subsection == path).Any()) |
| 59 | + continue; |
106 | 60 |
|
107 | | - var result = await command.ExecuteAsync(); |
| 61 | + var url = remoteConfig.GetString("file", path, "url"); |
| 62 | + if (url != null) |
| 63 | + config = config.SetString("file", path, "url", url); |
108 | 64 |
|
109 | | - // If there were changes and a changelog was requested, emit it |
110 | | - // to a file. |
111 | | - if (changelog != null && |
112 | | - command is AddCommand add && |
113 | | - add.Changes.Count > 0 && |
114 | | - GitHub.IsInstalled) |
115 | | - { |
116 | | - GitHub.WriteChanges(changelog!, add.Changes); |
| 65 | + config = config.SetBoolean("file", path, "skip", true); |
117 | 66 | } |
118 | | - |
119 | | - return result; |
120 | 67 | } |
| 68 | + finally |
| 69 | + { |
| 70 | + File.Delete(tempConfig); |
| 71 | + File.Delete(tempFile); |
| 72 | + } |
| 73 | +} |
121 | 74 |
|
122 | | - static int ShowHelp() |
| 75 | +var command = commandName switch |
| 76 | +{ |
| 77 | + "add" => new AddCommand(config), |
| 78 | + "changes" => new ChangesCommand(config), |
| 79 | + "delete" => new DeleteCommand(config), |
| 80 | + "init" => new InitCommand(config), |
| 81 | + "list" => new ListCommand(config), |
| 82 | + "sync" => new SyncCommand(config), |
| 83 | + "update" => new UpdateCommand(config), |
| 84 | + _ => Devlooped.Command.NullCommand, |
| 85 | +}; |
| 86 | + |
| 87 | +if (command == Devlooped.Command.NullCommand) |
| 88 | + return ShowHelp(); |
| 89 | + |
| 90 | +// Add remainder arguments as if they were just files or urls provided |
| 91 | +// to the command. Allows skipping the -f|-u switches. |
| 92 | +var skip = false; |
| 93 | +var files = new List<FileSpec>(); |
| 94 | +List<FileSpec>? configured = default; |
| 95 | + |
| 96 | +for (var i = 0; i < extraArgs.Count; i++) |
| 97 | +{ |
| 98 | + if (skip) |
123 | 99 | { |
124 | | - Console.WriteLine(File.ReadAllText( |
125 | | - Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "Help.txt"))); |
| 100 | + skip = false; |
| 101 | + continue; |
| 102 | + } |
126 | 103 |
|
127 | | - return 0; |
| 104 | + // Try to pair Uri+File to allow intuitive download>path mapping, such as |
| 105 | + // https://gitub.com/org/repo/docs/file.md > docs/file.md |
| 106 | + if (Uri.TryCreate(extraArgs[i], UriKind.Absolute, out var uri)) |
| 107 | + { |
| 108 | + var next = i + 1; |
| 109 | + // If the next arg is not a URI, use that as the file path for the uri |
| 110 | + if (next < extraArgs.Count && !Uri.TryCreate(extraArgs[next], UriKind.Absolute, out _)) |
| 111 | + { |
| 112 | + files.Add(FileSpec.WithPath(extraArgs[next], uri)); |
| 113 | + skip = true; |
| 114 | + } |
| 115 | + else |
| 116 | + { |
| 117 | + // If the next file is a URI, then no path has been specified. |
| 118 | + // We should attempt to recreate the same path structure locally, |
| 119 | + // which is the most intuitive default. If users don't want that, |
| 120 | + // they can specify '.' to get the old behavior. |
| 121 | + files.Add(FileSpec.WithDefaultPath(uri)); |
| 122 | + } |
| 123 | + } |
| 124 | + else |
| 125 | + { |
| 126 | + // Attempt to match a simple filename to a configured one |
| 127 | + configured ??= [.. command.GetConfiguredFiles()]; |
| 128 | + if (configured.FirstOrDefault(x => x.Path == extraArgs[i]) is { } spec) |
| 129 | + files.Add(spec); |
128 | 130 | } |
129 | 131 | } |
| 132 | + |
| 133 | +command.Files.AddRange(files); |
| 134 | + |
| 135 | +var result = await command.ExecuteAsync(); |
| 136 | + |
| 137 | +// If there were changes and a changelog was requested, emit it |
| 138 | +// to a file. |
| 139 | +if (changelog != null && |
| 140 | + command is AddCommand add && |
| 141 | + add.Changes.Count > 0 && |
| 142 | + Devlooped.GitHub.IsInstalled) |
| 143 | +{ |
| 144 | + Devlooped.GitHub.WriteChanges(changelog!, add.Changes); |
| 145 | +} |
| 146 | + |
| 147 | +return result; |
| 148 | + |
| 149 | +static int ShowHelp() |
| 150 | +{ |
| 151 | + Console.WriteLine(File.ReadAllText( |
| 152 | + Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "Help.txt"))); |
| 153 | + |
| 154 | + return 0; |
| 155 | +} |
0 commit comments