proschi 0.7.0 → 0.8.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.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # proschi
2
2
 
3
3
  Command-line checker and language server for
4
- [Proschi](https://gvart.github.io/proschi/), a small text language for
4
+ [Proschi](https://proschi.app/), a small text language for
5
5
  microservice architectures and the request flows that run through them.
6
6
 
7
7
  ```sh
package/dist/cli.cjs CHANGED
@@ -2237,8 +2237,8 @@ var require_resolve = __commonJS({
2237
2237
  }
2238
2238
  return count;
2239
2239
  }
2240
- function getFullPath(resolver, id = "", normalize) {
2241
- if (normalize !== false)
2240
+ function getFullPath(resolver, id = "", normalize2) {
2241
+ if (normalize2 !== false)
2242
2242
  id = normalizeId(id);
2243
2243
  const p = resolver.parse(id);
2244
2244
  return _getFullPath(resolver, p);
@@ -3833,7 +3833,7 @@ var require_fast_uri = __commonJS({
3833
3833
  }
3834
3834
  return decodedScheme;
3835
3835
  }
3836
- function normalize(uri, options) {
3836
+ function normalize2(uri, options) {
3837
3837
  if (typeof uri === "string") {
3838
3838
  uri = /** @type {T} */
3839
3839
  normalizeString(uri, options);
@@ -4211,7 +4211,7 @@ var require_fast_uri = __commonJS({
4211
4211
  }
4212
4212
  var fastUri = {
4213
4213
  SCHEMES,
4214
- normalize,
4214
+ normalize: normalize2,
4215
4215
  resolve: resolve4,
4216
4216
  resolveComponent,
4217
4217
  equal,
@@ -19285,9 +19285,12 @@ var PROBLEM_MD = "problem.md";
19285
19285
  var GIVEN = "given.proschi";
19286
19286
  var STARTER = "starter.proschi";
19287
19287
  var SOLUTION = "solution.proschi";
19288
+ var LESSON_MD = "lesson.md";
19288
19289
  var WRONG_DIR = "wrong";
19289
19290
  var REQUIRED = [PROBLEM_MD, GIVEN, STARTER, SOLUTION];
19291
+ var OPTIONAL = [LESSON_MD];
19290
19292
  var ID = /^[a-z0-9]+(-[a-z0-9]+)*$/;
19293
+ var RESERVED_IDS = ["problem", "roadmap", "approach"];
19291
19294
  var EXPECT_FAIL = /^#\s*expect-fail:\s*(.*?)\s*$/;
19292
19295
  var ProblemFolderError = class extends Error {
19293
19296
  folder;
@@ -19313,7 +19316,7 @@ function expectFailLines(source2) {
19313
19316
  }
19314
19317
  return names;
19315
19318
  }
19316
- var FIELDS = ["title", "summary", "difficulty", "tags", "order", "hints"];
19319
+ var FIELDS = ["title", "summary", "difficulty", "tags", "company", "order", "hints", "version"];
19317
19320
  function readProblemMd(id, text) {
19318
19321
  const fail2 = (message, line) => {
19319
19322
  throw new ProblemFolderError(id, message, PROBLEM_MD, line);
@@ -19339,17 +19342,22 @@ function readProblemMd(id, text) {
19339
19342
  };
19340
19343
  const difficulty = str("difficulty");
19341
19344
  if (!DIFFICULTIES.includes(difficulty)) fail2(`'difficulty' must be one of ${DIFFICULTIES.join(", ")}`);
19345
+ const company = data.company === void 0 ? void 0 : str("company");
19342
19346
  const order = data.order;
19343
19347
  if (order !== void 0 && typeof order !== "number") fail2("'order' must be a number");
19348
+ const version = data.version;
19349
+ if (version !== void 0 && !(typeof version === "number" && Number.isInteger(version) && version >= 1)) fail2("'version' must be a whole number from 1");
19344
19350
  if (body === "") fail2("The statement (the Markdown after the front matter) is empty");
19345
19351
  return {
19346
19352
  title: str("title"),
19347
19353
  summary: str("summary"),
19348
19354
  difficulty,
19349
19355
  tags: list("tags"),
19356
+ ...company !== void 0 ? { company } : {},
19350
19357
  ...typeof order === "number" ? { order } : {},
19351
19358
  statement: body,
19352
- hints: list("hints")
19359
+ hints: list("hints"),
19360
+ ...typeof version === "number" ? { version } : {}
19353
19361
  };
19354
19362
  }
19355
19363
  function problemFromFiles(id, files) {
@@ -19357,11 +19365,12 @@ function problemFromFiles(id, files) {
19357
19365
  throw new ProblemFolderError(id, message);
19358
19366
  };
19359
19367
  if (!ID.test(id)) fail2('The folder name is the problem id and must be lowercase words joined by "-", e.g. url-shortener');
19368
+ if (RESERVED_IDS.includes(id)) fail2(`"${id}" is taken by a practice page; reserved ids: ${RESERVED_IDS.join(", ")}`);
19360
19369
  for (const name of REQUIRED) if (files[name] === void 0) fail2(`Missing ${name}`);
19361
19370
  for (const name of Object.keys(files)) {
19362
19371
  const wrong2 = name.startsWith(`${WRONG_DIR}/`) ? name.slice(WRONG_DIR.length + 1) : void 0;
19363
- if (!REQUIRED.includes(name) && !(wrong2 && /^[a-z0-9][a-z0-9-]*\.proschi$/.test(wrong2))) {
19364
- fail2(`Unexpected file ${name}: a problem folder holds ${REQUIRED.join(", ")} and ${WRONG_DIR}/<name>.proschi (lowercase, digits and "-")`);
19372
+ if (!REQUIRED.includes(name) && !OPTIONAL.includes(name) && !(wrong2 && /^[a-z0-9][a-z0-9-]*\.proschi$/.test(wrong2))) {
19373
+ fail2(`Unexpected file ${name}: a problem folder holds ${REQUIRED.join(", ")}, optionally ${OPTIONAL.join(", ")}, and ${WRONG_DIR}/<name>.proschi (lowercase, digits and "-")`);
19365
19374
  }
19366
19375
  }
19367
19376
  const meta = readProblemMd(id, files[PROBLEM_MD]);
@@ -19372,10 +19381,63 @@ function problemFromFiles(id, files) {
19372
19381
  given: files[GIVEN],
19373
19382
  starter: files[STARTER],
19374
19383
  solution: files[SOLUTION],
19384
+ ...files[LESSON_MD] !== void 0 ? { lesson: files[LESSON_MD] } : {},
19375
19385
  ...wrong.length ? { wrong } : {}
19376
19386
  };
19377
19387
  }
19378
19388
 
19389
+ // ../frontend/src/practice/lesson.ts
19390
+ var LESSON_HEADINGS = [
19391
+ "What you'll learn",
19392
+ "The problem, explained",
19393
+ "Back-of-the-envelope",
19394
+ "Concepts",
19395
+ "Designing it step by step",
19396
+ "Common mistakes",
19397
+ "In the interview",
19398
+ "Further reading"
19399
+ ];
19400
+ var normalize = (heading) => heading.replace(/[‘’]/g, "'").trim();
19401
+ function proseLines(source2) {
19402
+ const out = [];
19403
+ let fence = false;
19404
+ source2.replace(/\r\n?/g, "\n").split("\n").forEach((text, i) => {
19405
+ if (/^\s*```/.test(text)) fence = !fence;
19406
+ else if (!fence) out.push({ text, line: i + 1 });
19407
+ });
19408
+ return out;
19409
+ }
19410
+ function h2Headings(source2) {
19411
+ return proseLines(source2).map(({ text, line }) => ({ m: /^##\s+(.*?)\s*#*\s*$/.exec(text), line })).filter((h) => h.m !== null).map(({ m, line }) => ({ text: m[1], line }));
19412
+ }
19413
+ function lessonIssues(source2) {
19414
+ const issues = [];
19415
+ if (!source2.trim()) return [{ message: "The lesson is empty" }];
19416
+ const headings = h2Headings(source2);
19417
+ let last = -1;
19418
+ let lastName = "";
19419
+ for (const required of LESSON_HEADINGS) {
19420
+ const found = headings.find((h) => normalize(h.text) === required);
19421
+ if (!found) {
19422
+ issues.push({ message: `The lesson needs a "## ${required}" section (the sections are: ${LESSON_HEADINGS.join(", ")})` });
19423
+ continue;
19424
+ }
19425
+ const index = headings.indexOf(found);
19426
+ if (index < last) issues.push({ message: `"## ${required}" must come after "## ${lastName}"`, line: found.line });
19427
+ else {
19428
+ last = index;
19429
+ lastName = required;
19430
+ }
19431
+ }
19432
+ for (const { text, line } of proseLines(source2)) {
19433
+ const prose = text.replace(/`[^`]*`/g, "");
19434
+ for (const m of prose.matchAll(/\]\(([^)\s]*)[^)]*\)/g)) {
19435
+ if (!/^https?:\/\/\S+$/i.test(m[1])) issues.push({ message: `Links in a lesson must go to an http(s) address, not "${m[1]}"`, line });
19436
+ }
19437
+ }
19438
+ return issues;
19439
+ }
19440
+
19379
19441
  // ../frontend/src/dsl/imports.ts
19380
19442
  function joinImportPath(fromPath, importPath) {
19381
19443
  const parts = importPath.startsWith("/") || !fromPath ? [] : fromPath.split("/").slice(0, -1);
@@ -19470,6 +19532,7 @@ function validateProblem(problem, engine = defaultEngine) {
19470
19532
  if (!problem.statement.includes(`**${name}**`)) add(PROBLEM_MD, `The statement must name the use case **${name}** in bold`);
19471
19533
  if (!problem.solution.includes(`usecase "${name}"`)) add(SOLUTION, `The solution must define usecase "${name}" from the traffic`);
19472
19534
  }
19535
+ if (problem.lesson !== void 0) for (const issue of lessonIssues(problem.lesson)) add(LESSON_MD, issue.message, issue.line);
19473
19536
  const importLine = problem.given.split("\n").findIndex((l) => /^\s*import /.test(l));
19474
19537
  if (importLine >= 0) add(GIVEN, "The given must not import anything", importLine + 1);
19475
19538
  report(GIVEN, parse(problem.given).diagnostics.filter((d) => d.severity === "error"));
@@ -19845,7 +19908,7 @@ ${PROBLEM_USAGE}`);
19845
19908
  }
19846
19909
 
19847
19910
  // src/cli.ts
19848
- var VERSION2 = true ? "0.7.0" : "dev";
19911
+ var VERSION2 = true ? "0.8.0" : "dev";
19849
19912
  var USAGE = `Usage:
19850
19913
  proschi check [--strict] [--format text|github|json] [--openapi <node>=<spec>]... <file|dir>...
19851
19914
  proschi parse <file>