@ship-ui/core 0.18.13 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/mcp/components.json +57 -34
- package/bin/ship-fg-scanner +0 -0
- package/bin/ship-fg.mjs +3588 -0
- package/bin/ship-fg.ts +1 -2
- package/bin/src/scanner.zig +322 -0
- package/bin/src/ship-fg.ts +101 -183
- package/bin/src/subset.ts +97 -0
- package/bin/src/utilities.js +19 -19
- package/bin/src/utilities.ts +19 -19
- package/fesm2022/ship-ui-core.mjs +518 -331
- package/fesm2022/ship-ui-core.mjs.map +1 -1
- package/package.json +6 -5
- package/snippets/ship-ui.code-snippets +0 -7
- package/styles/components/ship-sidenav.scss +1 -1
- package/styles/components/ship-sortable.scss +13 -22
- package/types/ship-ui-core.d.ts +78 -46
- package/bin/ship-fg-node +0 -40
- package/bin/src/ship-fg-node.js +0 -373
package/bin/ship-fg.ts
CHANGED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
|
|
3
|
+
const IconProperties = struct {
|
|
4
|
+
ligatures: ?[]const u8 = null,
|
|
5
|
+
name: ?[]const u8 = null,
|
|
6
|
+
code: usize = 0,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const IconEntry = struct {
|
|
10
|
+
properties: IconProperties,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const SelectionJson = struct {
|
|
14
|
+
icons: []IconEntry,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const PackageJson = struct {
|
|
18
|
+
libraryIcons: ?[][]const u8 = null,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
pub fn main() !void {
|
|
22
|
+
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
23
|
+
defer arena.deinit();
|
|
24
|
+
const alloc = arena.allocator();
|
|
25
|
+
|
|
26
|
+
var arg_it = try std.process.argsWithAllocator(alloc);
|
|
27
|
+
defer arg_it.deinit();
|
|
28
|
+
|
|
29
|
+
_ = arg_it.skip(); // skip executable name
|
|
30
|
+
|
|
31
|
+
const target_dir = arg_it.next() orelse {
|
|
32
|
+
std.debug.print("Usage: scanner <target_dir> <shipui_dir> <consumer_dir>\n", .{});
|
|
33
|
+
std.process.exit(1);
|
|
34
|
+
};
|
|
35
|
+
const shipui_dir = arg_it.next() orelse {
|
|
36
|
+
std.debug.print("Usage: scanner <target_dir> <shipui_dir> <consumer_dir>\n", .{});
|
|
37
|
+
std.process.exit(1);
|
|
38
|
+
};
|
|
39
|
+
const consumer_dir = arg_it.next() orelse {
|
|
40
|
+
std.debug.print("Usage: scanner <target_dir> <shipui_dir> <consumer_dir>\n", .{});
|
|
41
|
+
std.process.exit(1);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
var unique_icons = std.StringHashMap(void).init(alloc);
|
|
45
|
+
|
|
46
|
+
// 1. Read libraryIcons from package.json
|
|
47
|
+
try readPackageJson(alloc, shipui_dir, &unique_icons);
|
|
48
|
+
|
|
49
|
+
// 2. Scan target_dir
|
|
50
|
+
try scanDirectory(alloc, target_dir, &unique_icons);
|
|
51
|
+
|
|
52
|
+
// 3. Output payload string
|
|
53
|
+
try buildPayload(alloc, consumer_dir, &unique_icons);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fn readPackageJson(alloc: std.mem.Allocator, shipui_dir: []const u8, unique_icons: *std.StringHashMap(void)) !void {
|
|
57
|
+
var cwd = std.fs.cwd();
|
|
58
|
+
const pkg_path = try std.fs.path.join(alloc, &.{ shipui_dir, "package.json" });
|
|
59
|
+
const contents = cwd.readFileAlloc(alloc, pkg_path, 10 * 1024 * 1024) catch return;
|
|
60
|
+
const parsed = std.json.parseFromSlice(PackageJson, alloc, contents, .{ .ignore_unknown_fields = true }) catch return;
|
|
61
|
+
defer parsed.deinit();
|
|
62
|
+
|
|
63
|
+
if (parsed.value.libraryIcons) |icons| {
|
|
64
|
+
for (icons) |icon| {
|
|
65
|
+
try unique_icons.put(try alloc.dupe(u8, icon), {});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fn scanDirectory(alloc: std.mem.Allocator, target_dir: []const u8, unique_icons: *std.StringHashMap(void)) !void {
|
|
71
|
+
var cwd = std.fs.cwd();
|
|
72
|
+
var dir = cwd.openDir(target_dir, .{ .iterate = true }) catch return;
|
|
73
|
+
defer dir.close();
|
|
74
|
+
|
|
75
|
+
var walker = try dir.walk(alloc);
|
|
76
|
+
defer walker.deinit();
|
|
77
|
+
|
|
78
|
+
while (try walker.next()) |entry| {
|
|
79
|
+
if (entry.kind != .file) continue;
|
|
80
|
+
|
|
81
|
+
const is_html = std.mem.endsWith(u8, entry.path, ".html");
|
|
82
|
+
const is_ts = std.mem.endsWith(u8, entry.path, ".ts");
|
|
83
|
+
|
|
84
|
+
if (!is_html and !is_ts) continue;
|
|
85
|
+
if (std.mem.indexOf(u8, entry.path, "node_modules") != null) continue;
|
|
86
|
+
|
|
87
|
+
const contents = dir.readFileAlloc(alloc, entry.path, 100 * 1024 * 1024) catch continue;
|
|
88
|
+
|
|
89
|
+
if (is_html) {
|
|
90
|
+
try scanHtml(alloc, contents, unique_icons);
|
|
91
|
+
}
|
|
92
|
+
if (is_ts) {
|
|
93
|
+
try scanTs(alloc, contents, unique_icons);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fn scanHtml(alloc: std.mem.Allocator, contents: []const u8, unique_icons: *std.StringHashMap(void)) !void {
|
|
99
|
+
var pos: usize = 0;
|
|
100
|
+
while (pos < contents.len) {
|
|
101
|
+
const start_idx = std.mem.indexOfPos(u8, contents, pos, "<sh-icon") orelse break;
|
|
102
|
+
pos = start_idx + "<sh-icon".len;
|
|
103
|
+
|
|
104
|
+
const close_tag_idx = std.mem.indexOfPos(u8, contents, pos, ">") orelse break;
|
|
105
|
+
const tag_body = contents[pos..close_tag_idx];
|
|
106
|
+
const is_self_closing = std.mem.endsWith(u8, tag_body, "/");
|
|
107
|
+
|
|
108
|
+
pos = close_tag_idx + 1;
|
|
109
|
+
|
|
110
|
+
if (std.mem.indexOf(u8, tag_body, "icon=\"")) |icon_start_rel| {
|
|
111
|
+
const attr_start = icon_start_rel + 6;
|
|
112
|
+
if (std.mem.indexOf(u8, tag_body[attr_start..], "\"")) |quote_end_rel| {
|
|
113
|
+
const icon_name = std.mem.trim(u8, tag_body[attr_start .. attr_start + quote_end_rel], " \t\r\n");
|
|
114
|
+
if (icon_name.len > 0 and isValidIcon(icon_name)) {
|
|
115
|
+
try unique_icons.put(try alloc.dupe(u8, icon_name), {});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} else if (std.mem.indexOf(u8, tag_body, "icon='")) |icon_start_rel| {
|
|
119
|
+
const attr_start = icon_start_rel + 6;
|
|
120
|
+
if (std.mem.indexOf(u8, tag_body[attr_start..], "'")) |quote_end_rel| {
|
|
121
|
+
const icon_name = std.mem.trim(u8, tag_body[attr_start .. attr_start + quote_end_rel], " \t\r\n");
|
|
122
|
+
if (icon_name.len > 0 and isValidIcon(icon_name)) {
|
|
123
|
+
try unique_icons.put(try alloc.dupe(u8, icon_name), {});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!is_self_closing) {
|
|
129
|
+
const end_sh_icon = std.mem.indexOfPos(u8, contents, pos, "</sh-icon>") orelse break;
|
|
130
|
+
const inner = contents[pos..end_sh_icon];
|
|
131
|
+
pos = end_sh_icon + "</sh-icon>".len;
|
|
132
|
+
|
|
133
|
+
const trimmed_inner = std.mem.trim(u8, inner, " \t\r\n");
|
|
134
|
+
if (trimmed_inner.len > 0 and isValidIcon(trimmed_inner)) {
|
|
135
|
+
try unique_icons.put(try alloc.dupe(u8, trimmed_inner), {});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
fn scanTs(alloc: std.mem.Allocator, contents: []const u8, unique_icons: *std.StringHashMap(void)) !void {
|
|
142
|
+
var pos: usize = 0;
|
|
143
|
+
while (pos < contents.len) {
|
|
144
|
+
const start_idx = std.mem.indexOfPos(u8, contents, pos, "shicon:") orelse break;
|
|
145
|
+
pos = start_idx + "shicon:".len;
|
|
146
|
+
|
|
147
|
+
var end_idx: usize = pos;
|
|
148
|
+
while (end_idx < contents.len and contents[end_idx] != '\'' and contents[end_idx] != '"') {
|
|
149
|
+
end_idx += 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const icon_name = contents[pos..end_idx];
|
|
153
|
+
if (icon_name.len > 0 and isValidIcon(icon_name)) {
|
|
154
|
+
try unique_icons.put(try alloc.dupe(u8, icon_name), {});
|
|
155
|
+
}
|
|
156
|
+
pos = end_idx;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
fn isValidIcon(s: []const u8) bool {
|
|
161
|
+
for (s) |c| {
|
|
162
|
+
if ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or c == '-') {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const GroupArray = std.ArrayListUnmanaged([2][]const u8);
|
|
171
|
+
|
|
172
|
+
const GroupedIcons = struct {
|
|
173
|
+
bold: GroupArray,
|
|
174
|
+
thin: GroupArray,
|
|
175
|
+
light: GroupArray,
|
|
176
|
+
fill: GroupArray,
|
|
177
|
+
regular: GroupArray,
|
|
178
|
+
duotone: GroupArray,
|
|
179
|
+
text: GroupArray,
|
|
180
|
+
missing: std.ArrayListUnmanaged([]const u8),
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const WriteContext = struct {
|
|
184
|
+
list: *std.ArrayListUnmanaged(u8),
|
|
185
|
+
alloc: std.mem.Allocator,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
fn anyWriteFn(ctx: *const anyopaque, bytes: []const u8) anyerror!usize {
|
|
189
|
+
const context: *const WriteContext = @ptrCast(@alignCast(ctx));
|
|
190
|
+
try context.list.appendSlice(context.alloc, bytes);
|
|
191
|
+
return bytes.len;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fn buildPayload(alloc: std.mem.Allocator, consumer_dir: []const u8, unique_icons: *std.StringHashMap(void)) !void {
|
|
195
|
+
var cwd = std.fs.cwd();
|
|
196
|
+
|
|
197
|
+
const variants = [_][]const u8{ "bold", "thin", "light", "fill", "regular", "duotone" };
|
|
198
|
+
var glyph_maps = std.StringHashMap([2][]const u8).init(alloc);
|
|
199
|
+
|
|
200
|
+
for (variants) |variant| {
|
|
201
|
+
const sel_path = try std.fs.path.join(alloc, &.{ consumer_dir, "node_modules", "@phosphor-icons", "web", "src", variant, "selection.json" });
|
|
202
|
+
const contents = cwd.readFileAlloc(alloc, sel_path, 10 * 1024 * 1024) catch continue;
|
|
203
|
+
const parsed = std.json.parseFromSlice(SelectionJson, alloc, contents, .{ .ignore_unknown_fields = true }) catch continue;
|
|
204
|
+
defer parsed.deinit();
|
|
205
|
+
|
|
206
|
+
const is_duotone = std.mem.eql(u8, variant, "duotone");
|
|
207
|
+
|
|
208
|
+
for (parsed.value.icons) |icon| {
|
|
209
|
+
var hex_buf = try alloc.alloc(u8, 16);
|
|
210
|
+
const hex_len = try std.fmt.bufPrint(hex_buf, "{x}", .{icon.properties.code});
|
|
211
|
+
const valid_hex = hex_buf[0..hex_len.len];
|
|
212
|
+
|
|
213
|
+
var unicode_buf = try alloc.alloc(u8, 16);
|
|
214
|
+
const unicode_len = try std.fmt.bufPrint(unicode_buf, "U+{s}", .{valid_hex});
|
|
215
|
+
const final_unicode = unicode_buf[0..unicode_len.len];
|
|
216
|
+
|
|
217
|
+
// Convert character code directly into UTF-8 slice
|
|
218
|
+
var char_buf = try alloc.alloc(u8, 4);
|
|
219
|
+
const char_len = std.unicode.utf8Encode(@as(u21, @intCast(icon.properties.code)), char_buf) catch continue;
|
|
220
|
+
const final_glyph = char_buf[0..char_len];
|
|
221
|
+
|
|
222
|
+
var glyph_name: []const u8 = "";
|
|
223
|
+
if (is_duotone) {
|
|
224
|
+
if (icon.properties.name) |n| glyph_name = n;
|
|
225
|
+
} else {
|
|
226
|
+
if (icon.properties.ligatures) |l| glyph_name = l;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (glyph_name.len == 0) continue;
|
|
230
|
+
|
|
231
|
+
var val: [2][]const u8 = undefined;
|
|
232
|
+
val[0] = try alloc.dupe(u8, final_glyph);
|
|
233
|
+
val[1] = final_unicode;
|
|
234
|
+
|
|
235
|
+
try glyph_maps.put(try alloc.dupe(u8, glyph_name), val);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
var grouped_icons = GroupedIcons{
|
|
240
|
+
.bold = .{},
|
|
241
|
+
.thin = .{},
|
|
242
|
+
.light = .{},
|
|
243
|
+
.fill = .{},
|
|
244
|
+
.regular = .{},
|
|
245
|
+
.duotone = .{},
|
|
246
|
+
.text = .{},
|
|
247
|
+
.missing = .{},
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
var it = unique_icons.keyIterator();
|
|
251
|
+
while (it.next()) |key_ptr| {
|
|
252
|
+
const icon = key_ptr.*;
|
|
253
|
+
const bold = std.mem.endsWith(u8, icon, "-bold");
|
|
254
|
+
const thin = std.mem.endsWith(u8, icon, "-thin");
|
|
255
|
+
const light = std.mem.endsWith(u8, icon, "-light");
|
|
256
|
+
const fill = std.mem.endsWith(u8, icon, "-fill");
|
|
257
|
+
const duotone = std.mem.endsWith(u8, icon, "-duotone");
|
|
258
|
+
const regular = !bold and !thin and !light and !fill and !duotone;
|
|
259
|
+
|
|
260
|
+
if (glyph_maps.get(icon)) |glyph| {
|
|
261
|
+
var tuple1: [2][]const u8 = undefined;
|
|
262
|
+
tuple1[0] = icon;
|
|
263
|
+
tuple1[1] = "";
|
|
264
|
+
|
|
265
|
+
if (bold) {
|
|
266
|
+
try grouped_icons.bold.append(alloc, tuple1);
|
|
267
|
+
try grouped_icons.bold.append(alloc, glyph);
|
|
268
|
+
} else if (thin) {
|
|
269
|
+
try grouped_icons.thin.append(alloc, tuple1);
|
|
270
|
+
try grouped_icons.thin.append(alloc, glyph);
|
|
271
|
+
} else if (light) {
|
|
272
|
+
try grouped_icons.light.append(alloc, tuple1);
|
|
273
|
+
try grouped_icons.light.append(alloc, glyph);
|
|
274
|
+
} else if (fill) {
|
|
275
|
+
try grouped_icons.fill.append(alloc, tuple1);
|
|
276
|
+
try grouped_icons.fill.append(alloc, glyph);
|
|
277
|
+
} else if (duotone) {
|
|
278
|
+
try grouped_icons.duotone.append(alloc, tuple1);
|
|
279
|
+
try grouped_icons.duotone.append(alloc, glyph);
|
|
280
|
+
} else if (regular) {
|
|
281
|
+
try grouped_icons.regular.append(alloc, tuple1);
|
|
282
|
+
try grouped_icons.regular.append(alloc, glyph);
|
|
283
|
+
}
|
|
284
|
+
} else {
|
|
285
|
+
try grouped_icons.missing.append(alloc, icon);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
var string = std.ArrayListUnmanaged(u8){};
|
|
290
|
+
var w = string.writer(alloc);
|
|
291
|
+
|
|
292
|
+
try w.writeAll("{");
|
|
293
|
+
|
|
294
|
+
inline for (.{ "bold", "thin", "light", "fill", "regular", "duotone" }, 0..) |variant, i| {
|
|
295
|
+
if (i > 0) try w.writeAll(",");
|
|
296
|
+
try w.print("\"{s}\":[", .{variant});
|
|
297
|
+
|
|
298
|
+
var group_list: GroupArray = undefined;
|
|
299
|
+
if (std.mem.eql(u8, variant, "bold")) group_list = grouped_icons.bold;
|
|
300
|
+
if (std.mem.eql(u8, variant, "thin")) group_list = grouped_icons.thin;
|
|
301
|
+
if (std.mem.eql(u8, variant, "light")) group_list = grouped_icons.light;
|
|
302
|
+
if (std.mem.eql(u8, variant, "fill")) group_list = grouped_icons.fill;
|
|
303
|
+
if (std.mem.eql(u8, variant, "regular")) group_list = grouped_icons.regular;
|
|
304
|
+
if (std.mem.eql(u8, variant, "duotone")) group_list = grouped_icons.duotone;
|
|
305
|
+
|
|
306
|
+
for (group_list.items, 0..) |item, j| {
|
|
307
|
+
if (j > 0) try w.writeAll(",");
|
|
308
|
+
try w.print("[\"{s}\",\"{s}\"]", .{ item[0], item[1] });
|
|
309
|
+
}
|
|
310
|
+
try w.writeAll("]");
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
try w.writeAll(",\"missing\":[");
|
|
314
|
+
for (grouped_icons.missing.items, 0..) |item, i| {
|
|
315
|
+
if (i > 0) try w.writeAll(",");
|
|
316
|
+
try w.print("\"{s}\"", .{item});
|
|
317
|
+
}
|
|
318
|
+
try w.writeAll("]}");
|
|
319
|
+
|
|
320
|
+
var stdout = std.fs.File.stdout();
|
|
321
|
+
try stdout.writeAll(string.items);
|
|
322
|
+
}
|