Skip to content

Commit 98b7ca4

Browse files
authored
Templatize readme (#193)
* Templatize readme * Fix order of build
1 parent 1f07a44 commit 98b7ca4

File tree

6 files changed

+183
-67
lines changed

6 files changed

+183
-67
lines changed

README.md

Lines changed: 56 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
# Safe Units
22

3-
[![Build Status](https://travis-ci.org/jscheiny/safe-units.svg?branch=master)](https://travis-ci.org/jscheiny/safe-units) [![NPM Version](https://img.shields.io/npm/v/safe-units.svg)](https://www.npmjs.com/package/safe-units) [![MIT License](https://img.shields.io/npm/l/safe-units.svg)](https://github.com/jscheiny/safe-units/blob/master/LICENSE)
3+
[![NPM Version](https://img.shields.io/npm/v/safe-units.svg)](https://www.npmjs.com/package/safe-units) [![MIT License](https://img.shields.io/npm/l/safe-units.svg)](https://github.com/jscheiny/safe-units/blob/master/LICENSE)
44

55
Safe Units is a type safe library for using units of measurement in TypeScript. Safe Units provides an implementation of an SI based unit system but is flexible enough to allow users to create their own unit systems which can be independent or can interoperate with the built-in units. Users can also make unit systems for any numeric type they'd like not just the JavaScript `number` type. This library requires TypeScript 3.2 or higher.
66

7-
```typescript
7+
```ts
88
import { Length, Measure, meters, seconds, Time, Velocity } from "safe-units";
99

1010
const length: Length = Measure.of(30, meters);
1111
const time: Time = Measure.of(15, seconds);
1212
const velocity: Velocity = length.over(time);
1313

14-
console.log(length.toString()); // 30 m
15-
console.log(time.toString()); // 15 s
14+
console.log(length.toString()); // 30 m
15+
console.log(time.toString()); // 15 s
1616
console.log(velocity.toString()); // 2 m * s^-1
1717

18+
// @ts-expect-error ERROR: A measure of m*s isn't assignable to a measure of m/s.
1819
const error: Velocity = length.times(time);
19-
// ERROR: A measure of m*s isn't assignable to a measure of m/s.
2020
```
2121

2222
## Features
2323

2424
  Compile-time unit arithmetic for typesafe dimensional analysis (with exponents between -5 and +5)!
2525

26-
  Large library of predefined units including metric (with prefixes), Imperial, and US customary units!
26+
  Large library of predefined units including SI (with prefixes), Imperial, and US customary units!
2727

2828
  Ability to add your own unit system that can work with built-in units!
2929

@@ -50,15 +50,14 @@ yarn add safe-units
5050

5151
## Examples
5252

53-
### Unit arithmetic
53+
### Unit Arithmetic
5454

55-
```typescript
55+
```ts
5656
import { bars, kilograms, Measure, meters, milli, seconds } from "safe-units";
5757

5858
const width = Measure.of(3, meters);
5959
const height = Measure.of(4, meters);
6060
const area = width.times(height).scale(0.5);
61-
const hypot = Measure.sqrt(width.squared().plus(height.squared())); // 5 m
6261

6362
const mass = Measure.of(30, kilograms);
6463
const mps2 = meters.per(seconds.squared());
@@ -67,73 +66,87 @@ const acceleration = Measure.of(9.8, mps2);
6766
const force = mass.times(acceleration); // 294 N
6867
const pressure = force.over(area); // 49 Pa
6968
const maxPressure = Measure.of(0.5, milli(bars)); // 0.5 mbar
70-
pressure.lt(maxPressure) // true
69+
pressure.lt(maxPressure); // true
70+
7171
```
7272

73-
### Type errors
73+
### Type Errors
7474

75-
```typescript
76-
import { Force, Length, Measure, meters, seconds, Time } from "safe-units";
75+
```ts
76+
import { Force, hours, Length, Measure, meters, seconds, Time } from "safe-units";
7777

7878
const length: Length = Measure.of(10, meters);
7979
const time: Time = Measure.of(10, seconds);
8080

81+
// @ts-expect-error - Measures of different units cannot be added
8182
length.plus(time);
82-
// ERROR: Measures of different units cannot be added
8383

84-
length.minus(time);
85-
// ERROR: Measures of different units cannot be subtracted
84+
// @ts-expect-error - Measures of different units cannot be compared
85+
length.eq(time);
8686

87+
// @ts-expect-error - Measure of m/s is not assigneable to measure of kg*m/s^2
8788
const force: Force = length.over(time);
88-
// ERROR: Measure of m/s is not assignable to measure of kg*m/s^2
8989

90-
const root = Measure.sqrt(length);
91-
// ERROR: Can't take sqrt of measure of m since it's not a perfect square
90+
// @ts-expect-error - Cannot convert length measure to time measure
91+
length.in(hours);
92+
9293
```
9394

94-
### Naming units
95+
### Naming Units
9596

96-
```typescript
97-
import { days, Measure, miles, speedOfLight, yards } from "safe-units";
97+
```ts
98+
import { days, Measure, mega, micro, miles, speedOfLight, yards } from "safe-units";
9899

99100
const furlongs = Measure.of(220, yards, "fur");
100101

101-
console.log(Measure.of(8, furlongs).in(miles)); // 1 mi
102-
console.log(Measure.of(1, miles).in(furlongs)); // 8 fur
102+
Measure.of(8, furlongs).in(miles); // "1 mi"
103+
Measure.of(1, miles).in(furlongs); // "8 fur"
103104

104105
const fortnights = Measure.of(14, days, "ftn");
105-
const megaFurlongsPerMicroFortnight = mega(furlongs)
106-
.per(micro(fortnights))
107-
.withSymbol("Mfur/µftn");
106+
const megafurlong = mega(furlongs);
107+
const microfortnight = micro(fortnights);
108+
const mfPerUFtn = megafurlong.per(microfortnight).withSymbol("Mfur/µftn");
109+
110+
speedOfLight.in(mfPerUFtn); // "1.8026174997852542 Mfur/µftn"
108111

109-
console.log(speedOfLight.in(megaFurlongsPerMicroFortnight)); // 1.8026174997852542 Mfur/µftn
110112
```
111113

112-
### Deriving quantities
114+
### Deriving Quantities
113115

114-
```typescript
115-
import { Acceleration, Measure, meters, seconds, Time } from "safe-units";
116+
```ts
117+
import { kilograms, liters, Mass, Measure, Volume } from "safe-units";
116118

117-
const Jerk = Acceleration.over(Time);
118-
type Jerk = typeof Jerk;
119+
const VolumeDensity = Mass.over(Volume);
120+
type VolumeDensity = typeof VolumeDensity;
119121

120-
const mps2 = meters.per(seconds.squared());
121-
const acceleration = Measure.of(9.8, mps2);
122-
const jerk: Jerk = acceleration.over(Measure.of(2, seconds));
122+
const mass = Measure.of(30, kilograms);
123+
const volume = Measure.of(3, liters);
124+
125+
const density: VolumeDensity = mass.over(volume);
126+
127+
console.log(density.toString()); // 10 kg * m^-3
123128

124-
console.log(jerk.toString()); // 4.9 m * s^-3
125129
```
126130

127-
### Defining dimensions
131+
### Defining Unit Systems
132+
133+
```ts
134+
import { Measure, UnitSystem } from "safe-units";
128135

129-
```typescript
130-
import { Area, Measure, minutes, seconds, Time } from "safe-units";
136+
const GameUnitSystem = UnitSystem.from({
137+
frames: "fr",
138+
time: "s",
139+
});
131140

132-
const frames = Measure.dimension("frames");
141+
const frames = Measure.dimension(GameUnitSystem, "frames");
142+
const seconds = Measure.dimension(GameUnitSystem, "time");
133143

134144
const Frames = frames;
135145
type Frames = typeof frames;
136146

147+
const Time = seconds;
148+
type Time = typeof seconds;
149+
137150
const FrameRate = Frames.over(Time);
138151
type FrameRate = typeof FrameRate;
139152

@@ -142,10 +155,11 @@ const fps: FrameRate = frames.per(seconds).withSymbol("fps");
142155
const minFrameRate = Measure.of(60, fps);
143156

144157
const measuredFrames = Measure.of(8000, frames);
145-
const elapsedTime = Measure.of(2, minutes);
158+
const elapsedTime = Measure.of(120, seconds);
146159
const measuredFps: FrameRate = measuredFrames.over(elapsedTime);
147160

148161
if (measuredFps.lt(minFrameRate)) {
149162
// Optimize
150163
}
164+
151165
```

docs/readme/readme.template.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Safe Units
2+
3+
[![NPM Version](https://img.shields.io/npm/v/safe-units.svg)](https://www.npmjs.com/package/safe-units) [![MIT License](https://img.shields.io/npm/l/safe-units.svg)](https://github.com/jscheiny/safe-units/blob/master/LICENSE)
4+
5+
Safe Units is a type safe library for using units of measurement in TypeScript. Safe Units provides an implementation of an SI based unit system but is flexible enough to allow users to create their own unit systems which can be independent or can interoperate with the built-in units. Users can also make unit systems for any numeric type they'd like not just the JavaScript `number` type. This library requires TypeScript 3.2 or higher.
6+
7+
example: intro.ts
8+
9+
## Features
10+
11+
  Compile-time unit arithmetic for typesafe dimensional analysis (with exponents between -5 and +5)!
12+
13+
  Large library of predefined units including SI (with prefixes), Imperial, and US customary units!
14+
15+
  Ability to add your own unit system that can work with built-in units!
16+
17+
  Long build times & cryptic error messages!
18+
19+
## Prerequisites
20+
21+
Safe units is written in TypeScript and should be consumed by TypeScript users to take full advantage of what it provides. In addition you will need the following:
22+
23+
- [TypeScript](http://www.typescriptlang.org/) 3.2 or later
24+
- [Strict null checks](https://www.typescriptlang.org/docs/handbook/compiler-options.html) enabled for your project
25+
26+
## Installation
27+
28+
```
29+
npm install safe-units
30+
```
31+
32+
or
33+
34+
```
35+
yarn add safe-units
36+
```
37+
38+
## Examples
39+
40+
### Unit Arithmetic
41+
42+
example: introUnitArithmetic.ts
43+
44+
### Type Errors
45+
46+
example: introTypeErrors.ts
47+
48+
### Naming Units
49+
50+
example: introNamingUnits.ts
51+
52+
### Deriving Quantities
53+
54+
example: introDerivingQuantities.ts
55+
56+
### Defining Unit Systems
57+
58+
example: introUnitSystem.ts

docsgen/example.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { existsSync, readFileSync } from "fs";
2+
import { join } from "path";
3+
import { exit } from "process";
4+
5+
const EXAMPLE_START_REGEX = /^\/\/\s+start\s*/i;
6+
const EXAMPLE_END_REGX = /^\/\/\s+end\s*/i;
7+
8+
export function readExample(fileName: string) {
9+
const examplePath = join("docs", "examples", fileName.trim());
10+
if (!existsSync(examplePath)) {
11+
console.error(`Example file not found: ${examplePath}`);
12+
exit(1);
13+
}
14+
15+
const contents = readFileSync(examplePath, "utf-8");
16+
17+
const lines = contents.split("\n");
18+
const startLine = lines.findIndex(line => EXAMPLE_START_REGEX.test(line));
19+
const endLine = lines.findIndex(line => EXAMPLE_END_REGX.test(line));
20+
21+
if (startLine === -1 || endLine === -1) {
22+
return lines;
23+
}
24+
25+
return lines.slice(startLine + 1, endLine);
26+
}

docsgen/markdown.tsx

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import highlight from "highlight.js";
33
import * as React from "react";
44
import { createNodeId, getNodeText } from "./markdownUtils";
55
import { component } from "./style";
6-
import { join } from "path";
7-
import { existsSync, readFileSync } from "fs";
6+
import { readExample } from "./example";
87

98
interface MarkdownProps {
109
root: Node;
@@ -152,27 +151,9 @@ function getCodeBlockText(root: Node): string {
152151
throw new Error("Expected example code block to have a reference to an example file.");
153152
}
154153

155-
const examplePath = join("docs", "examples", root.literal.trim());
156-
if (!existsSync(examplePath)) {
157-
throw new Error(`Example file not found: ${examplePath}`);
158-
}
159-
160-
const contents = readFileSync(examplePath, "utf-8");
161-
162-
const lines = contents.split("\n");
163-
const startLine = lines.findIndex(line => EXAMPLE_START_REGEX.test(line));
164-
const endLine = lines.findIndex(line => EXAMPLE_END_REGX.test(line));
165-
166-
if (startLine === -1 || endLine === -1) {
167-
return contents;
168-
}
169-
170-
return lines.slice(startLine + 1, endLine).join("\n");
154+
return readExample(root.literal).join("\n");
171155
}
172156

173-
const EXAMPLE_START_REGEX = /^\/\/\s+start\s*/i;
174-
const EXAMPLE_END_REGX = /^\/\/\s+end\s*/i;
175-
176157
const CodeBlock = component("code-block", "code", {
177158
borderRadius: 3,
178159
$nest: {

docsgen/readme.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { existsSync, readFileSync, writeFileSync } from "fs";
2+
import { join } from "path";
3+
import { readExample } from "./example";
4+
import { exit } from "process";
5+
6+
const EXAMPLE_INLINE_REGEX = /^example:\s*(\w+\.ts)/i;
7+
8+
const templatePath = join("docs", "readme", "readme.template.md");
9+
if (!existsSync(templatePath)) {
10+
console.error(`Template file not found: ${templatePath}`);
11+
exit(1);
12+
}
13+
14+
const file = readFileSync(templatePath, "utf-8");
15+
const templateLines = file.split("\n");
16+
const resolvedLines = templateLines.flatMap(line => {
17+
const match = EXAMPLE_INLINE_REGEX.exec(line);
18+
if (match == null) {
19+
return [line];
20+
}
21+
22+
const fileName = match[1];
23+
return ["```ts", ...readExample(fileName), "```"];
24+
});
25+
const expectedReadme = resolvedLines.join("\n");
26+
27+
if (process.argv.length >= 3 && process.argv[2] === "--check") {
28+
const existingReadme = readFileSync("README.md", "utf-8");
29+
if (existingReadme !== expectedReadme) {
30+
console.error("README.md is out of date. Run `yarn readme` to update it.");
31+
exit(1);
32+
}
33+
} else {
34+
writeFileSync("README.md", expectedReadme);
35+
}

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,23 @@
2424
"clean": "rimraf dist docs/build",
2525
"compile:docs": "tsc -p docsgen",
2626
"compile:examples": "tsc -p docs/examples",
27-
"docs": "npm-run-all -s compile:docs compile:examples node:docs",
27+
"docs": "npm-run-all -s compile:docs compile:examples node:docs readme",
2828
"docs:watch": "npm-run-all -s compile:docs compile:examples node:docs:watch",
2929
"lint:docs": "eslint --config eslint.config.mjs docsgen",
3030
"lint:examples": "eslint --config eslint.config.mjs docs/examples",
31-
"lint:src": "eslint --config eslint.config.mjs src",
3231
"lint:dist": "./scripts/check-typings.sh",
32+
"lint:readme": "node dist/docsgen/readme --check",
33+
"lint:src": "eslint --config eslint.config.mjs src",
3334
"lint:test": "eslint --config eslint.config.mjs test",
34-
"lint": "npm-run-all -p lint:docs lint:examples lint:src lint:dist lint:test",
35+
"lint": "npm-run-all -p lint:docs lint:examples lint:src lint:dist lint:readme lint:test",
3536
"node:docs": "node dist/docsgen/index",
3637
"node:docs:watch": "node dist/docsgen/watch",
38+
"readme": "node dist/docsgen/readme",
3739
"test:src": "jest --config jest.config.ts",
3840
"test:types": "tsc -p test",
3941
"test": "npm-run-all -p test:src test:types",
4042
"prepack": "yarn clean && yarn build",
41-
"verify": "npm-run-all -s build lint test docs"
43+
"verify": "npm-run-all -s build test docs lint"
4244
},
4345
"devDependencies": {
4446
"@eslint/js": "^9.2.0",

0 commit comments

Comments
 (0)