mystmd 1.3.6__py3-none-any.whl → 1.3.8__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mystmd
3
- Version: 1.3.6
3
+ Version: 1.3.8
4
4
  Summary: Command line tools for MyST Markdown
5
5
  Project-URL: Homepage, https://github.com/jupyter-book/mystmd
6
6
  Project-URL: Bug Tracker, https://github.com/jupyter-book/mystmd/issues
@@ -21,6 +21,8 @@ Classifier: Programming Language :: Python :: 3.11
21
21
  Classifier: Programming Language :: Python :: 3.12
22
22
  Classifier: Topic :: Scientific/Engineering
23
23
  Requires-Python: >=3.8
24
+ Requires-Dist: platformdirs~=4.2.2
25
+ Requires-Dist: nodeenv~=1.9.1
24
26
  Provides-Extra: execute
25
27
  Requires-Dist: ipykernel; extra == 'execute'
26
28
  Requires-Dist: jupyter-server; extra == 'execute'
@@ -0,0 +1,9 @@
1
+ mystmd_py/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ mystmd_py/main.py,sha256=o4Kz4iw3TEAw63qPsTuX0lrrq-tFEbrUbrxC4dTlXB8,4855
3
+ mystmd_py/myst.cjs,sha256=CMFNN3Q13AfakBIB9G_kZw0Hk_o_d-j1sMP39yJnvBo,13104265
4
+ mystmd_py/nodeenv.py,sha256=3dJ9ZmO5u4smh5EkmOPDYOuHR8-5or181ifPHY-TBaA,2089
5
+ mystmd-1.3.8.dist-info/METADATA,sha256=f7jBsGyPHN4E4eJsY3jWiHIRSWGPQVSQQ_20ecboL_E,3019
6
+ mystmd-1.3.8.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
7
+ mystmd-1.3.8.dist-info/entry_points.txt,sha256=eC2ol2gqS2q5E-ktkMrBSvV0tckGUcNGS-c4hEQ-_V4,45
8
+ mystmd-1.3.8.dist-info/licenses/LICENSE,sha256=4BcikqvulW5nh_MxaocO-lC7ydIX23dMbcqtNTUSxr4,1082
9
+ mystmd-1.3.8.dist-info/RECORD,,
mystmd_py/main.py CHANGED
@@ -1,33 +1,125 @@
1
1
  import os
2
2
  import pathlib
3
+ import platform
3
4
  import shutil
4
5
  import subprocess
5
6
  import sys
6
7
  import re
8
+ import textwrap
7
9
 
8
10
 
9
- def main():
10
- NODE_LOCATION = (
11
- shutil.which("node") or shutil.which("node.exe") or shutil.which("node.cmd")
11
+ NODEENV_VERSION = "18.0.0"
12
+ INSTALL_NODEENV_KEY = "MYSTMD_ALLOW_NODEENV"
13
+
14
+
15
+ class PermissionDeniedError(Exception): ...
16
+
17
+
18
+ class NodeEnvCreationError(Exception): ...
19
+
20
+
21
+ def is_windows():
22
+ return platform.system() == "Windows"
23
+
24
+
25
+ def find_installed_node():
26
+ # shutil.which can find things with PATHEXT, but 3.12.0 breaks this by preferring NODE over NODE.EXE on Windows
27
+ return shutil.which("node.exe") if is_windows() else shutil.which("node")
28
+
29
+
30
+ def find_nodeenv_path():
31
+ # The conda packaging of this package does not need to install node!
32
+ import platformdirs
33
+
34
+ return platformdirs.user_data_path(
35
+ appname="myst", appauthor=False, version=NODEENV_VERSION
12
36
  )
13
- PATH_TO_BIN_JS = (pathlib.Path(__file__).parent / "myst.cjs").resolve()
14
37
 
15
- if not NODE_LOCATION:
38
+
39
+ def ask_to_install_node(path):
40
+ if env_value := os.environ.get(INSTALL_NODEENV_KEY, "").lower():
41
+ return env_value in {"yes", "true", "1", "y"}
42
+
43
+ return input(f"❔ Install Node.js in '{path}'? (y/N): ").lower() == "y"
44
+
45
+
46
+ def create_nodeenv(env_path):
47
+ command = [
48
+ sys.executable,
49
+ "-m",
50
+ "nodeenv",
51
+ "-v",
52
+ f"--node={NODEENV_VERSION}",
53
+ "--prebuilt",
54
+ "--clean-src",
55
+ env_path,
56
+ ]
57
+ result = subprocess.run(command, capture_output=True, encoding="utf-8")
58
+ if result.returncode:
59
+ shutil.rmtree(env_path)
60
+ raise NodeEnvCreationError(result.stderr)
61
+ else:
62
+ return env_path
63
+
64
+
65
+ def find_any_node(binary_path):
66
+ node_path = find_installed_node()
67
+ if node_path is not None:
68
+ return pathlib.Path(node_path).absolute(), binary_path
69
+
70
+ nodeenv_path = find_nodeenv_path()
71
+ if not nodeenv_path.exists():
72
+ print("❗ Node.js (node) is required to run MyST, but could not be found`.")
73
+ if ask_to_install_node(nodeenv_path):
74
+ print(f"⚙️ Attempting to install Node.js in {nodeenv_path} ...")
75
+ create_nodeenv(nodeenv_path)
76
+ print(f"ℹ️ Successfully installed Node.js {NODEENV_VERSION}")
77
+ else:
78
+ raise PermissionDeniedError("Node.js installation was not permitted")
79
+
80
+ # Find the executable path
81
+ new_node_path = (
82
+ (nodeenv_path / "Scripts" / "node.exe")
83
+ if is_windows()
84
+ else (nodeenv_path / "bin" / "node")
85
+ )
86
+ new_path = os.pathsep.join(
87
+ [*binary_path.split(os.pathsep), str(new_node_path.parent)]
88
+ )
89
+ return new_node_path, new_path
90
+
91
+
92
+ def main():
93
+ # Find NodeJS (and potential new PATH)
94
+ binary_path = os.environ.get("PATH", os.defpath)
95
+ try:
96
+ node_path, os_path = find_any_node(binary_path)
97
+ except NodeEnvCreationError as err:
98
+ message = textwrap.indent(err.args[0], " ")
16
99
  raise SystemExit(
17
- "You must install node >=18 to run MyST\n\n"
18
- "We recommend installing the latest LTS release, using your preferred package manager\n"
19
- "or following instructions here: https://nodejs.org/en/download"
20
- )
21
- node = pathlib.Path(NODE_LOCATION).absolute()
100
+ "💥 The attempt to install Node.js was unsuccessful.\n"
101
+ f"🔍 Underlying error:\n{message}\n\n"
102
+ "ℹ️ We recommend installing the latest LTS release, using your preferred package manager "
103
+ "or following instructions here: https://nodejs.org\n\n"
104
+ ) from err
105
+ except PermissionDeniedError as err:
106
+ raise SystemExit(
107
+ "💥 The attempt to install Node.js failed because the user denied the request.\n"
108
+ "ℹ️ We recommend installing the latest LTS release, using your preferred package manager "
109
+ "or following instructions here: https://nodejs.org\n\n"
110
+ ) from err
111
+
112
+ # Build new env dict
113
+ node_env = {**os.environ, "PATH": os_path}
22
114
 
115
+ # Check version
23
116
  _version = subprocess.run(
24
- [node, "-v"], capture_output=True, check=True, text=True
117
+ [node_path, "-v"], capture_output=True, check=True, text=True, env=node_env
25
118
  ).stdout
26
119
  major_version_match = re.match(r"v(\d+).*", _version)
27
120
 
28
121
  if major_version_match is None:
29
122
  raise SystemExit(f"MyST could not determine the version of Node.js: {_version}")
30
-
31
123
  major_version = int(major_version_match[1])
32
124
  if not (major_version in {18, 20, 22} or major_version > 22):
33
125
  raise SystemExit(
@@ -35,11 +127,24 @@ def main():
35
127
  "Please update to the latest LTS release, using your preferred package manager\n"
36
128
  "or following instructions here: https://nodejs.org/en/download"
37
129
  )
38
- os.execve(
39
- node,
40
- [node.name, PATH_TO_BIN_JS, *sys.argv[1:]],
41
- {**os.environ, "MYST_LANG": "PYTHON"},
42
- )
130
+
131
+ # Find path to compiled JS
132
+ js_path = (pathlib.Path(__file__).parent / "myst.cjs").resolve()
133
+
134
+ # Build args for Node.js process
135
+ myst_node_args = [js_path, *sys.argv[1:]]
136
+ myst_env = {**node_env, "MYST_LANG": "PYTHON"}
137
+
138
+ # Invoke appropriate binary for platform
139
+ if platform.system() == "Windows":
140
+ result = subprocess.run([node_path, *myst_node_args], env=myst_env)
141
+ sys.exit(result.returncode)
142
+ else:
143
+ os.execve(
144
+ node_path,
145
+ [node_path.name, *myst_node_args],
146
+ myst_env,
147
+ )
43
148
 
44
149
 
45
150
  if __name__ == "__main__":
mystmd_py/myst.cjs CHANGED
@@ -193562,7 +193562,7 @@ var {
193562
193562
  } = import_index.default;
193563
193563
 
193564
193564
  // src/version.ts
193565
- var version = "1.3.6";
193565
+ var version = "1.3.8";
193566
193566
  var version_default = version;
193567
193567
 
193568
193568
  // ../myst-cli/dist/build/build.js
@@ -280389,7 +280389,7 @@ function runDirectives(state) {
280389
280389
  directiveOpen.map = map14;
280390
280390
  directiveOpen.meta = {
280391
280391
  arg: arg2,
280392
- options: simplifyDirectiveOptions(options),
280392
+ options: getDirectiveOptions(options),
280393
280393
  // Tightness is computed for all directives (are they separated by a newline before/after)
280394
280394
  tight: computeBlockTightness(state.src, token.map)
280395
280395
  };
@@ -280471,7 +280471,7 @@ function parseDirectiveContent(content3, info, state) {
280471
280471
  const match3 = COLON_OPTION_REGEX.exec(line2);
280472
280472
  const { option: option3, value } = (_a6 = match3 === null || match3 === void 0 ? void 0 : match3.groups) !== null && _a6 !== void 0 ? _a6 : {};
280473
280473
  if (option3)
280474
- options.push([option3, value || "true"]);
280474
+ options.push([option3, value || true]);
280475
280475
  bodyOffset++;
280476
280476
  }
280477
280477
  }
@@ -280482,31 +280482,28 @@ function parseDirectiveContent(content3, info, state) {
280482
280482
  function directiveArgToTokens(arg2, lineNumber, state) {
280483
280483
  return nestedPartToTokens("directive_arg", arg2, lineNumber, state, "run_directives", true);
280484
280484
  }
280485
- function simplifyDirectiveOptions(options) {
280485
+ function getDirectiveOptions(options) {
280486
280486
  if (!options)
280487
280487
  return void 0;
280488
280488
  const simplified = {};
280489
280489
  options.forEach(([key2, val]) => {
280490
280490
  if (simplified[key2] !== void 0) {
280491
280491
  return;
280492
- } else if (!isNaN(Number(val))) {
280493
- simplified[key2] = Number(val);
280494
- } else if (typeof val === "string" && val.toLowerCase() === "true") {
280495
- simplified[key2] = true;
280496
- } else if (typeof val === "string" && val.toLowerCase() === "false") {
280497
- simplified[key2] = false;
280498
- } else {
280499
- simplified[key2] = val;
280500
280492
  }
280493
+ simplified[key2] = val;
280501
280494
  });
280502
280495
  return simplified;
280503
280496
  }
280504
280497
  function directiveOptionsToTokens(options, lineNumber, state) {
280505
280498
  const tokens = options.map(([key2, value], index4) => {
280506
- const optTokens = nestedPartToTokens("directive_option", `${value}`, lineNumber + index4, state, "run_directives", true);
280499
+ const optTokens = typeof value === "string" ? nestedPartToTokens("directive_option", value, lineNumber + index4, state, "run_directives", true) : [
280500
+ new state.Token("directive_option_open", "", 1),
280501
+ new state.Token("directive_option_close", "", -1)
280502
+ ];
280507
280503
  if (optTokens.length) {
280508
280504
  optTokens[0].info = key2;
280509
- optTokens[0].content = value;
280505
+ optTokens[0].content = typeof value === "string" ? value : "";
280506
+ optTokens[0].meta = { value };
280510
280507
  }
280511
280508
  return optTokens;
280512
280509
  });
@@ -287928,7 +287925,7 @@ var defaultMdast = {
287928
287925
  getAttrs(t2) {
287929
287926
  return {
287930
287927
  name: t2.info,
287931
- value: t2.content
287928
+ value: t2.meta.value
287932
287929
  };
287933
287930
  }
287934
287931
  },
@@ -288129,6 +288126,8 @@ function contentFromNode(node3, spec, vfile2, description, ruleId) {
288129
288126
  return void 0;
288130
288127
  }
288131
288128
  if (spec.type === ParseTypesEnum.string || spec.type === String) {
288129
+ if (value === true)
288130
+ return "";
288132
288131
  if (typeof value !== "string" && !(value && typeof value === "number" && !isNaN(value))) {
288133
288132
  fileWarn(vfile2, `value is not a string for ${description}`, { node: node3, ruleId });
288134
288133
  }
@@ -288136,7 +288135,7 @@ function contentFromNode(node3, spec, vfile2, description, ruleId) {
288136
288135
  }
288137
288136
  if (spec.type === ParseTypesEnum.number || spec.type === Number) {
288138
288137
  const valueAsNumber = Number(value);
288139
- if (isNaN(valueAsNumber)) {
288138
+ if (value === true || isNaN(valueAsNumber)) {
288140
288139
  const fileFn = spec.required ? fileError : fileWarn;
288141
288140
  fileFn(vfile2, `number not provided for ${description}`, { node: node3, ruleId });
288142
288141
  return void 0;
@@ -288471,17 +288470,22 @@ var cardDirective = {
288471
288470
  name: "card",
288472
288471
  alias: ["grid-item-card"],
288473
288472
  arg: {
288474
- type: "myst"
288473
+ type: "myst",
288474
+ doc: "The title of the card, usually shown as bolded text at the top of the card."
288475
288475
  },
288476
288476
  options: {
288477
- link: {
288478
- type: String
288477
+ url: {
288478
+ type: String,
288479
+ alias: ["link"],
288480
+ doc: "Turns the card into a link, can be internal or external."
288479
288481
  },
288480
288482
  header: {
288481
- type: "myst"
288483
+ type: "myst",
288484
+ doc: "Adds a header to the card."
288482
288485
  },
288483
288486
  footer: {
288484
- type: "myst"
288487
+ type: "myst",
288488
+ doc: "Adds a footer to the card."
288485
288489
  }
288486
288490
  // // https://sphinx-design.readthedocs.io/en/furo-theme/cards.html#card-options
288487
288491
  // width
@@ -288512,10 +288516,11 @@ var cardDirective = {
288512
288516
  },
288513
288517
  body: {
288514
288518
  type: "myst",
288515
- required: true
288519
+ required: true,
288520
+ doc: "Main body content of the card."
288516
288521
  },
288517
288522
  run(data) {
288518
- const { link: link4, header, footer: footer2 } = data.options || {};
288523
+ const { url, header, footer: footer2 } = data.options || {};
288519
288524
  let headerChildren;
288520
288525
  let bodyChildren;
288521
288526
  let footerChildren;
@@ -288552,7 +288557,7 @@ var cardDirective = {
288552
288557
  return [
288553
288558
  {
288554
288559
  type: "card",
288555
- url: link4,
288560
+ url,
288556
288561
  children
288557
288562
  }
288558
288563
  ];
@@ -289038,7 +289043,7 @@ var import_node_path15 = __toESM(require("path"), 1);
289038
289043
  var import_nbtx = __toESM(require_cjs2(), 1);
289039
289044
 
289040
289045
  // ../myst-cli/dist/version.js
289041
- var version2 = "1.3.6";
289046
+ var version2 = "1.3.8";
289042
289047
  var version_default2 = version2;
289043
289048
 
289044
289049
  // ../myst-cli/dist/utils/headers.js
@@ -302319,13 +302324,13 @@ function applyComputedOutputsToNodes(nodes, computedResult) {
302319
302324
  async function kernelExecutionTransform(tree, vfile2, opts) {
302320
302325
  var _a6;
302321
302326
  const log = (_a6 = opts.log) !== null && _a6 !== void 0 ? _a6 : console;
302322
- if (opts.frontmatter.kernelspec === void 0) {
302323
- return fileError(vfile2, `Notebook does not declare the necessary 'kernelspec' frontmatter key required for execution`);
302324
- }
302325
302327
  const executableNodes = selectAll(`block[kind=${NotebookCell.code}],inlineExpression`, tree);
302326
302328
  if (executableNodes.length === 0) {
302327
302329
  return;
302328
302330
  }
302331
+ if (opts.frontmatter.kernelspec === void 0) {
302332
+ return fileError(vfile2, `Notebook does not declare the necessary 'kernelspec' frontmatter key required for execution`);
302333
+ }
302329
302334
  const cacheKey = buildCacheKey(opts.frontmatter.kernelspec, executableNodes);
302330
302335
  let cachedResults = opts.cache.get(cacheKey);
302331
302336
  if (opts.ignoreCache || cachedResults === void 0) {
@@ -304400,6 +304405,10 @@ var ENVIRONMENTS2 = [
304400
304405
  ];
304401
304406
  var RE_OPEN2 = new RegExp(`^\\\\begin{(${ENVIRONMENTS2.join("|")})([*]?)}`);
304402
304407
  function isAmsmathEnvironment(value) {
304408
+ const matches4 = value.trim().matchAll(new RegExp(`\\\\begin{(${ENVIRONMENTS2.join("|")})}`, "g"));
304409
+ if ([...matches4].length > 1) {
304410
+ return false;
304411
+ }
304403
304412
  const matchOpen = value.trim().match(RE_OPEN2);
304404
304413
  if (!matchOpen)
304405
304414
  return false;
mystmd_py/nodeenv.py ADDED
@@ -0,0 +1,76 @@
1
+ import os
2
+ import pathlib
3
+ import shutil
4
+ import subprocess
5
+ import sys
6
+
7
+
8
+ NODEENV_VERSION = "18.0.0"
9
+ INSTALL_NODEENV_KEY = "MYSTMD_ALLOW_NODEENV"
10
+
11
+
12
+ class PermissionDeniedError(Exception): ...
13
+
14
+
15
+ class NodeEnvCreationError(Exception): ...
16
+
17
+
18
+ def find_installed_node():
19
+ return shutil.which("node") or shutil.which("node.exe") or shutil.which("node.cmd")
20
+
21
+
22
+ def find_nodeenv_path():
23
+ # The conda packaging of this package does not need to install node!
24
+ import platformdirs
25
+ return platformdirs.user_data_path(
26
+ appname="myst", appauthor=False, version=NODEENV_VERSION
27
+ )
28
+
29
+
30
+ def ask_to_install_node(path):
31
+ if env_value := os.environ.get(INSTALL_NODEENV_KEY, "").lower():
32
+ return env_value in {"yes", "true", "1", "y"}
33
+
34
+ return input(f"❔ Install Node.js in '{path}'? (y/N): ").lower() == "y"
35
+
36
+
37
+ def create_nodeenv(env_path):
38
+ command = [
39
+ sys.executable,
40
+ "-m",
41
+ "nodeenv",
42
+ "-v",
43
+ f"--node={NODEENV_VERSION}",
44
+ "--prebuilt",
45
+ "--clean-src",
46
+ env_path,
47
+ ]
48
+ result = subprocess.run(command, capture_output=True, encoding="utf-8")
49
+ if result.returncode:
50
+ shutil.rmtree(env_path)
51
+ raise NodeEnvCreationError(result.stderr)
52
+ else:
53
+ return env_path
54
+
55
+
56
+ def find_any_node(binary_path):
57
+ node_path = find_installed_node()
58
+ if node_path is not None:
59
+ return pathlib.Path(node_path).absolute(), binary_path
60
+
61
+ nodeenv_path = find_nodeenv_path()
62
+ if not nodeenv_path.exists():
63
+ print("❗ Node.js (node) is required to run MyST, but could not be found`.")
64
+ if ask_to_install_node(nodeenv_path):
65
+ print(f"⚙️ Attempting to install Node.js in {nodeenv_path} ...")
66
+ create_nodeenv(nodeenv_path)
67
+ print(f"ℹ️ Successfully installed Node.js {NODEENV_VERSION}")
68
+ else:
69
+ raise PermissionDeniedError("Node.js installation was not permitted")
70
+
71
+ new_path = os.pathsep.join(
72
+ [*binary_path.split(os.pathsep), str(nodeenv_path / "bin")]
73
+ )
74
+ return nodeenv_path / "bin" / "node", new_path
75
+
76
+
@@ -1,8 +0,0 @@
1
- mystmd_py/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- mystmd_py/main.py,sha256=BaPniv8thshA5DKIBoq2huFUs1Jb0zFkMxN2UIHXUkw,1511
3
- mystmd_py/myst.cjs,sha256=FxEBSPoYyXHIws693tzyrUqdWGKTKrIeWQN38ZQSPcc,13103855
4
- mystmd-1.3.6.dist-info/METADATA,sha256=edCiMgoJBMsYUhBcw_gfIGm94Cwfwno7-zpdcXdxLMU,2954
5
- mystmd-1.3.6.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
6
- mystmd-1.3.6.dist-info/entry_points.txt,sha256=eC2ol2gqS2q5E-ktkMrBSvV0tckGUcNGS-c4hEQ-_V4,45
7
- mystmd-1.3.6.dist-info/licenses/LICENSE,sha256=4BcikqvulW5nh_MxaocO-lC7ydIX23dMbcqtNTUSxr4,1082
8
- mystmd-1.3.6.dist-info/RECORD,,
File without changes