Forge
csharpdeeb25a2
1#nullable enable
2using System.Diagnostics.CodeAnalysis;
3using System.Text.Json;
4using CascadeIDE.Contracts;
5
6namespace CascadeIDE.Features.Launch.DataAcquisition;
7
8/// <summary>
9/// Чтение SDK <c>Properties/launchSettings.json</c> и сопоставление с <see cref="LaunchProfileModel"/> (только <c>commandName: Project</c>, Kestrel).
10/// </summary>
11[IoBoundary]
12internal static class LaunchSettingsJsonImport
13{
14 internal static bool TryReadProjectProfiles(
15 string json,
16 string projectPathRelativeToSolution,
17 [NotNullWhen(true)] out List<(string Name, LaunchProfileModel Model)>? profiles,
18 [NotNullWhen(false)] out string? error)
19 {
20 profiles = null;
21 error = null;
22 try
23 {
24 using var doc = JsonDocument.Parse(json);
25 if (!doc.RootElement.TryGetProperty("profiles", out var p) || p.ValueKind != JsonValueKind.Object)
26 {
27 error = "launch_settings_no_profiles";
28 return false;
29 }
30
31 var list = new List<(string, LaunchProfileModel)>();
32 var proj = projectPathRelativeToSolution.Replace('/', Path.DirectorySeparatorChar);
33 foreach (var prop in p.EnumerateObject())
34 {
35 if (TryMapProjectProfile(prop.Value, proj, out var m))
36 list.Add((prop.Name, m));
37 }
38
39 if (list.Count == 0)
40 {
41 error = "launch_settings_no_project_profiles";
42 return false;
43 }
44
45 profiles = list;
46 return true;
47 }
48 catch (Exception ex)
49 {
50 error = "launch_settings_parse: " + ex.Message;
51 return false;
52 }
53 }
54
55 private static bool TryMapProjectProfile(JsonElement el, string projectRelative, [NotNullWhen(true)] out LaunchProfileModel? model)
56 {
57 model = null;
58 if (!IsKestrelProjectProfile(el))
59 return false;
60
61 var m = CreateBaseProfileModel(projectRelative);
62 ApplyCommandLineArgs(el, m);
63 ApplyApplicationUrl(el, m);
64 ApplyLaunchUrl(el, m);
65 ApplyLaunchBrowserFlag(el, m);
66 m.Env = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
67 ApplyEnvironmentVariables(el, m);
68 ApplyKestrelBrowserHeuristic(m);
69
70 model = m;
71 return true;
72 }
73
74 private static bool IsKestrelProjectProfile(JsonElement el)
75 {
76 if (el.ValueKind != JsonValueKind.Object)
77 return false;
78 if (!el.TryGetProperty("commandName", out var cn) || cn.ValueKind != JsonValueKind.String)
79 return false;
80 return string.Equals(cn.GetString(), "Project", StringComparison.Ordinal);
81 }
82
83 private static LaunchProfileModel CreateBaseProfileModel(string projectRelative) =>
84 new()
85 {
86 Project = projectRelative,
87 Configuration = LaunchProfilesStore.DefaultConfiguration
88 };
89
90 // Семантика как у SDK: токенизация по пробелу без кавычек; аргументы с пробелами в кавычках не разбираем.
91 private static void ApplyCommandLineArgs(JsonElement el, LaunchProfileModel m)
92 {
93 if (!el.TryGetProperty("commandLineArgs", out var cla) || cla.ValueKind != JsonValueKind.String)
94 return;
95 var s = cla.GetString();
96 if (string.IsNullOrWhiteSpace(s))
97 return;
98 var t = s.Trim();
99 if (t.Length == 0)
100 return;
101 m.ProgramArgs = t.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList();
102 }
103
104 private static void ApplyApplicationUrl(JsonElement el, LaunchProfileModel m)
105 {
106 if (!el.TryGetProperty("applicationUrl", out var au) || au.ValueKind != JsonValueKind.String)
107 return;
108 var u = au.GetString();
109 if (!string.IsNullOrWhiteSpace(u))
110 m.ApplicationUrls = u.Trim();
111 }
112
113 private static void ApplyLaunchUrl(JsonElement el, LaunchProfileModel m)
114 {
115 if (!el.TryGetProperty("launchUrl", out var lurl) || lurl.ValueKind != JsonValueKind.String)
116 return;
117 var s = lurl.GetString();
118 if (!string.IsNullOrWhiteSpace(s))
119 m.LaunchUrl = s.Trim();
120 }
121
122 private static void ApplyLaunchBrowserFlag(JsonElement el, LaunchProfileModel m)
123 {
124 if (!el.TryGetProperty("launchBrowser", out var lb) ||
125 (lb.ValueKind != JsonValueKind.True && lb.ValueKind != JsonValueKind.False))
126 return;
127 m.LaunchBrowser = lb.GetBoolean();
128 }
129
130 private static void ApplyEnvironmentVariables(JsonElement el, LaunchProfileModel m)
131 {
132 if (!el.TryGetProperty("environmentVariables", out var ev) || ev.ValueKind != JsonValueKind.Object)
133 return;
134 foreach (var v in ev.EnumerateObject())
135 {
136 if (string.IsNullOrEmpty(v.Name))
137 continue;
138 var val = v.Value.ValueKind == JsonValueKind.String
139 ? (v.Value.GetString() ?? "")
140 : v.Value.ToString();
141 m.Env![v.Name] = val;
142 }
143 }
144
145 private static void ApplyKestrelBrowserHeuristic(LaunchProfileModel m)
146 {
147 if (m.LaunchBrowser is not true && ApplicationUrlsSuggestKestrelListener(m.ApplicationUrls))
148 m.LaunchBrowser = true;
149 }
150
151 /// <summary>Любой сегмент в <c>applicationUrl</c> (через <c>;</c> как у ASPNETCORE_URLS) с префиксом http(s).</summary>
152 internal static bool ApplicationUrlsSuggestKestrelListener(string? applicationUrls)
153 {
154 if (string.IsNullOrWhiteSpace(applicationUrls))
155 return false;
156 foreach (var part in applicationUrls.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
157 {
158 if (part.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
159 part.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
160 return true;
161 }
162
163 return false;
164 }
165}
166
View only · write via MCP/CIDE