book-kit 0.0.0-stage → 0.6.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/LICENSE +21 -0
- package/NOTICE.md +72 -0
- package/README.md +251 -2
- package/bin/book-kit.js +115 -0
- package/commands/_emoji_io.py +32 -0
- package/commands/audit-chapter-lengths.js +244 -0
- package/commands/check-epub-size.js +68 -0
- package/commands/check-pdf-page-count.js +162 -0
- package/commands/composite-kindle-emoji.py +85 -0
- package/commands/composite-print-emoji.py +98 -0
- package/commands/conventions.js +76 -0
- package/commands/convert-pdfx.sh +194 -0
- package/commands/download-icc-profile.sh +105 -0
- package/commands/fonts.py +212 -0
- package/commands/generate-reference-docx.py +344 -0
- package/commands/kindle-fonts.js +173 -0
- package/commands/links-to-footnotes.js +366 -0
- package/commands/markdownlint.js +25 -0
- package/commands/minify-css.js +43 -0
- package/commands/optimize-images.js +73 -0
- package/commands/parity.sh +150 -0
- package/commands/postprocess-epub.js +541 -0
- package/commands/prep-print-src.js +77 -0
- package/commands/prose.js +38 -0
- package/commands/release-files.js +89 -0
- package/commands/reorder-print-frontmatter.js +79 -0
- package/commands/strip-page-rules.js +1168 -0
- package/commands/strip-property-rules.js +93 -0
- package/commands/update-key-takeaways.js +314 -0
- package/commands/validate.js +283 -0
- package/configs/markdownlint-cli2.jsonc +98 -0
- package/configs/textlintrc.json +22 -0
- package/css/theme/01-tokens.css +83 -0
- package/css/theme/02-fonts.css +79 -0
- package/css/theme/03-prose.css +53 -0
- package/css/theme/04-base.css +226 -0
- package/css/theme/05-components.css +546 -0
- package/css/theme/06-utilities.css +40 -0
- package/css/theme/07-ereader.css +245 -0
- package/css/theme/08-epub.css +865 -0
- package/css/theme/09-back-matter.css +47 -0
- package/css/theme/10-dark.css +302 -0
- package/css/theme/11-print.css +1229 -0
- package/css/theme.css +38 -0
- package/defaults/book.config.yml +318 -0
- package/defaults/pandoc-base.yml +19 -0
- package/defaults/pandoc-docx.yml +6 -0
- package/defaults/pandoc-epub-fonts.yml +6 -0
- package/defaults/pandoc-epub.yml +15 -0
- package/defaults/pandoc-kindle.yml +6 -0
- package/emoji/1f382.png +0 -0
- package/emoji/1f389.png +0 -0
- package/emoji/1f440.png +0 -0
- package/emoji/1f44d.png +0 -0
- package/emoji/1f44f.png +0 -0
- package/emoji/1f454.png +0 -0
- package/emoji/1f4a1.png +0 -0
- package/emoji/1f4bb.png +0 -0
- package/emoji/1f602.png +0 -0
- package/emoji/1f603.png +0 -0
- package/emoji/1f604.png +0 -0
- package/emoji/1f60a.png +0 -0
- package/emoji/1f60f.png +0 -0
- package/emoji/1f64c.png +0 -0
- package/emoji/1f64f.png +0 -0
- package/emoji/1f914.png +0 -0
- package/emoji/1f923.png +0 -0
- package/emoji/2714.png +0 -0
- package/emoji/274c.png +0 -0
- package/filters/add-div-titles.lua +196 -0
- package/filters/arc-notice.lua +36 -0
- package/filters/body-emoji-images.lua +48 -0
- package/filters/callout-classes.lua +40 -0
- package/filters/lib/book_kit.lua +110 -0
- package/filters/revision.lua +17 -0
- package/filters/strip-comments.lua +40 -0
- package/filters/strip-emoji-kindle.lua +134 -0
- package/fonts/LICENSE-source-code-pro.md +93 -0
- package/fonts/LICENSE-source-sans.md +93 -0
- package/fonts/LICENSE-source-serif.md +93 -0
- package/fonts/recipe.json +41 -0
- package/just/book-kit.just +229 -0
- package/lib/acronyms.js +47 -0
- package/lib/callouts.js +17 -0
- package/lib/config.js +211 -0
- package/lib/is-main.js +21 -0
- package/lib/lint.js +148 -0
- package/lib/pandoc.js +148 -0
- package/lib/paragraphs.js +173 -0
- package/lib/revision.js +35 -0
- package/lib/structure.js +39 -0
- package/lib/textlint-github-formatter.js +58 -0
- package/lib/validate.js +202 -0
- package/package.json +98 -4
- package/presets/business-nonfiction/CONVENTIONS.md +35 -0
- package/presets/business-nonfiction/preset.yml +128 -0
- package/profiles/pdfx-def.ps +42 -0
- package/requirements.txt +14 -0
- package/rules/no-ai-like-patterns.js +209 -0
- package/rules/no-assumptive.js +76 -0
- package/rules/no-bare-acronyms.js +129 -0
- package/rules/no-cliches.js +544 -0
- package/rules/no-cultural-idioms.js +422 -0
- package/rules/no-vague-intensifiers.js +94 -0
- package/templates/template-epub.html +79 -0
- package/templates/template.html +73 -0
- package/toolchain.json +42 -0
- package/validators/acronym-expansion.js +94 -0
- package/validators/bold-keywords.js +56 -0
- package/validators/callout-balance.js +74 -0
- package/validators/callout-divs.js +66 -0
- package/validators/callout-labels.js +102 -0
- package/validators/chapter-lengths.js +14 -0
- package/validators/chapter.js +57 -0
- package/validators/cross-references.js +45 -0
- package/validators/doubled-words.js +76 -0
- package/validators/duplicate-headings.js +52 -0
- package/validators/em-dashes.js +73 -0
- package/validators/escaped-smallcaps.js +46 -0
- package/validators/footnotes.js +78 -0
- package/validators/heading-hierarchy.js +58 -0
- package/validators/hypothetical-hooks.js +103 -0
- package/validators/index-integrity.js +28 -0
- package/validators/index.js +73 -0
- package/validators/list-punctuation.js +123 -0
- package/validators/min-opener-length.js +87 -0
- package/validators/no-bare-url-link-text.js +38 -0
- package/validators/no-todo-markers.js +49 -0
- package/validators/no-transition-todos.js +34 -0
- package/validators/number-range-dashes.js +72 -0
- package/validators/paragraph-length.js +62 -0
- package/validators/quote-punctuation.js +85 -0
- package/validators/section-intro-final-lead.js +90 -0
- package/validators/section-intro-has-lead.js +41 -0
- package/validators/section-intro-no-tldr.js +37 -0
- package/validators/section-intros.js +33 -0
- package/validators/sentence-starters.js +132 -0
- package/validators/smallcaps.js +67 -0
- package/validators/tldr-length.js +58 -0
package/lib/validate.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// Helpers shared by the built-in validators (validators/) and the runner
|
|
2
|
+
// (commands/validate.js).
|
|
3
|
+
//
|
|
4
|
+
// Validators read the book's conventions from book.config.yml. Paths (here,
|
|
5
|
+
// in lint.exclude, and in validators.allow) are relative to the book root,
|
|
6
|
+
// which is the working directory when run through `book-kit validate`.
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import * as yaml from 'js-yaml';
|
|
11
|
+
import { deepMerge, loadConfig } from './config.js';
|
|
12
|
+
import { smallcapsAcronyms, universalAcronyms } from './acronyms.js';
|
|
13
|
+
|
|
14
|
+
export const config = loadConfig();
|
|
15
|
+
const SRC_DIR = config.paths.src;
|
|
16
|
+
const INDEX_PATH = config.paths.index;
|
|
17
|
+
// How a chapter entry under paths.src starts in index.yml ("- src/").
|
|
18
|
+
const SRC_ENTRY_PREFIX = `- ${path.normalize(SRC_DIR).replace(/\/+$/, '')}/`;
|
|
19
|
+
export const TAKEAWAYS_SUFFIX = config.structure.takeaways_suffix;
|
|
20
|
+
export const REQUIRED_CALLOUT = config.structure.required_callout;
|
|
21
|
+
|
|
22
|
+
// Files to exclude from validation (index.yml presence, TODO checks, and the
|
|
23
|
+
// required callout): lint.exclude in book.config.yml.
|
|
24
|
+
export const EXCLUDE_PATTERNS = config.lint.exclude;
|
|
25
|
+
|
|
26
|
+
// Machine-generated pages (lint.generated) whose text is derived from
|
|
27
|
+
// already-validated sources. Skip prose-level lexical validators that would
|
|
28
|
+
// misfire on them, e.g. acronyms locked inside link text that can't be
|
|
29
|
+
// smallcapped. List them in lint.exclude too for the per-chapter checks.
|
|
30
|
+
export const GENERATED_FILES = new Set(config.lint.generated);
|
|
31
|
+
|
|
32
|
+
// Acronyms that should always use [ACRONYM]{.smallcaps} formatting: the kit's
|
|
33
|
+
// list (lib/acronyms.js, shared with the no-bare-acronyms textlint rule) plus
|
|
34
|
+
// lint.acronyms. The smallcaps validator only flags acronyms it knows about.
|
|
35
|
+
export const KNOWN_ACRONYMS = smallcapsAcronyms(config);
|
|
36
|
+
|
|
37
|
+
// Acronyms readers know without expansion on first use: the kit's list plus
|
|
38
|
+
// lint.universal_acronyms.
|
|
39
|
+
export const UNIVERSALLY_KNOWN_ACRONYMS = universalAcronyms(config);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A validator's intentional exceptions (validators.allow.<name>).
|
|
43
|
+
* @param {string} name - Validator name
|
|
44
|
+
* @returns {Set<string>}
|
|
45
|
+
*/
|
|
46
|
+
export function allowFor(name) {
|
|
47
|
+
return new Set(config.validators.allow?.[name] ?? []);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A validator's options: its defaults deep-merged with
|
|
52
|
+
* validators.options.<name> (maps merge; lists and scalars replace).
|
|
53
|
+
* @param {string} name - Validator name
|
|
54
|
+
* @param {object} [defaults] - The validator's own defaults
|
|
55
|
+
* @param {object} [cfg] - Resolved config (the book's, by default)
|
|
56
|
+
* @returns {object}
|
|
57
|
+
*/
|
|
58
|
+
export function optionsFor(name, defaults = {}, cfg = config) {
|
|
59
|
+
return deepMerge(structuredClone(defaults), cfg.validators?.options?.[name] ?? {});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get all markdown chapters from src directory
|
|
64
|
+
* @returns {string[]} Array of chapter paths
|
|
65
|
+
*/
|
|
66
|
+
export function getChapters() {
|
|
67
|
+
const srcDir = SRC_DIR;
|
|
68
|
+
if (!fs.existsSync(srcDir)) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
return fs.readdirSync(srcDir)
|
|
72
|
+
.filter(file => file.endsWith('.md'))
|
|
73
|
+
.sort()
|
|
74
|
+
.map(file => path.join(srcDir, file));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Load the index.yml file and get input-files
|
|
79
|
+
* @returns {string[]} Array of chapter paths from index
|
|
80
|
+
*/
|
|
81
|
+
export function getIndexFiles() {
|
|
82
|
+
const indexPath = INDEX_PATH;
|
|
83
|
+
if (!fs.existsSync(indexPath)) {
|
|
84
|
+
console.error('Error: index.yml not found');
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const indexContent = fs.readFileSync(indexPath, 'utf8');
|
|
89
|
+
const index = yaml.load(indexContent);
|
|
90
|
+
return index['input-files'] || [];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check if a chapter should be excluded from validation
|
|
95
|
+
* @param {string} chapter - Path to chapter file
|
|
96
|
+
* @returns {boolean} True if chapter should be excluded
|
|
97
|
+
*/
|
|
98
|
+
export function isExcluded(chapter) {
|
|
99
|
+
return EXCLUDE_PATTERNS.includes(chapter);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parse index.yml to find section intro files (first heading file in each section).
|
|
104
|
+
* Sections are identified by YAML comment lines (# Section name).
|
|
105
|
+
* For each section, finds the first file whose content starts with a markdown heading.
|
|
106
|
+
* @returns {{section: string, file: string}[]} Array of section intro entries
|
|
107
|
+
*/
|
|
108
|
+
export function getSectionIntros() {
|
|
109
|
+
const indexPath = INDEX_PATH;
|
|
110
|
+
if (!fs.existsSync(indexPath)) {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const lines = fs.readFileSync(indexPath, 'utf8').split('\n');
|
|
115
|
+
const intros = [];
|
|
116
|
+
let currentSection = null;
|
|
117
|
+
let foundIntro = false;
|
|
118
|
+
|
|
119
|
+
for (const line of lines) {
|
|
120
|
+
const trimmed = line.trim();
|
|
121
|
+
|
|
122
|
+
// Detect YAML comment section headers (e.g., "# Part one")
|
|
123
|
+
if (trimmed.startsWith('#') && !trimmed.startsWith('#-')) {
|
|
124
|
+
currentSection = trimmed.replace(/^#+\s*/, '');
|
|
125
|
+
foundIntro = false;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Find the first file in the section whose content starts with a heading
|
|
130
|
+
if (currentSection && !foundIntro && trimmed.startsWith(SRC_ENTRY_PREFIX)) {
|
|
131
|
+
const file = trimmed.replace(/^-\s*/, '');
|
|
132
|
+
if (fs.existsSync(file)) {
|
|
133
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
134
|
+
const linesInFile = content.split('\n');
|
|
135
|
+
const firstHeadingLine = linesInFile.find((l) => /^#+\s/.test(l.trim()));
|
|
136
|
+
if (firstHeadingLine) {
|
|
137
|
+
intros.push({ section: currentSection, file });
|
|
138
|
+
foundIntro = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return intros;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Generate a pandoc-style anchor ID from heading text.
|
|
149
|
+
* Strips smallcaps spans, explicit IDs, inline code, and converts to kebab-case.
|
|
150
|
+
* @param {string} headingText - Raw heading line (including ## prefix)
|
|
151
|
+
* @returns {string} The anchor ID
|
|
152
|
+
*/
|
|
153
|
+
export function headingToAnchor(headingText) {
|
|
154
|
+
let text = headingText.replace(/^#+\s*/, '');
|
|
155
|
+
|
|
156
|
+
// Use explicit anchor ID if present: {#my-id}
|
|
157
|
+
const explicitId = text.match(/\{#([a-z0-9-]+)\}/);
|
|
158
|
+
if (explicitId) return explicitId[1];
|
|
159
|
+
|
|
160
|
+
// Strip smallcaps spans: [ACRONYM]{.smallcaps} -> ACRONYM
|
|
161
|
+
text = text.replace(/\\?\[([^\]]*?)\\?\]\{\.smallcaps[^}]*\}/g, '$1');
|
|
162
|
+
|
|
163
|
+
// Strip other pandoc attributes: {epub:type=...} etc.
|
|
164
|
+
text = text.replace(/\{[^}]*\}/g, '');
|
|
165
|
+
|
|
166
|
+
// Strip inline code backticks
|
|
167
|
+
text = text.replace(/`([^`]*)`/g, '$1');
|
|
168
|
+
|
|
169
|
+
// Strip links: [text](url) -> text
|
|
170
|
+
text = text.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1');
|
|
171
|
+
|
|
172
|
+
// Strip bold/italic markers
|
|
173
|
+
text = text.replace(/[*_]+/g, '');
|
|
174
|
+
|
|
175
|
+
// Convert to pandoc-style ID
|
|
176
|
+
return text
|
|
177
|
+
.toLowerCase()
|
|
178
|
+
.trim()
|
|
179
|
+
.replace(/[^\w\s-]/g, '') // Remove non-word chars except spaces and hyphens
|
|
180
|
+
.replace(/\s+/g, '-') // Spaces to hyphens
|
|
181
|
+
.replace(/-+/g, '-') // Collapse multiple hyphens
|
|
182
|
+
.replace(/^-|-$/g, ''); // Trim leading/trailing hyphens
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Collect all heading anchors from production chapters.
|
|
187
|
+
* @param {string[]} indexFiles - Files listed in index.yml
|
|
188
|
+
* @returns {Set<string>} Set of all anchor IDs
|
|
189
|
+
*/
|
|
190
|
+
export function collectAllAnchors(indexFiles) {
|
|
191
|
+
const anchors = new Set();
|
|
192
|
+
for (const file of indexFiles) {
|
|
193
|
+
if (!fs.existsSync(file)) continue;
|
|
194
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
195
|
+
for (const line of content.split('\n')) {
|
|
196
|
+
if (/^#{1,6}\s/.test(line)) {
|
|
197
|
+
anchors.add(headingToAnchor(line));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return anchors;
|
|
202
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,100 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "book-kit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Build and quality tooling for Markdown books: EPUB 3, Kindle, print-ready PDF and PDF/X-1a, HTML, and DOCX from one source, via pandoc, WeasyPrint, and Tailwind CSS.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/benbalter/book-kit.git"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public",
|
|
13
|
+
"provenance": true
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"book-kit": "bin/book-kit.js"
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
"./css/theme.css": "./css/theme.css",
|
|
20
|
+
"./css/theme/*": "./css/theme/*",
|
|
21
|
+
"./lib/*": "./lib/*",
|
|
22
|
+
"./presets/*": "./presets/*",
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"NOTICE.md",
|
|
28
|
+
"bin/",
|
|
29
|
+
"commands/",
|
|
30
|
+
"configs/",
|
|
31
|
+
"css/",
|
|
32
|
+
"defaults/",
|
|
33
|
+
"emoji/",
|
|
34
|
+
"filters/",
|
|
35
|
+
"fonts/*.md",
|
|
36
|
+
"fonts/recipe.json",
|
|
37
|
+
"just/",
|
|
38
|
+
"lib/",
|
|
39
|
+
"presets/",
|
|
40
|
+
"profiles/",
|
|
41
|
+
"requirements.txt",
|
|
42
|
+
"rules/",
|
|
43
|
+
"templates/",
|
|
44
|
+
"toolchain.json",
|
|
45
|
+
"validators/"
|
|
46
|
+
],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=22"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"lint": "eslint .",
|
|
53
|
+
"setup:python": "python3 -m pip install -r requirements.txt"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@tailwindcss/cli": "^4.3.3",
|
|
57
|
+
"@tailwindcss/typography": "^0.5.20",
|
|
58
|
+
"@textlint-rule/textlint-rule-no-duplicate-abbr": "1.0.2",
|
|
59
|
+
"@textlint-rule/textlint-rule-no-unmatched-pair": "2.0.4",
|
|
60
|
+
"cssnano": "^9.1.0",
|
|
61
|
+
"js-yaml": "^5.4.1",
|
|
62
|
+
"markdownlint-cli2": "0.23.3",
|
|
63
|
+
"postcss": "^8.5.26",
|
|
64
|
+
"postcss-preset-env": "11.6.1",
|
|
65
|
+
"sharp": "^0.35.5",
|
|
66
|
+
"tailwindcss": "^4.3.3",
|
|
67
|
+
"textlint": "15.8.0",
|
|
68
|
+
"textlint-filter-rule-comments": "1.3.0",
|
|
69
|
+
"textlint-rule-abbr-within-parentheses": "1.0.2",
|
|
70
|
+
"textlint-rule-alex": "5.0.0",
|
|
71
|
+
"textlint-rule-common-misspellings": "1.0.1",
|
|
72
|
+
"textlint-rule-diacritics": "2.1.4",
|
|
73
|
+
"textlint-rule-doubled-spaces": "1.0.2",
|
|
74
|
+
"textlint-rule-en-capitalization": "2.0.3",
|
|
75
|
+
"textlint-rule-en-max-word-count": "2.0.1",
|
|
76
|
+
"textlint-rule-max-comma": "4.0.0",
|
|
77
|
+
"textlint-rule-no-start-duplicated-conjunction": "2.0.2",
|
|
78
|
+
"textlint-rule-sentence-length": "5.2.1",
|
|
79
|
+
"textlint-rule-stop-words": "5.2.0",
|
|
80
|
+
"textlint-rule-terminology": "5.2.16",
|
|
81
|
+
"textlint-rule-write-good": "2.0.0"
|
|
82
|
+
},
|
|
83
|
+
"devDependencies": {
|
|
84
|
+
"@eslint/js": "10.0.1",
|
|
85
|
+
"eslint": "10.13.0",
|
|
86
|
+
"globals": "17.13.0",
|
|
87
|
+
"vitest": "^5.0.2"
|
|
88
|
+
},
|
|
89
|
+
"overrides": {
|
|
90
|
+
"@tailwindcss/typography": {
|
|
91
|
+
"postcss-selector-parser": "^7.1.6"
|
|
92
|
+
},
|
|
93
|
+
"markdownlint-cli2": {
|
|
94
|
+
"smol-toml": "^1.9.0"
|
|
95
|
+
},
|
|
96
|
+
"micromark-extension-math": {
|
|
97
|
+
"katex": "^0.18.2"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
## business-nonfiction house style
|
|
2
|
+
|
|
3
|
+
These are the preset's conventions. `book-kit validate` and `just prose` enforce most of them; the rest are judgment calls for the writer and editor.
|
|
4
|
+
|
|
5
|
+
### TL;DRs and takeaways
|
|
6
|
+
|
|
7
|
+
- Every chapter opens with `::: {.tldr custom-style="tldr"}` right after its H2. It has one or two sentences that state the chapter's argument, not its topic.
|
|
8
|
+
- Section `*-key-takeaways.md` chapters collect the TL;DRs. After adding or editing a TL;DR, or changing `index.yml`, run `just key-takeaways`.
|
|
9
|
+
|
|
10
|
+
### Section openers
|
|
11
|
+
|
|
12
|
+
- A section's first file is an H1 with `{.section-opener}`, followed by a short `.lead` paragraph. It has no TL;DR.
|
|
13
|
+
|
|
14
|
+
### Role callouts
|
|
15
|
+
|
|
16
|
+
- Use `.for-managers` and `.for-ics` only where the advice genuinely differs by role, and give a chapter that has one the other as well.
|
|
17
|
+
- `.pro-tip` is for a concrete, actionable tip; `.common-objection` states a reader's pushback and answers it.
|
|
18
|
+
|
|
19
|
+
### Mechanics
|
|
20
|
+
|
|
21
|
+
- Acronyms: write them as `[API]{.smallcaps}`, and plurals as `[API]{.smallcaps}s`. Expand each one on first use, in book order.
|
|
22
|
+
- Dashes: em dashes (`—`) with no spaces for asides; en dashes (`–`) for number ranges.
|
|
23
|
+
- Quotes: periods and commas go inside quotation marks; colons and semicolons go outside. Use the serial comma.
|
|
24
|
+
- Footnotes get chapter-specific names (`[^style-guide]`, not `[^1]`), since chapters build into one document.
|
|
25
|
+
|
|
26
|
+
### Openings
|
|
27
|
+
|
|
28
|
+
- The first paragraph after the TL;DR shows the problem in action, states a provocative truth, asks the reader's question, or grounds the chapter in a specific experience.
|
|
29
|
+
- No throat-clearing ("In this chapter, we will explore…"), and no manufactured hypotheticals ("Picture this," "Imagine…," "Let's say…"). The hypothetical-hooks validator flags those.
|
|
30
|
+
- If the TL;DR already makes the point, skip the hook and start with the substance.
|
|
31
|
+
|
|
32
|
+
### Prose
|
|
33
|
+
|
|
34
|
+
- The kit's prose rules flag clichés, buzzwords, vague intensifiers, cultural idioms, assumptive phrasing ("simply," "of course"), and AI-sounding patterns. Read each finding in context: quoted bad examples and deliberate repetition aren't errors.
|
|
35
|
+
- Vary paragraph length and section shape. Bold-label bullet lists shouldn't stand in for an argument.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# business-nonfiction: the house style book-kit grew up with, for practical
|
|
2
|
+
# nonfiction aimed at managers and individual contributors. A book opts in with
|
|
3
|
+
#
|
|
4
|
+
# extends: business-nonfiction
|
|
5
|
+
#
|
|
6
|
+
# in book.config.yml. Keys here override the kit defaults; the book's own keys
|
|
7
|
+
# override these. Every key is documented in defaults/book.config.yml.
|
|
8
|
+
#
|
|
9
|
+
# Through 0.3 this was the kit default. Extending it reproduces that default
|
|
10
|
+
# exactly (tests/presets.test.js checks), so a book upgrading from 0.3 keeps
|
|
11
|
+
# its output by adding the line above.
|
|
12
|
+
|
|
13
|
+
# Six callouts: a TL;DR that opens every chapter, role-specific guidance for
|
|
14
|
+
# managers and individual contributors, and section takeaways. The two role
|
|
15
|
+
# callouts pair both ways: a chapter with one should have the other
|
|
16
|
+
# (the callout-balance validator).
|
|
17
|
+
callouts:
|
|
18
|
+
- class: tldr
|
|
19
|
+
label: "TL;DR:"
|
|
20
|
+
aria: Summary
|
|
21
|
+
- class: pro-tip
|
|
22
|
+
label: "Pro-Tip:"
|
|
23
|
+
aria: Pro tip
|
|
24
|
+
emoji: "💡"
|
|
25
|
+
emoji_cp: 1f4a1
|
|
26
|
+
emoji_alt: light bulb
|
|
27
|
+
- class: for-managers
|
|
28
|
+
label: "For managers:"
|
|
29
|
+
aria: Guidance for managers
|
|
30
|
+
pair_with: for-ics
|
|
31
|
+
emoji: "👔"
|
|
32
|
+
emoji_cp: 1f454
|
|
33
|
+
emoji_alt: necktie
|
|
34
|
+
- class: for-ics
|
|
35
|
+
label: "For individual contributors:"
|
|
36
|
+
aria: Guidance for individual contributors
|
|
37
|
+
pair_with: for-managers
|
|
38
|
+
emoji: "💻"
|
|
39
|
+
emoji_cp: 1f4bb
|
|
40
|
+
emoji_alt: laptop
|
|
41
|
+
- class: common-objection
|
|
42
|
+
label: "Common objection:"
|
|
43
|
+
aria: Common objection
|
|
44
|
+
emoji: "🤔"
|
|
45
|
+
emoji_cp: 1f914
|
|
46
|
+
emoji_alt: thinking
|
|
47
|
+
- class: takeaways
|
|
48
|
+
aria: Key takeaways
|
|
49
|
+
|
|
50
|
+
structure:
|
|
51
|
+
# Every chapter opens with a TL;DR.
|
|
52
|
+
required_callout: tldr
|
|
53
|
+
# Each section ends with a generated chapter collecting its TL;DRs
|
|
54
|
+
# (`just key-takeaways`).
|
|
55
|
+
takeaways_suffix: -key-takeaways
|
|
56
|
+
takeaways_skip_sections: [Front matter, Appendix, Conclusion, Back cover]
|
|
57
|
+
|
|
58
|
+
validators:
|
|
59
|
+
options:
|
|
60
|
+
# The short name callout-balance warnings use for .for-ics.
|
|
61
|
+
callout-balance:
|
|
62
|
+
names: { for-ics: For ICs }
|
|
63
|
+
# The house-style checks the kit defaults turn off, back on at each
|
|
64
|
+
# validator's own severity.
|
|
65
|
+
severity:
|
|
66
|
+
section-intros: "on"
|
|
67
|
+
section-intro-no-tldr: "on"
|
|
68
|
+
section-intro-has-lead: "on"
|
|
69
|
+
section-intro-final-lead: "on"
|
|
70
|
+
no-transition-todos: "on"
|
|
71
|
+
min-opener-length: "on"
|
|
72
|
+
chapter-lengths: "on"
|
|
73
|
+
smallcaps: "on"
|
|
74
|
+
escaped-smallcaps: "on"
|
|
75
|
+
callout-balance: "on"
|
|
76
|
+
tldr-length: "on"
|
|
77
|
+
acronym-expansion: "on"
|
|
78
|
+
em-dashes: "on"
|
|
79
|
+
number-range-dashes: "on"
|
|
80
|
+
quote-punctuation: "on"
|
|
81
|
+
sentence-starters: "on"
|
|
82
|
+
list-punctuation: "on"
|
|
83
|
+
bold-keywords: "on"
|
|
84
|
+
hypothetical-hooks: "on"
|
|
85
|
+
paragraph-length: "on"
|
|
86
|
+
|
|
87
|
+
lint:
|
|
88
|
+
# Prose-style textlint settings on top of the kit's mechanical base.
|
|
89
|
+
textlint:
|
|
90
|
+
rules:
|
|
91
|
+
write-good:
|
|
92
|
+
passive: false
|
|
93
|
+
so: false
|
|
94
|
+
weasel: false
|
|
95
|
+
thereIs: true
|
|
96
|
+
tooWordy: false
|
|
97
|
+
adverb: false
|
|
98
|
+
cliches: true
|
|
99
|
+
eprime: false
|
|
100
|
+
terminology:
|
|
101
|
+
defaultTerms: false
|
|
102
|
+
exclude: [HTML, ID, URL, URLs]
|
|
103
|
+
terms: [[Github, GitHub], [Javascript, JavaScript], [Typescript, TypeScript], [e-mail, email], [on-line, online], [web site, website]]
|
|
104
|
+
sentence-length:
|
|
105
|
+
max: 350
|
|
106
|
+
en-max-word-count:
|
|
107
|
+
max: 80
|
|
108
|
+
stop-words:
|
|
109
|
+
defaultWords: false
|
|
110
|
+
# Inclusive-language checks, with words that are fine in business prose
|
|
111
|
+
# (and in this style's own advice) allowed.
|
|
112
|
+
alex:
|
|
113
|
+
allow: [actors-actresses, adult, adults, amateur, angry, attack, baby, ball, bigger, blind, bomb, burn, burns, buried, clearly, crack, cracks, dad-mom, damn, daughter-son, daughters-sons, dead, deposit, desire, destroys, devil, die, dirty, dive, doom, easy, enemy, everyone-knows, failed, failure, failures, faith, fear, fellowship, fight, fire, firing, gal-guy, god, hang, harder, he-she, her-him, hero-heroine, hijacking, hole, host-hostess, just, kids, kill, kills, lie, lies, master, master-key, newsman-newswoman, nuts, obvious, of-course, period, premature, radical, reject, remains, shoot, sick, simple, special, straightforward, suck, sucks, trap, traps, tribe]
|
|
114
|
+
profanitySureness: 2
|
|
115
|
+
max-comma:
|
|
116
|
+
max: 6
|
|
117
|
+
# All six of the kit's house-style textlint rules.
|
|
118
|
+
rules:
|
|
119
|
+
no-ai-like-patterns: true
|
|
120
|
+
no-assumptive: true
|
|
121
|
+
no-bare-acronyms: true
|
|
122
|
+
no-cliches: true
|
|
123
|
+
no-cultural-idioms: true
|
|
124
|
+
no-vague-intensifiers: true
|
|
125
|
+
markdownlint:
|
|
126
|
+
proper-names:
|
|
127
|
+
names: [GitHub, JavaScript, TypeScript, Markdown, README]
|
|
128
|
+
code_blocks: false
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
%!PS-Adobe-3.0
|
|
2
|
+
% PDF/X-1a:2001 output intent definition for Ghostscript
|
|
3
|
+
%
|
|
4
|
+
% This file defines the output intent metadata required by the PDF/X-1a:2001
|
|
5
|
+
% standard. It references the USWebCoatedSWOP.icc CMYK ICC profile, which is
|
|
6
|
+
% the standard profile for U.S. web offset printing.
|
|
7
|
+
%
|
|
8
|
+
% Used by: commands/convert-pdfx.sh via Ghostscript
|
|
9
|
+
% See also: commands/download-icc-profile.sh for obtaining the ICC profile
|
|
10
|
+
%
|
|
11
|
+
% The pdfmark sequence follows Ghostscript's PDFX_def.ps example (see
|
|
12
|
+
% NOTICE.md).
|
|
13
|
+
%
|
|
14
|
+
% The __ICC_PROFILE_PATH__ and __TITLE__ placeholders are replaced at runtime by
|
|
15
|
+
% convert-pdfx.sh (the title is PostScript-escaped there).
|
|
16
|
+
|
|
17
|
+
% Document info dictionary
|
|
18
|
+
[ /GTS_PDFXVersion (PDF/X-1a:2001)
|
|
19
|
+
/Title (__TITLE__)
|
|
20
|
+
/Trapped /False
|
|
21
|
+
/DOCINFO pdfmark
|
|
22
|
+
|
|
23
|
+
% ICC profile path (placeholder substituted at build time)
|
|
24
|
+
/ICCProfile (__ICC_PROFILE_PATH__) def
|
|
25
|
+
|
|
26
|
+
% Create a stream object for the ICC profile
|
|
27
|
+
[/_objdef {icc_PDFX} /type /stream /OBJ pdfmark
|
|
28
|
+
[{icc_PDFX} << /N 4 >> /PUT pdfmark
|
|
29
|
+
[{icc_PDFX} ICCProfile (r) file /PUT pdfmark
|
|
30
|
+
|
|
31
|
+
% Output intent dictionary
|
|
32
|
+
[/_objdef {OutputIntent_PDFX} /type /dict /OBJ pdfmark
|
|
33
|
+
[{OutputIntent_PDFX} <<
|
|
34
|
+
/Type /OutputIntent
|
|
35
|
+
/S /GTS_PDFX
|
|
36
|
+
/OutputCondition (U.S. Web Coated \(SWOP\) v2)
|
|
37
|
+
/OutputConditionIdentifier (CGATS TR 001)
|
|
38
|
+
/RegistryName (http://www.color.org)
|
|
39
|
+
/Info (U.S. Web Coated \(SWOP\) v2)
|
|
40
|
+
/DestOutputProfile {icc_PDFX}
|
|
41
|
+
>> /PUT pdfmark
|
|
42
|
+
[{Catalog} << /OutputIntents [ {OutputIntent_PDFX} ] >> /PUT pdfmark
|
package/requirements.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Python build dependencies. Versions must match toolchain.json (the single
|
|
2
|
+
# source of truth); tests/toolchain.test.js enforces it.
|
|
3
|
+
# weasyprint - renders the print PDF (drives pagination: bump deliberately)
|
|
4
|
+
# pikepdf - PDF/X-1a conversion (convert-pdfx)
|
|
5
|
+
# Pillow - emoji compositing (composite-*-emoji)
|
|
6
|
+
# python-docx - DOCX reference template (generate-reference-docx)
|
|
7
|
+
# fonttools - subsets the upstream fonts at build time (book-kit fonts)
|
|
8
|
+
# brotli - WOFF2 compression for fonttools
|
|
9
|
+
weasyprint==70.0
|
|
10
|
+
pikepdf==10.10.0
|
|
11
|
+
Pillow==12.3.0
|
|
12
|
+
python-docx==1.2.0
|
|
13
|
+
fonttools==4.67.0
|
|
14
|
+
brotli==1.2.0
|