Skip to content

Commit

Permalink
Templatize readme (#193)
Browse files Browse the repository at this point in the history
* Templatize readme

* Fix order of build
  • Loading branch information
jscheiny authored May 19, 2024
1 parent 1f07a44 commit 98b7ca4
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 67 deletions.
98 changes: 56 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
# Safe Units

[![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)
[![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)

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.

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

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

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

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

## Features

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

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

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

Expand All @@ -50,15 +50,14 @@ yarn add safe-units

## Examples

### Unit arithmetic
### Unit Arithmetic

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

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

const mass = Measure.of(30, kilograms);
const mps2 = meters.per(seconds.squared());
Expand All @@ -67,73 +66,87 @@ const acceleration = Measure.of(9.8, mps2);
const force = mass.times(acceleration); // 294 N
const pressure = force.over(area); // 49 Pa
const maxPressure = Measure.of(0.5, milli(bars)); // 0.5 mbar
pressure.lt(maxPressure) // true
pressure.lt(maxPressure); // true

```

### Type errors
### Type Errors

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

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

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

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

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

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

```

### Naming units
### Naming Units

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

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

console.log(Measure.of(8, furlongs).in(miles)); // 1 mi
console.log(Measure.of(1, miles).in(furlongs)); // 8 fur
Measure.of(8, furlongs).in(miles); // "1 mi"
Measure.of(1, miles).in(furlongs); // "8 fur"

const fortnights = Measure.of(14, days, "ftn");
const megaFurlongsPerMicroFortnight = mega(furlongs)
.per(micro(fortnights))
.withSymbol("Mfur/µftn");
const megafurlong = mega(furlongs);
const microfortnight = micro(fortnights);
const mfPerUFtn = megafurlong.per(microfortnight).withSymbol("Mfur/µftn");

speedOfLight.in(mfPerUFtn); // "1.8026174997852542 Mfur/µftn"

console.log(speedOfLight.in(megaFurlongsPerMicroFortnight)); // 1.8026174997852542 Mfur/µftn
```

### Deriving quantities
### Deriving Quantities

```typescript
import { Acceleration, Measure, meters, seconds, Time } from "safe-units";
```ts
import { kilograms, liters, Mass, Measure, Volume } from "safe-units";

const Jerk = Acceleration.over(Time);
type Jerk = typeof Jerk;
const VolumeDensity = Mass.over(Volume);
type VolumeDensity = typeof VolumeDensity;

const mps2 = meters.per(seconds.squared());
const acceleration = Measure.of(9.8, mps2);
const jerk: Jerk = acceleration.over(Measure.of(2, seconds));
const mass = Measure.of(30, kilograms);
const volume = Measure.of(3, liters);

const density: VolumeDensity = mass.over(volume);

console.log(density.toString()); // 10 kg * m^-3

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

### Defining dimensions
### Defining Unit Systems

```ts
import { Measure, UnitSystem } from "safe-units";

```typescript
import { Area, Measure, minutes, seconds, Time } from "safe-units";
const GameUnitSystem = UnitSystem.from({
frames: "fr",
time: "s",
});

const frames = Measure.dimension("frames");
const frames = Measure.dimension(GameUnitSystem, "frames");
const seconds = Measure.dimension(GameUnitSystem, "time");

const Frames = frames;
type Frames = typeof frames;

const Time = seconds;
type Time = typeof seconds;

const FrameRate = Frames.over(Time);
type FrameRate = typeof FrameRate;

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

const measuredFrames = Measure.of(8000, frames);
const elapsedTime = Measure.of(2, minutes);
const elapsedTime = Measure.of(120, seconds);
const measuredFps: FrameRate = measuredFrames.over(elapsedTime);

if (measuredFps.lt(minFrameRate)) {
// Optimize
}

```
58 changes: 58 additions & 0 deletions docs/readme/readme.template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# 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)

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.

example: intro.ts

## Features

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

  Large library of predefined units including SI (with prefixes), Imperial, and US customary units!

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

  Long build times & cryptic error messages!

## Prerequisites

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:

- [TypeScript](http://www.typescriptlang.org/) 3.2 or later
- [Strict null checks](https://www.typescriptlang.org/docs/handbook/compiler-options.html) enabled for your project

## Installation

```
npm install safe-units
```

or

```
yarn add safe-units
```

## Examples

### Unit Arithmetic

example: introUnitArithmetic.ts

### Type Errors

example: introTypeErrors.ts

### Naming Units

example: introNamingUnits.ts

### Deriving Quantities

example: introDerivingQuantities.ts

### Defining Unit Systems

example: introUnitSystem.ts
26 changes: 26 additions & 0 deletions docsgen/example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { existsSync, readFileSync } from "fs";
import { join } from "path";
import { exit } from "process";

const EXAMPLE_START_REGEX = /^\/\/\s+start\s*/i;
const EXAMPLE_END_REGX = /^\/\/\s+end\s*/i;

export function readExample(fileName: string) {
const examplePath = join("docs", "examples", fileName.trim());
if (!existsSync(examplePath)) {
console.error(`Example file not found: ${examplePath}`);
exit(1);
}

const contents = readFileSync(examplePath, "utf-8");

const lines = contents.split("\n");
const startLine = lines.findIndex(line => EXAMPLE_START_REGEX.test(line));
const endLine = lines.findIndex(line => EXAMPLE_END_REGX.test(line));

if (startLine === -1 || endLine === -1) {
return lines;
}

return lines.slice(startLine + 1, endLine);
}
23 changes: 2 additions & 21 deletions docsgen/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import highlight from "highlight.js";
import * as React from "react";
import { createNodeId, getNodeText } from "./markdownUtils";
import { component } from "./style";
import { join } from "path";
import { existsSync, readFileSync } from "fs";
import { readExample } from "./example";

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

const examplePath = join("docs", "examples", root.literal.trim());
if (!existsSync(examplePath)) {
throw new Error(`Example file not found: ${examplePath}`);
}

const contents = readFileSync(examplePath, "utf-8");

const lines = contents.split("\n");
const startLine = lines.findIndex(line => EXAMPLE_START_REGEX.test(line));
const endLine = lines.findIndex(line => EXAMPLE_END_REGX.test(line));

if (startLine === -1 || endLine === -1) {
return contents;
}

return lines.slice(startLine + 1, endLine).join("\n");
return readExample(root.literal).join("\n");
}

const EXAMPLE_START_REGEX = /^\/\/\s+start\s*/i;
const EXAMPLE_END_REGX = /^\/\/\s+end\s*/i;

const CodeBlock = component("code-block", "code", {
borderRadius: 3,
$nest: {
Expand Down
35 changes: 35 additions & 0 deletions docsgen/readme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { existsSync, readFileSync, writeFileSync } from "fs";
import { join } from "path";
import { readExample } from "./example";
import { exit } from "process";

const EXAMPLE_INLINE_REGEX = /^example:\s*(\w+\.ts)/i;

const templatePath = join("docs", "readme", "readme.template.md");
if (!existsSync(templatePath)) {
console.error(`Template file not found: ${templatePath}`);
exit(1);
}

const file = readFileSync(templatePath, "utf-8");
const templateLines = file.split("\n");
const resolvedLines = templateLines.flatMap(line => {
const match = EXAMPLE_INLINE_REGEX.exec(line);
if (match == null) {
return [line];
}

const fileName = match[1];
return ["```ts", ...readExample(fileName), "```"];
});
const expectedReadme = resolvedLines.join("\n");

if (process.argv.length >= 3 && process.argv[2] === "--check") {
const existingReadme = readFileSync("README.md", "utf-8");
if (existingReadme !== expectedReadme) {
console.error("README.md is out of date. Run `yarn readme` to update it.");
exit(1);
}
} else {
writeFileSync("README.md", expectedReadme);
}
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,23 @@
"clean": "rimraf dist docs/build",
"compile:docs": "tsc -p docsgen",
"compile:examples": "tsc -p docs/examples",
"docs": "npm-run-all -s compile:docs compile:examples node:docs",
"docs": "npm-run-all -s compile:docs compile:examples node:docs readme",
"docs:watch": "npm-run-all -s compile:docs compile:examples node:docs:watch",
"lint:docs": "eslint --config eslint.config.mjs docsgen",
"lint:examples": "eslint --config eslint.config.mjs docs/examples",
"lint:src": "eslint --config eslint.config.mjs src",
"lint:dist": "./scripts/check-typings.sh",
"lint:readme": "node dist/docsgen/readme --check",
"lint:src": "eslint --config eslint.config.mjs src",
"lint:test": "eslint --config eslint.config.mjs test",
"lint": "npm-run-all -p lint:docs lint:examples lint:src lint:dist lint:test",
"lint": "npm-run-all -p lint:docs lint:examples lint:src lint:dist lint:readme lint:test",
"node:docs": "node dist/docsgen/index",
"node:docs:watch": "node dist/docsgen/watch",
"readme": "node dist/docsgen/readme",
"test:src": "jest --config jest.config.ts",
"test:types": "tsc -p test",
"test": "npm-run-all -p test:src test:types",
"prepack": "yarn clean && yarn build",
"verify": "npm-run-all -s build lint test docs"
"verify": "npm-run-all -s build test docs lint"
},
"devDependencies": {
"@eslint/js": "^9.2.0",
Expand Down

0 comments on commit 98b7ca4

Please sign in to comment.