mosaic-headless 1.29.2 → 1.30.0

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.
@@ -0,0 +1,277 @@
1
+ """The design-system lab: what a token is, and why this install does not use variants.
2
+
3
+ Mosaic has three styling layers and most of a site belongs in the top two. The
4
+ bottom one - a style on one node - is what every page builder gives you and is the
5
+ wrong place to build a site. Above it:
6
+
7
+ - **collection variables**, which compile to real CSS custom properties on `:root`
8
+ and can be pointed at from 39 of the style properties;
9
+ - **variants**, which style every element of a KIND once, site-wide, from a catalog
10
+ of 156 entries you do not create but commit styles for.
11
+
12
+ This page shows the first layer working on itself: every swatch's colour IS the
13
+ token it names, read through `{"token": "--mk-…"}`, so the page cannot describe a
14
+ value it is not also displaying. Change the token and the page changes with it.
15
+
16
+ The second layer gets the honest treatment instead of a demonstration, because
17
+ **variants are theme-global and this install carries two brands on one theme**.
18
+ Committing a Heading 2 style here would restyle the other brand's pages too. That
19
+ constraint is worth more to a reader than a staged example would be, so the page
20
+ states it, and shows the part that IS observable: every element already carries its
21
+ variant's class name (`m-heading-2`, `m-button`), which is what a variant rule
22
+ would hook onto.
23
+
24
+ The token-referencable list is read from `data/style-properties.csv` at build time
25
+ rather than typed, so the page cannot drift from the table that was extracted from
26
+ the plugin.
27
+ """
28
+ import csv
29
+ import io
30
+ import os
31
+
32
+ from _moksa import (CJK, DISPLAY, MONO, RULE, T, TOKENS, bp, box, grid, ml, mono,
33
+ section, wrap)
34
+
35
+ _HERE = os.path.dirname(os.path.abspath(__file__))
36
+
37
+
38
+ def tokenable():
39
+ path = os.path.join(_HERE, "..", "data", "style-properties.csv")
40
+ with io.open(path, encoding="utf-8") as fh:
41
+ return [r["property"] for r in csv.DictReader(fh) if r["tokenable"]]
42
+
43
+
44
+ TOKENABLE = tokenable()
45
+
46
+ # What each token is actually for. The catalog is the point of a token system:
47
+ # a name that says WHERE it is used outlives a name that says what colour it is.
48
+ ROLES = {
49
+ "--mk-ink": "正文和標題的字色",
50
+ "--mk-paper": "頁面底色",
51
+ "--mk-panel": "次要區塊的底色",
52
+ "--mk-accent": "強調:按鈕、重點標記",
53
+ "--mk-accent-ink": "強調色的深版,用在小字",
54
+ "--mk-ai": "藍摺:入口動畫的色塊",
55
+ "--mk-muted": "次要文字",
56
+ "--mk-rule": "分隔線",
57
+ "--mk-faint": "標籤、頁碁、最淺的那層字",
58
+ }
59
+
60
+
61
+ def swatch(name, meta):
62
+ value = meta.get("value", "")
63
+ dark = name in ("--mk-ink", "--mk-ai", "--mk-accent-ink", "--mk-muted",
64
+ "--mk-faint")
65
+ return box("tk-sw-" + name.strip("-"), {
66
+ "display": "flex", "flexDirection": "column", "rowGap": "0px",
67
+ "customDeclarations": "border:1px solid " + RULE + ";"}, [
68
+ # the colour field IS the token - nothing here restates the hex by hand
69
+ box("tk-chip-" + name.strip("-"), {
70
+ "height": "88px", "backgroundColor": {"token": name}}, []),
71
+ box("tk-meta-" + name.strip("-"), {
72
+ "paddingTop": "14px", "paddingBottom": "16px",
73
+ "paddingLeft": "14px", "paddingRight": "14px"}, [
74
+ mono(name, size="11px", color="--mk-ink", track="0.08em"),
75
+ mono(value, size="10px", color="--mk-faint", track="0.04em",
76
+ marginTop="6px"),
77
+ T("p", ROLES.get(name, ""), fontFamily=CJK, fontSize="12px",
78
+ lineHeight="1.7", color={"token": "--mk-muted"}, marginTop="8px"),
79
+ ]),
80
+ ])
81
+
82
+
83
+ HEAD = section("tk-head", [wrap("tk-head-w", [
84
+ box("tk-head-pad", {"paddingTop": "116px"}, [], _m={"paddingTop": "26px"}),
85
+ mono("DESIGN SYSTEM / 設計系統", size="11px",
86
+ color="--mk-faint", track="0.26em"),
87
+ ml("h1", "一個值,\n改一次。",
88
+ fontFamily=DISPLAY, fontWeight="600", fontSize="62px", lineHeight="1.06",
89
+ letterSpacing="0.005em", color={"token": "--mk-ink"}, marginTop="18px",
90
+ _t={"fontSize": "48px"}, _m={"fontSize": "34px"}),
91
+ T("p",
92
+ "頁面編輯器通常只給你最底層:"
93
+ "把樣式寫在單一節點上。"
94
+ "那是起點,不是蓋網站的地方。"
95
+ "Mosaic 在它之上還有兩層:"
96
+ "集合變數(設計代幣)跟 variant(元件類別)。",
97
+ fontFamily=CJK, fontSize="16px", lineHeight="1.95",
98
+ color={"token": "--mk-ink"}, marginTop="22px", maxWidth="660px"),
99
+ T("p",
100
+ "下面每一塊色的背景都是它自己那個代幣——"
101
+ "不是手寫的色碼。所以這頁沒辦法描述一個"
102
+ "它沒有同時顯示出來的值。",
103
+ fontFamily=CJK, fontSize="14px", lineHeight="1.9",
104
+ color={"token": "--mk-muted"}, marginTop="12px", maxWidth="660px"),
105
+ box("tk-head-gap", {"paddingTop": "46px"}, []),
106
+ ])], pad_y="0px")
107
+
108
+ SWATCHES = section("tk-sw", [wrap("tk-sw-w", [
109
+ grid("tk-sw-grid", 3, "18px",
110
+ [swatch(n, m) for n, m in TOKENS.items()], tcols=2, mcols=1),
111
+ box("tk-sw-foot", {"paddingTop": "64px"}, []),
112
+ ])], bg="--mk-panel", pad_y="0px")
113
+
114
+
115
+ def step(num, title, body, codes):
116
+ return box("tk-step-%d" % num, {
117
+ "minWidth": "0px",
118
+ "display": "flex", "flexDirection": "column", "rowGap": "0px"}, [
119
+ mono("STEP %d" % num, size="10px", color="--mk-accent-ink", track="0.24em"),
120
+ ml("h3", title, fontFamily=DISPLAY, fontWeight="600", fontSize="18px",
121
+ lineHeight="1.3", color={"token": "--mk-ink"}, marginTop="10px"),
122
+ T("p", body, fontFamily=CJK, fontSize="13px", lineHeight="1.85",
123
+ color={"token": "--mk-muted"}, marginTop="8px"),
124
+ box("tk-step-code-%d" % num, {
125
+ "minWidth": "0px", "marginTop": "14px", "paddingTop": "12px", "paddingBottom": "12px",
126
+ "paddingLeft": "14px", "paddingRight": "14px",
127
+ "backgroundColor": {"token": "--mk-ink"},
128
+ "overflow": "auto"},
129
+ [mono(c, size="10px", color="rgb(250,250,247)", track="0.02em",
130
+ whiteSpace="pre", marginTop="4px" if i else "0px")
131
+ for i, c in enumerate(codes)]),
132
+ ])
133
+
134
+
135
+ PIPELINE = section("tk-pipe", [wrap("tk-pipe-w", [
136
+ box("tk-pipe-pad", {"paddingTop": "62px"}, []),
137
+ mono("HOW IT COMPILES", size="10px", color="--mk-faint", track="0.26em"),
138
+ ml("h2", "從資料到 CSS,三步。",
139
+ fontFamily=DISPLAY, fontWeight="600", fontSize="32px", lineHeight="1.15",
140
+ letterSpacing="0.005em", color={"token": "--mk-ink"}, marginTop="14px",
141
+ _m={"fontSize": "24px"}),
142
+ box("tk-pipe-gap", {"paddingTop": "26px"}, []),
143
+ grid("tk-pipe-grid", 3, "20px", [
144
+ step(1, "宣告一個代幣",
145
+ "一個 collection variable。customProperty 是逐字輸出的,"
146
+ "所以前面那兩個減號必須自己寫。",
147
+ ['{"name": "Ink",',
148
+ ' "type": "color",',
149
+ ' "customProperty": "--mk-ink",',
150
+ ' "skinsData": {skin: {mode:',
151
+ ' {"value": "rgb(22,24,28)"}}}}']),
152
+ step(2, "在節點上指向它",
153
+ "39 個樣式屬性接得住代幣引用,"
154
+ "寫法是一把鑰匙的物件。",
155
+ ['"color": {"token": "--mk-ink"}',
156
+ '',
157
+ '# 實際傳的是變數 uuid:',
158
+ '"color": {"var": "<uuid>"}']),
159
+ step(3, "輸出的 CSS",
160
+ "代幣落在 :root,節點的規則用 var() 指過去。"
161
+ "改一個值,所有指過來的地方跟著變。",
162
+ [':root{',
163
+ ' --mk-ink: rgb(22, 24, 28);',
164
+ '}',
165
+ '._d{ color: var(--mk-ink) }']),
166
+ ], tcols=1, mcols=1),
167
+ box("tk-pipe-foot", {"paddingTop": "60px"}, []),
168
+ ])], pad_y="0px")
169
+
170
+ PROPS = section("tk-props", [wrap("tk-props-w", [
171
+ box("tk-props-pad", {"paddingTop": "60px"}, []),
172
+ mono("TOKENABLE", size="10px", color="--mk-faint", track="0.26em"),
173
+ ml("h2", "哪些屬性接得住代幣?%d 個。" % len(TOKENABLE),
174
+ fontFamily=DISPLAY, fontWeight="600", fontSize="32px", lineHeight="1.15",
175
+ letterSpacing="0.005em", color={"token": "--mk-ink"}, marginTop="14px",
176
+ _m={"fontSize": "23px"}),
177
+ T("p",
178
+ "不是每個樣式屬性都行。"
179
+ "這份清單是從外掛原始碼萃取出來的表"
180
+ "(data/style-properties.csv)在建置時讀出來的,"
181
+ "不是手打的——所以它不會跟表脱節。",
182
+ fontFamily=CJK, fontSize="14px", lineHeight="1.9",
183
+ color={"token": "--mk-muted"}, marginTop="10px", maxWidth="620px"),
184
+ box("tk-props-list", {
185
+ "marginTop": "22px", "display": "flex", "flexWrap": "wrap",
186
+ "columnGap": "8px", "rowGap": "8px"},
187
+ [box("tk-prop-%d" % i, {
188
+ "paddingTop": "6px", "paddingBottom": "6px",
189
+ "paddingLeft": "10px", "paddingRight": "10px",
190
+ "customDeclarations": "border:1px solid " + RULE + ";"},
191
+ [mono(p, size="10px", color="--mk-ink", track="0.04em")])
192
+ for i, p in enumerate(TOKENABLE)]),
193
+ box("tk-props-foot", {"paddingTop": "64px"}, []),
194
+ ])], bg="--mk-panel", pad_y="0px")
195
+
196
+ VARIANTS = section("tk-var", [wrap("tk-var-w", [
197
+ box("tk-var-pad", {"paddingTop": "60px"}, []),
198
+ mono("VARIANTS", size="10px", color="--mk-faint", track="0.26em"),
199
+ ml("h2", "第二層:把所有 h2 一次改完。",
200
+ fontFamily=DISPLAY, fontWeight="600", fontSize="32px", lineHeight="1.15",
201
+ letterSpacing="0.005em", color={"token": "--mk-ink"}, marginTop="14px",
202
+ _m={"fontSize": "24px"}),
203
+ T("p",
204
+ "Mosaic 內建一份 156 筆的 variant 目錄(Heading 2、"
205
+ "Button、Section、Body……)。你不創建 variant,"
206
+ "你是用目錄裡現成的 ID 提交一組樣式,"
207
+ "輸出就是一條全站規則:"
208
+ "h2,.m-heading-2{…}。排版節奏和元件預設屬於這一層。",
209
+ fontFamily=CJK, fontSize="15px", lineHeight="1.95",
210
+ color={"token": "--mk-ink"}, marginTop="14px", maxWidth="680px"),
211
+ box("tk-var-note", {
212
+ "marginTop": "22px", "paddingTop": "20px", "paddingBottom": "20px",
213
+ "paddingLeft": "22px", "paddingRight": "22px",
214
+ "backgroundColor": {"token": "--mk-paper"},
215
+ "customDeclarations": "border-left:3px solid rgb(255,90,54);"}, [
216
+ mono("為什麼這個站沒用這一層",
217
+ size="10px", color="--mk-accent-ink", track="0.2em"),
218
+ T("p",
219
+ "因為 variant 是主題全域的,"
220
+ "而這個安裝同時挂著兩個品牌。"
221
+ "在這裡提交一條 Heading 2 的規則,"
222
+ "另一個品牌的頁面也會跟著變——"
223
+ "所以這個站把排版烘進節點裡。"
224
+ "一個主題一個品牌的時候,"
225
+ "排版應該全部放在 variant 層,"
226
+ "而不是像這裡一樣每個節點都寫一遍。",
227
+ fontFamily=CJK, fontSize="13px", lineHeight="1.9",
228
+ color={"token": "--mk-ink"}, marginTop="10px", maxWidth="680px"),
229
+ ]),
230
+ T("p",
231
+ "不過有一半是看得到的:"
232
+ "每個元素本來就帶著自己 variant 的類別名稱。"
233
+ "這頁的標題都有 m-heading-2 / m-heading-3,"
234
+ "按鈕都有 m-button——那就是 variant 規則會挖上去的地方。",
235
+ fontFamily=CJK, fontSize="14px", lineHeight="1.9",
236
+ color={"token": "--mk-muted"}, marginTop="18px", maxWidth="680px"),
237
+ box("tk-var-foot", {"paddingTop": "60px"}, []),
238
+ ])], pad_y="0px")
239
+
240
+
241
+ def trap(num, title, body):
242
+ return box("tk-trap-%d" % num, {}, [
243
+ mono("TRAP %02d" % num, size="10px", color="--mk-accent-ink", track="0.22em"),
244
+ ml("h3", title, fontFamily=DISPLAY, fontWeight="600", fontSize="17px",
245
+ lineHeight="1.3", color={"token": "--mk-ink"}, marginTop="10px"),
246
+ T("p", body, fontFamily=CJK, fontSize="13px", lineHeight="1.85",
247
+ color={"token": "--mk-muted"}, marginTop="8px"),
248
+ ])
249
+
250
+
251
+ TRAPS = section("tk-traps", [wrap("tk-traps-w", [
252
+ box("tk-traps-inner", {
253
+ "paddingTop": "54px", "paddingBottom": "84px",
254
+ "customDeclarations": "border-top:1px solid " + RULE + ";"}, [
255
+ mono("踩過的坑", size="10px", color="--mk-faint", track="0.26em"),
256
+ box("tk-traps-gap", {"paddingTop": "20px"}, []),
257
+ grid("tk-traps-grid", 3, "22px", [
258
+ trap(1, "customProperty 要自帶兩個減號",
259
+ "它是逐字輸出的。寫 brand 會得到 "
260
+ ":root{brand: …} 和 background-color:var(brand),"
261
+ "兩個都不是合法 CSS,而且沒有任何錯誤訊息。"),
262
+ trap(2, "skinsData 形狀寫錯不會失敗",
263
+ "變數照樣建立,:root 照樣有宣告,"
264
+ "但值是子工廂的預設——顏色是 #FFF。"
265
+ "所以代幣變白色的時候,"
266
+ "意思是值根本沒傳到。"),
267
+ trap(3, "改值會留下舊的",
268
+ "改一個代幣的值,舊的宣告不會自己消失:"
269
+ ":root 裡會同時出現兩行。"
270
+ "要用 status:\"delete\" 明確刪掉,"
271
+ "清空節點是不會動到它的。"),
272
+ ], tcols=1, mcols=1),
273
+ ]),
274
+ ])], pad_y="0px")
275
+
276
+ TREE = {"type": "div", "data": {"attrID": "tk-page"},
277
+ "children": [HEAD, SWATCHES, PIPELINE, PROPS, VARIANTS, TRAPS]}
@@ -39,6 +39,7 @@ refused outright unless --force, because committing one is how you get a 200-res
39
39
  page that serves a 54-byte error string. After committing, the page is fetched and its
40
40
  size checked - the only reliable signal that the write actually worked.
41
41
  """
42
+ import mosaic_config
42
43
  import argparse
43
44
  import csv
44
45
  import json
@@ -559,11 +560,12 @@ def build(client, cfg, spec, force):
559
560
 
560
561
  if __name__ == "__main__":
561
562
  ap = argparse.ArgumentParser()
562
- ap.add_argument("--config", required=True)
563
+ ap.add_argument("--config",
564
+ help="JSON with base/cookie/nonce; omit it and the MOSAIC_SITE_URL / MOSAIC_REST_COOKIE / MOSAIC_REST_NONCE environment variables supply them instead")
563
565
  ap.add_argument("--spec", required=True)
564
566
  ap.add_argument("--force", action="store_true", help="skip the placement/outcome checks")
565
567
  a = ap.parse_args()
566
- cfg = json.load(open(a.config, encoding="utf-8"))
568
+ cfg = mosaic_config.load(a.config)
567
569
  spec = json.load(open(a.spec, encoding="utf-8"))
568
570
  client = Client(cfg)
569
571
  build(client, cfg, spec, a.force)
@@ -28,6 +28,7 @@ a theme.
28
28
  Pages are bound to WordPress posts by slug, so the posts must already exist. The theme
29
29
  is committed once, before anything references a token.
30
30
  """
31
+ import mosaic_config
31
32
  import argparse
32
33
  import json
33
34
  import os
@@ -374,10 +375,11 @@ def cache_check(url, _fresh_bytes=None):
374
375
 
375
376
  def main():
376
377
  ap = argparse.ArgumentParser()
377
- ap.add_argument("--config", required=True)
378
+ ap.add_argument("--config",
379
+ help="JSON with base/cookie/nonce; omit it and the MOSAIC_SITE_URL / MOSAIC_REST_COOKIE / MOSAIC_REST_NONCE environment variables supply them instead")
378
380
  ap.add_argument("--site", required=True)
379
381
  a = ap.parse_args()
380
- cfg = json.load(open(a.config, encoding="utf-8"))
382
+ cfg = mosaic_config.load(a.config)
381
383
  site = json.load(open(a.site, encoding="utf-8"))
382
384
  client = Client(cfg)
383
385
  surface = Surface()
@@ -26,6 +26,7 @@ Always shows the diff and asks, unless --yes. A style copy is not reversible fro
26
26
  this tool - the previous value is gone once the commit lands - so it prints the
27
27
  before and after of every target first.
28
28
  """
29
+ import mosaic_config
29
30
  import argparse
30
31
  import json
31
32
  import os
@@ -96,7 +97,8 @@ def merge(dst, src):
96
97
 
97
98
  def main():
98
99
  ap = argparse.ArgumentParser()
99
- ap.add_argument("--config", required=True)
100
+ ap.add_argument("--config",
101
+ help="JSON with base/cookie/nonce; omit it and the MOSAIC_SITE_URL / MOSAIC_REST_COOKIE / MOSAIC_REST_NONCE environment variables supply them instead")
100
102
  ap.add_argument("--from", dest="src", required=True, help="source attrID")
101
103
  ap.add_argument("--to", help="comma-separated target attrIDs")
102
104
  ap.add_argument("--to-prefix", help="every attrID starting with this, except the source")
@@ -107,7 +109,7 @@ def main():
107
109
  ap.add_argument("--yes", action="store_true")
108
110
  a = ap.parse_args()
109
111
 
110
- cfg = json.load(open(a.config, encoding="utf-8"))
112
+ cfg = mosaic_config.load(a.config)
111
113
  client = Client(cfg)
112
114
 
113
115
  if a.to_prefix:
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env python3
2
+ """Where a tool gets the site URL and the REST session, in one place.
3
+
4
+ Every tool here talks to ONE WordPress site over its REST API, and needs three
5
+ things to do it: the site's base URL, a logged-in cookie, and a matching
6
+ `wp_rest` nonce. `tools/mint_session.php` prints all three from WP-CLI on the
7
+ server; nothing here ever goes looking for them.
8
+
9
+ Two ways to supply them, and the environment wins:
10
+
11
+ MOSAIC_SITE_URL https://example.com
12
+ MOSAIC_REST_COOKIE wordpress_logged_in_…=…
13
+ MOSAIC_REST_NONCE 0a0326da96
14
+ MOSAIC_THEME_ID the theme uuid, when a tool needs one
15
+
16
+ or a JSON file passed with `--config`:
17
+
18
+ {"base": "https://example.com", "cookie": "…", "nonce": "…",
19
+ "themeID": "…", "version": "1.0.9"}
20
+
21
+ The environment path exists so the credential never has to be written to disk at
22
+ all. When this skill is installed as a plugin, its `userConfig` prompts for these
23
+ values - the cookie and the nonce declared `sensitive: true`, so Claude Code masks
24
+ them and keeps them in the platform's credential store rather than in
25
+ `settings.json` - and the user exports them for the tool run. A value is asked
26
+ FOR, never read from wherever the user happened to leave it.
27
+
28
+ The file is still supported because a sweep over a scratch site is easier to
29
+ repeat from one, and because `--config` is what every example in this skill shows.
30
+ Nothing in either path is sent anywhere but the site named in `base`.
31
+ """
32
+ import json
33
+ import os
34
+
35
+ ENV = {
36
+ "base": "MOSAIC_SITE_URL",
37
+ "cookie": "MOSAIC_REST_COOKIE",
38
+ "nonce": "MOSAIC_REST_NONCE",
39
+ "themeID": "MOSAIC_THEME_ID",
40
+ "version": "MOSAIC_VERSION",
41
+ }
42
+
43
+ REQUIRED = ("base", "cookie", "nonce")
44
+
45
+
46
+ def load(path=None, require=REQUIRED):
47
+ """Return the config dict, environment over file.
48
+
49
+ `path` may be None, in which case the environment has to carry everything.
50
+ A missing required key is a hard error naming both ways to supply it, rather
51
+ than a KeyError five frames deep in a request.
52
+ """
53
+ cfg = {}
54
+ if path:
55
+ with open(path, encoding="utf-8") as fh:
56
+ cfg = json.load(fh)
57
+ for key, var in ENV.items():
58
+ value = os.environ.get(var)
59
+ if value:
60
+ cfg[key] = value
61
+
62
+ missing = [k for k in (require or ()) if not cfg.get(k)]
63
+ if missing:
64
+ raise SystemExit(
65
+ "missing %s.\n"
66
+ "Supply them in the environment (%s) or in a JSON file passed with "
67
+ "--config.\n"
68
+ "`php tools/mint_session.php` run through WP-CLI on the site prints a "
69
+ "cookie and a matching nonce."
70
+ % (", ".join(missing), ", ".join(ENV[k] for k in missing)))
71
+ return cfg
package/tools/probe.py CHANGED
@@ -31,6 +31,7 @@ that "worked" can be checked against what it actually emitted rather than truste
31
31
  COMMIT_5xx PHP fatal during commit
32
32
  BROKE_PAGE committed, and the page stopped rendering
33
33
  """
34
+ import mosaic_config
34
35
  import argparse
35
36
  import csv
36
37
  import json
@@ -135,10 +136,11 @@ def run(client, cfg, cases, out_path):
135
136
 
136
137
  if __name__ == "__main__":
137
138
  ap = argparse.ArgumentParser()
138
- ap.add_argument("--config", required=True)
139
+ ap.add_argument("--config",
140
+ help="JSON with base/cookie/nonce; omit it and the MOSAIC_SITE_URL / MOSAIC_REST_COOKIE / MOSAIC_REST_NONCE environment variables supply them instead")
139
141
  ap.add_argument("--cases", required=True)
140
142
  ap.add_argument("--out")
141
143
  a = ap.parse_args()
142
- cfg = json.load(open(a.config, encoding="utf-8"))
144
+ cfg = mosaic_config.load(a.config)
143
145
  cases = json.load(open(a.cases, encoding="utf-8"))
144
146
  run(Client(cfg), cfg, cases, a.out)
@@ -16,6 +16,7 @@ is only worth writing down next to "and here is what happens when it is not".
16
16
  """
17
17
  from __future__ import annotations
18
18
 
19
+ import mosaic_config
19
20
  import argparse
20
21
  import csv
21
22
  import json
@@ -90,13 +91,14 @@ def place(client, cfg, surface, slug, post, node, label):
90
91
 
91
92
  def main():
92
93
  ap = argparse.ArgumentParser()
93
- ap.add_argument("--config", required=True)
94
+ ap.add_argument("--config",
95
+ help="JSON with base/cookie/nonce; omit it and the MOSAIC_SITE_URL / MOSAIC_REST_COOKIE / MOSAIC_REST_NONCE environment variables supply them instead")
94
96
  ap.add_argument("--slug", required=True)
95
97
  ap.add_argument("--post", type=int, required=True)
96
98
  ap.add_argument("--csv")
97
99
  a = ap.parse_args()
98
100
 
99
- cfg = json.load(open(a.config, encoding="utf-8"))
101
+ cfg = mosaic_config.load(a.config)
100
102
  client = Client(cfg)
101
103
  rows, failed = [], 0
102
104
 
@@ -41,6 +41,7 @@ text in the delivered page - because one instance rendering proves less than two
41
41
  """
42
42
  from __future__ import annotations
43
43
 
44
+ import mosaic_config
44
45
  import argparse
45
46
  import csv
46
47
  import json
@@ -89,13 +90,14 @@ def fetch(url):
89
90
 
90
91
  def main():
91
92
  ap = argparse.ArgumentParser()
92
- ap.add_argument("--config", required=True)
93
+ ap.add_argument("--config",
94
+ help="JSON with base/cookie/nonce; omit it and the MOSAIC_SITE_URL / MOSAIC_REST_COOKIE / MOSAIC_REST_NONCE environment variables supply them instead")
93
95
  ap.add_argument("--post", type=int, required=True)
94
96
  ap.add_argument("--slug", required=True)
95
97
  ap.add_argument("--csv")
96
98
  a = ap.parse_args()
97
99
 
98
- cfg = json.load(open(a.config, encoding="utf-8"))
100
+ cfg = mosaic_config.load(a.config)
99
101
  client, surface = Client(cfg), Surface()
100
102
  rows, failed = [], 0
101
103
 
@@ -47,6 +47,7 @@ Statuses
47
47
  """
48
48
  from __future__ import annotations
49
49
 
50
+ import mosaic_config
50
51
  import argparse
51
52
  import csv
52
53
  import json
@@ -279,7 +280,8 @@ def second_pass(client, master, template, cases):
279
280
 
280
281
  def main():
281
282
  ap = argparse.ArgumentParser()
282
- ap.add_argument("--config", required=True)
283
+ ap.add_argument("--config",
284
+ help="JSON with base/cookie/nonce; omit it and the MOSAIC_SITE_URL / MOSAIC_REST_COOKIE / MOSAIC_REST_NONCE environment variables supply them instead")
283
285
  ap.add_argument("--post", type=int, required=True)
284
286
  ap.add_argument("--slug", required=True)
285
287
  ap.add_argument("--csv")
@@ -287,7 +289,7 @@ def main():
287
289
  help="re-commit the same nodes once more before judging")
288
290
  a = ap.parse_args()
289
291
 
290
- cfg = json.load(open(a.config, encoding="utf-8"))
292
+ cfg = mosaic_config.load(a.config)
291
293
  client, surface = Client(cfg), Surface()
292
294
  cases = candidates()
293
295
 
@@ -42,6 +42,7 @@ as passes would be the blind-spot-scored-as-success failure this file was writte
42
42
  avoid; leaving them as SKIPPED understates the evidence by nearly seven hundred rows.
43
43
  So they get their own status, and it carries the count and the file that holds it.
44
44
  """
45
+ import mosaic_config
45
46
  import argparse
46
47
  import csv
47
48
  import json
@@ -158,12 +159,13 @@ def judge(prop, value, tag):
158
159
 
159
160
  def main():
160
161
  ap = argparse.ArgumentParser()
161
- ap.add_argument("--config", required=True)
162
+ ap.add_argument("--config",
163
+ help="JSON with base/cookie/nonce; omit it and the MOSAIC_SITE_URL / MOSAIC_REST_COOKIE / MOSAIC_REST_NONCE environment variables supply them instead")
162
164
  ap.add_argument("--page", required=True)
163
165
  ap.add_argument("--csv")
164
166
  a = ap.parse_args()
165
167
 
166
- cfg = json.load(open(a.config, encoding="utf-8"))
168
+ cfg = mosaic_config.load(a.config)
167
169
  here = os.path.dirname(os.path.abspath(__file__))
168
170
  props = list(csv.DictReader(
169
171
  open(os.path.join(here, "..", "data", "node-properties.csv"), encoding="utf-8")))
@@ -27,6 +27,7 @@ tree. This costs one page load per type and is the only way the numbers mean any
27
27
  BROKE_PAGE committed, and the page stopped rendering - the silent-failure case
28
28
  REJECTED the commit came back with an `exceptions` body (HTTP 200)
29
29
  """
30
+ import mosaic_config
30
31
  import argparse
31
32
  import csv
32
33
  import json
@@ -406,7 +407,8 @@ def sweep(client, cfg, types, out_path):
406
407
 
407
408
  if __name__ == "__main__":
408
409
  ap = argparse.ArgumentParser()
409
- ap.add_argument("--config", required=True)
410
+ ap.add_argument("--config",
411
+ help="JSON with base/cookie/nonce; omit it and the MOSAIC_SITE_URL / MOSAIC_REST_COOKIE / MOSAIC_REST_NONCE environment variables supply them instead")
410
412
  ap.add_argument("--setup", action="store_true")
411
413
  ap.add_argument("--sweep", action="store_true")
412
414
  ap.add_argument("--types", help="comma-separated subset; overrides --edition")
@@ -416,7 +418,7 @@ if __name__ == "__main__":
416
418
  ap.add_argument("--out", default="../data/node-verification.csv")
417
419
  a = ap.parse_args()
418
420
 
419
- cfg = json.load(open(a.config, encoding="utf-8"))
421
+ cfg = mosaic_config.load(a.config)
420
422
  client = Client(cfg)
421
423
  if a.setup:
422
424
  setup(client, cfg, a.config)
@@ -36,6 +36,7 @@ NO_EFFECT is not a failure. It is the honest answer for a property whose effect
36
36
  probe page cannot show, and it is reported as itself instead of being quietly counted
37
37
  as a pass.
38
38
  """
39
+ import mosaic_config
39
40
  import argparse
40
41
  import csv
41
42
  import json
@@ -206,10 +207,11 @@ def run(client, cfg, jobs, out_path):
206
207
 
207
208
  if __name__ == "__main__":
208
209
  ap = argparse.ArgumentParser()
209
- ap.add_argument("--config", required=True)
210
+ ap.add_argument("--config",
211
+ help="JSON with base/cookie/nonce; omit it and the MOSAIC_SITE_URL / MOSAIC_REST_COOKIE / MOSAIC_REST_NONCE environment variables supply them instead")
210
212
  ap.add_argument("--carriers", default="text,button,div,section,image,icon",
211
213
  help="types used to probe the inherited property set")
212
214
  ap.add_argument("--out", default="../data/property-verification.csv")
213
215
  a = ap.parse_args()
214
- cfg = json.load(open(a.config, encoding="utf-8"))
216
+ cfg = mosaic_config.load(a.config)
215
217
  run(Client(cfg), cfg, load_surface(a.carriers.split(",")), a.out)
@@ -46,6 +46,7 @@ Statuses
46
46
  `SKIPPED` and `DIFFERENT` are never folded into a pass rate. A sweep that scores its
47
47
  own blind spots as successes is the thing this skill exists to argue against.
48
48
  """
49
+ import mosaic_config
49
50
  import argparse
50
51
  import csv
51
52
  import json
@@ -133,12 +134,13 @@ def test_value(row):
133
134
 
134
135
  def main():
135
136
  ap = argparse.ArgumentParser()
136
- ap.add_argument("--config", required=True)
137
+ ap.add_argument("--config",
138
+ help="JSON with base/cookie/nonce; omit it and the MOSAIC_SITE_URL / MOSAIC_REST_COOKIE / MOSAIC_REST_NONCE environment variables supply them instead")
137
139
  ap.add_argument("--page", required=True, help="slug of a page that renders")
138
140
  ap.add_argument("--csv")
139
141
  a = ap.parse_args()
140
142
 
141
- cfg = json.load(open(a.config, encoding="utf-8"))
143
+ cfg = mosaic_config.load(a.config)
142
144
  here = os.path.dirname(os.path.abspath(__file__))
143
145
  props = list(csv.DictReader(
144
146
  open(os.path.join(here, "..", "data", "style-properties.csv"),
@@ -32,6 +32,7 @@ Statuses
32
32
  """
33
33
  from __future__ import annotations
34
34
 
35
+ import mosaic_config
35
36
  import argparse
36
37
  import csv
37
38
  import json
@@ -127,13 +128,14 @@ def norm(text):
127
128
 
128
129
  def main():
129
130
  ap = argparse.ArgumentParser()
130
- ap.add_argument("--config", required=True)
131
+ ap.add_argument("--config",
132
+ help="JSON with base/cookie/nonce; omit it and the MOSAIC_SITE_URL / MOSAIC_REST_COOKIE / MOSAIC_REST_NONCE environment variables supply them instead")
131
133
  ap.add_argument("--post", type=int, required=True)
132
134
  ap.add_argument("--slug", required=True)
133
135
  ap.add_argument("--csv")
134
136
  a = ap.parse_args()
135
137
 
136
- cfg = json.load(open(a.config, encoding="utf-8"))
138
+ cfg = mosaic_config.load(a.config)
137
139
  client, surface = Client(cfg), Surface()
138
140
  cases = plan()
139
141
  testable = [c for c in cases if not c["blocked"] and c["host"]]