1
0
mirror of https://github.com/twirl/The-API-Book.git synced 2025-05-25 22:08:06 +02:00

Builder updated

This commit is contained in:
Sergey Konstantinov 2021-11-15 21:28:32 +03:00
parent a759c4ec26
commit 5b4eaaf292
20 changed files with 901 additions and 1027 deletions

142
build.js
View File

@ -1,142 +0,0 @@
const fs = require('fs');
const path = require('path');
const templates = require('./src/templates');
const builders = require('./src/lib/builders');
const mdHtml = require('./src/lib/md-html');
const htmlProcess = require('./src/lib/html-process');
const l10n = {
en: require('./src/en/l10n.json'),
ru: require('./src/ru/l10n.json')
};
const langsToBuild = process.argv[2] &&
process.argv[2].split(',').map((s) => s.trim()) ||
['ru', 'en'];
const targets = (process.argv[3] &&
process.argv[3].split(',') ||
['html', 'pdf', 'epub']
).reduce((targets, arg) => {
targets[arg.trim()] = true;
return targets;
}, {});
buildDocs(langsToBuild, targets, l10n).then(() => {
console.log('Done!');
process.exit(0);
}, (e) => {
console.error(e);
process.exit(255);
});
function buildDocs (langsToBuild, targets, l10n) {
console.log(`Building in following languages: ${
langsToBuild.join(', ')
}, targets: ${
Object.keys(targets).join(', ')
}`);
return Promise.all(
langsToBuild.map((lang) => buildDoc(lang, targets, l10n[lang]))
);
}
async function buildDoc (lang, targets, l10n) {
const pageBreak = templates.pageBreak;
const structure = await getStructure({
path: `./src/${lang}/clean-copy/`,
l10n,
pageBreak
});
const tableOfContents = templates.toc(structure, l10n);
const htmlContent = [
structure.frontPage,
tableOfContents,
...structure.sections
.map((section) => section.chapters.reduce((content, chapter) => {
if (chapter.title) {
content.push(templates.chapterTitle(chapter));
}
content.push(chapter.content);
return content;
}, [templates.sectionTitle(section)]).join(''))
];
return Promise.all(['html', 'pdf', 'epub'].map((target) => {
if (targets[target]) {
return prepareHtml(htmlContent.join(''), l10n, target).then((html) => {
return builders[target]({
lang,
structure,
html,
l10n,
path: path.join(__dirname, 'docs', `API.${lang}.${target}`)
});
});
} else {
return Promise.resolve();
}
}));
}
async function getStructure ({ path, l10n, pageBreak}) {
const structure = {
frontPage: fs.readFileSync(`${path}intro.html`, 'utf-8') + pageBreak,
sections: []
};
let counter = 1;
await fs.readdirSync(path)
.filter((p) => fs.statSync(`${path}${p}`).isDirectory())
.sort()
.reduce(async (p, dir, index) => {
const structure = await p;
const name = dir.split('-')[1];
const section = {
title: name,
anchor: `section-${index + 1}`,
chapters: []
}
const subdir = `${path}${dir}/`;
await fs.readdirSync(subdir)
.filter((p) => fs.statSync(`${subdir}${p}`).isFile() && p.indexOf('.md') == p.length - 3)
.sort()
.reduce(async (p, file) => {
const section = await p;
const md = fs.readFileSync(`${subdir}${file}`, 'utf-8').trim();
const content = await mdHtml(md, {
counter,
l10n,
base: __dirname
});
section.chapters.push({
anchor: content.data.anchor,
title: content.data.title,
content: content.contents + pageBreak
});
counter++;
return section;
}, Promise.resolve(section));
structure.sections.push(section);
return structure;
}, Promise.resolve(structure));
return structure;
}
async function prepareHtml (content, l10n, target) {
if (target == 'epub') {
return '';
} else {
return (await htmlProcess(
templates[target == 'html' ? 'screenHtml' : 'printHtml'](content, l10n), {
base: __dirname
}
)).contents;
}
}

61
build.mjs Normal file
View File

@ -0,0 +1,61 @@
import { resolve as pathResolve } from 'path';
import templates from './src/templates.js';
import { init, plugins } from '../The-Book-Builder/index.js';
import { readFileSync } from 'fs';
const l10n = {
en: JSON.parse(readFileSync('./src/en/l10n.json', 'utf-8')),
ru: JSON.parse(readFileSync('./src/ru/l10n.json', 'utf-8'))
};
const langsToBuild = (process.argv[2] &&
process.argv[2].split(',').map((s) => s.trim())) || ['ru', 'en'];
const targets = (
(process.argv[3] && process.argv[3].split(',')) || ['html', 'pdf', 'epub']
).reduce((targets, arg) => {
targets[arg.trim()] = true;
return targets;
}, {});
console.log(`Building langs: ${langsToBuild.join(', ')}`);
langsToBuild.forEach((lang) => {
init({
l10n: l10n[lang],
basePath: pathResolve(`src`),
path: pathResolve(`src/${lang}/clean-copy`),
templates,
pipeline: {
css: {
beforeAll: [
plugins.css.backgroundImageDataUri,
plugins.css.fontFaceDataUri
]
},
ast: {
preProcess: [
plugins.ast.h3ToTitle,
plugins.ast.h5Counter,
plugins.ast.aImg,
plugins.ast.imgSrcResolve,
plugins.ast.ref,
plugins.ast.ghTableFix
]
},
htmlSourceValidator: {
validator: 'WHATWG',
ignore: ['heading-level', 'no-raw-characters', 'wcag/h37']
},
html: {
postProcess: [plugins.html.imgDataUri]
}
}
}).then((builder) => {
Object.keys(targets).forEach((target) => {
builder.build(
target,
pathResolve(`docs/${l10n[lang].file}.${lang}.${target}`)
);
});
});
});

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -5,17 +5,9 @@
"author": "Sergey Konstantinov <twirl-team@yandex.ru>",
"repository": "github.com:twirl/The-API-Book",
"devDependencies": {
"css": "^3.0.0",
"epub-gen": "^0.1.0",
"image-data-uri": "^2.0.1",
"puppeteer": "^5.5.0",
"rehype-parse": "^7.0.1",
"rehype-stringify": "^8.0.0",
"remark-parse": "^8.0.3",
"remark-rehype": "^7.0.0",
"unified": "^9.2.0"
"@twirl/book-builder": "0.0.9"
},
"scripts": {
"build": "node build.js"
"build": "node build.mjs"
}
}

View File

@ -1,4 +1,5 @@
html, body {
html,
body {
margin: 0;
padding: 0;
font-size: 14pt;
@ -8,11 +9,17 @@ html, body {
display: none;
}
pre, img {
pre,
img {
margin-right: 0.1em;
}
h1, h2, h3, h4, h5, h6 {
h1,
h2,
h3,
h4,
h5,
h6 {
page-break-after: never;
}
@ -28,7 +35,7 @@ img {
}
.cover {
background-image: url(./src/cover_300dpi.png);
background-image: url(/cover_300dpi.png);
background-size: 100% auto;
background-repeat: no-repeat;
background-position: 50% 100%;
@ -48,7 +55,9 @@ h1 {
margin: 0;
}
p, ul, ol {
p,
ul,
ol {
orphans: 4;
}
@ -60,4 +69,4 @@ p, ul, ol {
@page:first {
margin: 0;
size: 7.5in 10in;
}
}

View File

@ -1,6 +1,6 @@
h1 {
font-size: 48px;
background-image: url(./src/cover_96dpi.png);
background-image: url(/cover_96dpi.png);
background-repeat: no-repeat;
background-position: 50% 100%;
height: 1056px;
@ -9,4 +9,4 @@ h1 {
margin: 0 auto;
padding-top: 4em;
padding-left: 2em;
}
}

View File

@ -4,7 +4,8 @@ html {
padding: 0;
}
body, h6 {
body,
h6 {
font-family: 'PT Serif';
font-size: 14pt;
text-align: justify;
@ -24,7 +25,8 @@ body, h6 {
clear: left;
}
code, pre {
code,
pre {
font-family: Inconsolata, sans-serif;
}
@ -32,17 +34,17 @@ code {
white-space: nowrap;
}
p img {
.img-wrapper img {
max-width: 100%;
}
pre {
margin: 1em 0;
padding: 1em;
border-radius: .25em;
border-top: 1px solid rgba(0,0,0,.45);
border-left: 1px solid rgba(0,0,0,.45);
box-shadow: .1em .1em .1em rgba(0,0,0,.45);
border-radius: 0.25em;
border-top: 1px solid rgba(0, 0, 0, 0.45);
border-left: 1px solid rgba(0, 0, 0, 0.45);
box-shadow: 0.1em 0.1em 0.1em rgba(0, 0, 0, 0.45);
page-break-inside: avoid;
overflow-x: auto;
font-size: 90%;
@ -50,7 +52,7 @@ pre {
img:not(.cc-by-nc-img) {
border: 1px solid darkgray;
box-shadow: .1em .1em .1em rgba(0,0,0,.45);
box-shadow: 0.1em 0.1em 0.1em rgba(0, 0, 0, 0.45);
}
pre code {
@ -69,7 +71,11 @@ a:hover {
text-decoration: underline;
}
h1, h2, h3, h4, h5 {
h1,
h2,
h3,
h4,
h5 {
text-align: left;
font-family: 'PT Sans';
font-weight: bold;
@ -104,7 +110,8 @@ h3 {
font-variant: small-caps;
}
h4, h5 {
h4,
h5 {
font-size: 120%;
}
@ -154,15 +161,17 @@ a.anchor {
h5 a.anchor:before {
color: transparent;
}
a.anchor:hover:before {
color: black;
}
h2:not(.toc), h3, h5 {
h2:not(.toc),
h3,
h5 {
text-indent: -0.8em;
}
@keyframes octocat-wave {
0%,
100% {
@ -192,7 +201,8 @@ a.anchor {
padding: 0.2em;
}
ul, ol {
ul,
ol {
padding-left: 1em;
}

View File

@ -14,8 +14,7 @@ In other words, hundreds or even thousands of different APIs must work correctly
When I'm asked of an example of a well-designed API, I usually show the picture of a Roman aqueduct:
![The Pont-du-Gard aqueduct. Built in the 1st century AD](/src/img/pont-du-gard.jpg "The Pont-du-Gard aqueduct. Built in the 1st century AD")
###### Photo credit: [igorelick @ pixabay](https://pixabay.com/photos/pont-du-gard-france-aqueduct-bridge-3909998/)
[![igorelick @ pixabay](/img/pont-du-gard.jpg "The Pont-du-Gard aqueduct. Built in the 1st century AD")](https://pixabay.com/photos/pont-du-gard-france-aqueduct-bridge-3909998/)
* it interconnects two areas;
* backwards compatibility being broken not a single time in two thousand years.

View File

@ -225,7 +225,7 @@ GET /v1/orders/{id}
{ "order_id", "status" … }
```
```
// Returns all customers's orders
// Returns all customer's orders
// in all statuses
GET /v1/users/{id}/orders
```

View File

@ -39,7 +39,7 @@ Saying nothing about Fielding's loose interpretation of his own dissertation, le
#### REST: The Good Part
We don't actually know why of all overviews of abstract network-based architectures the Fielding's one became the most widely known. But it's clearly obvious that Fielding's theory being reflected in minds of millions of developers (including Fielding's own) morphed into a whole engineering subculture. Out of reducing the REST abstractions to the HTTP protocol and the URL standard, the chimaera of ‘RESTful API’ was born — the one which [nobody knowns the exact sense of](https://restfulapi.net/).
We don't actually know why of all overviews of abstract network-based architectures the Fielding's one became the most widely known. But it's clearly obvious that Fielding's theory being reflected in minds of millions of developers (including Fielding's own) morphed into a whole engineering subculture. Out of reducing the REST abstractions to the HTTP protocol and the URL standard, the chimaera of ‘RESTful API’ was born — the one which [nobody knows the exact sense of](https://restfulapi.net/).
Are we saying that REST concept is meaningless? Not at all. We were just trying to demonstrate that it allows for too loose interpretation, which is simultaneously its main power and its main flaw.
@ -61,17 +61,17 @@ Why we say this is ‘right’? Because modern client-server interaction stack i
(Actually, with regards to many technical aspects interim agents are taking many opportunities, not asking the developers about them. For example, freely changing `Accept-Encoding` and therefore `Content-Length` while proxying requests and responses.)
Every REST principle, named by Fielding, allows for making interim software work better. The stateless paradigm is a key: proxies might be sure that request's metadata describe it unambiguosly.
Every REST principle, named by Fielding, allows for making interim software work better. The stateless paradigm is a key: proxies might be sure that request's metadata describe it unambiguously.
Let's explore a simple example. Imagine we have operations for getting and deleting user's profile in our system. We may organize them in different ways. For example, like this:
```
// Get user's profile
GET /me
Cookie: session_id=<идентификатор сессии>
Cookie: session_id=<session identifier>
// Delete user's profile
GET /delete-me
Cookie: session_id=<идентификатор сессии>
Cookie: session_id=<session identifier>
```
Why this solution is defective from the interim agent's point of view?

View File

@ -6,5 +6,7 @@
"frontPage": "Front Page",
"description": "Designing APIs is a very special skill: API is a multiplier to both your opportunities and mistakes. This book is written to share the expertise and describe the best practices in the API design. The book comprises three large sections. In Section I we'll discuss designing APIs as a concept: how to build the architecture properly, from a high-level planning down to final interfaces. Section II is dedicated to an API's lifecycle: how interfaces evolve over time, and how to elaborate the product to match users' needs. Finally, Section III is more about un-engineering sides of the API, like API marketing, organizing support, and working with a community.",
"locale": "en_US",
"url": "https://twirl.github.io/The-API-Book/docs/API.en.html"
}
"file": "API",
"url": "https://twirl.github.io/The-API-Book/docs/API.en.html",
"imageCredit": "Image Credit"
}

View File

@ -14,8 +14,7 @@
Когда меня просят привести пример хорошего API, я обычно показываю фотографию древнеримского акведука:
![Древнеримский акведук Пон-дю-Гар. Построен в I веке н.э.](/src/img/pont-du-gard.jpg "Древнеримский акведук Пон-дю-Гар. Построен в I веке н.э.")
###### Photo credit: [igorelick @ pixabay](https://pixabay.com/photos/pont-du-gard-france-aqueduct-bridge-3909998/)
[![igorelick @ pixabay](/img/pont-du-gard.jpg "Древнеримский акведук Пон-дю-Гар. Построен в I веке н.э.")](https://pixabay.com/photos/pont-du-gard-france-aqueduct-bridge-3909998/)
* он связывает между собой две области
* обратная совместимость нарушена ноль раз за последние две тысячи лет.

View File

@ -6,5 +6,7 @@
"frontPage": "Титульный лист",
"description": "Разработка API — особый навык: API является как мультипликатором ваших возможностей, так и мультипликатором ваших ошибок. Эта книга написана для того, чтобы поделиться опытом и изложить лучшие практики проектирования API. Книга состоит из трёх больших разделов. В первом разделе мы поговорим о проектировании API на стадии разработки концепции — как грамотно выстроить архитектуру, от крупноблочного планирования до конечных интерфейсов. Второй раздел будет посвящён жизненному циклу API — как интерфейсы эволюционируют со временем и как развивать продукт так, чтобы отвечать потребностям пользователей. Наконец, третий раздел будет касаться больше не-разработческих сторон жизни API — поддержки, маркетинга, работы с комьюнити.",
"locale": "ru_RU",
"url": "https://twirl.github.io/The-API-Book/docs/API.ru.html"
}
"file": "API",
"url": "https://twirl.github.io/The-API-Book/docs/API.ru.html",
"imageCredit": "Image Credit"
}

View File

@ -1,79 +1,3 @@
const fs = require('fs');
const path = require('path');
const css = fs.readFileSync(path.resolve(__dirname, 'style.css'), 'utf-8');
const cssProcess = require('./lib/css-process');
const printCss = cssProcess(fs.readFileSync(path.resolve(__dirname, 'print.css'), 'utf-8'));
const screenCss = cssProcess(fs.readFileSync(path.resolve(__dirname, 'screen.css'), 'utf-8'));
const templates = module.exports = {
screenHtml: (html, l10n) => {
return `<html><head>
<meta charset="utf-8"/>
<title>${l10n.author}. ${l10n.title}</title>
<meta name="author" content="${l10n.author}"/>
<meta name="description" content="${l10n.description}"/>
<meta property="og:title" content="${l10n.author}. ${l10n.title}"/>
<meta property="og:url" content="${l10n.url}"/>
<meta property="og:type" content="article"/>
<meta property="og:description" content="${l10n.description}"/>
<meta property="og:locale" content="${l10n.locale}"/>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=PT+Serif&amp;family=PT+Sans&amp;family=Inconsolata"/>
<style>${css}</style>
<style>${screenCss}</style>
</head><body>
<article>
${html}
</article>
</body></html>`;
},
printHtml: (html, l10n) => {
return `<html><head>
<meta charset="utf-8"/>
<title>${l10n.author}. ${l10n.title}</title>
<meta name="author" content="${l10n.author}"/>
<meta name="description" content="${l10n.description}"/>
<meta property="og:title" content="${l10n.author}. ${l10n.title}"/>
<meta property="og:url" content="${l10n.url}"/>
<meta property="og:type" content="article"/>
<meta property="og:description" content="${l10n.description}"/>
<meta property="og:locale" content="${l10n.locale}"/>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=PT+Serif&amp;family=PT+Sans&amp;family=Inconsolata"/>
<style>${css}</style>
<style>${printCss}</style>
</head><body>
<article>
${html}
</article>
</body></html>`;
},
toc: (structure, l10n) => {
return `<nav><h2 class="toc">${l10n.toc}</h2><ul class="table-of-contents">${
structure.sections.map((section) => {
return `<li><a href="#${section.anchor}">${section.title}</a><ul>${
section.chapters.map((chapter) => {
return `<li><a href="#${chapter.anchor}">${chapter.title}</a></li>`
}).join('')
}</ul></li>`;
}).join('')
}</ul></nav>${templates.pageBreak}`
},
ref: (anchor, content) => {
return `<a href="#${anchor}" class="anchor" name="${anchor}">${content}</a>`;
},
sectionTitle: (section) => {
return section.title ?
`<h2>${templates.ref(section.anchor, section.title)}</h2>` :
'';
},
chapterTitle: (chapter) => {
return `<h3>${templates.ref(chapter.anchor, chapter.title)}</h3>`;
},
const templates = (module.exports = {
pageBreak: '<div class="page-break"></div>'
};
});