Installation
Quick Take
Examples
- An ambiguous hyphen is left alone
- Convert one em dash into an entity
- Disable the dash conversion
- Convert a letter range into an en dash
- Convert a number range into an en dash
- Convert a maths minus into an en dash
- Report the index offsets through a callback
- Supply the dash value separately
Idea
A hyphen (-) is not a dash. Typographically-correct English text uses three different characters:
| Character | Name | Used for |
|---|---|---|
- | ||
- | hyphen | compound words — well-known |
– (–) | ||
– (–) | en dash | ranges and minus — 1880–1912, 5 – 2 = 3 |
— (—) | ||
— (—) | em dash | a break in a sentence — like this one |
This program finds hyphens and dashes which are in the wrong place and reports what to replace them with. It reads the surrounding characters to decide, so it does not need you to mark anything up.
This is a sibling of string-apostrophes; both are used by detergent.
Sources used in rules logic and unit tests:
- Oxford A–Z of Grammar and Punctuation 2nd Ed., 2009, ISBN 978–0199564675
- Butterick’s Practical Typography 2nd Ed., “Hyphens and dashes” chapter
The rules
The program applies three rules. In each, the character being evaluated is a hyphen, an en dash or an em dash.
- Number range or
A-Zrange → en dash. A hyphen with a digit on each side (1880-1912) becomes an en dash. So does a hyphen between two standalone uppercase letters (A-Z). - Whitespace on both sides → em dash.
-becomes—. The exception is arithmetic: if the nearest non-whitespace characters on both sides are digits or currency symbols (1 - 2,$5 - $2), the hyphen is a minus sign, so it becomes an en dash instead. - Cut-off speech → em dash. A letter, then a hyphen, then a quote (
"I was just abo-") becomes an em dash.
Anything the rules don’t recognise is left alone.
API — convertAll()
The main function convertAll() is imported like this:
It’s a function which takes two input arguments:
| Input argument | Type | Obligatory | Description |
|---|---|---|---|
strType: String Obligatory: yes | |||
str | String | yes | String to process. |
optsType: Plain object Obligatory: no | |||
opts | Plain object | no | Optional Options Object. |
The Optional Options Object has the following shape:
| Key | Type | Default | Obligatory | Description |
|---|---|---|---|---|
convertEntitiesType: Boolean Default: falseObligatory: no | ||||
convertEntities | Boolean | false | no | Set to true to insert –/— instead of the raw characters. |
convertDashesType: Boolean Default: trueObligatory: no | ||||
convertDashes | Boolean | true | no | A killswitch. If it’s false, the program does nothing. |
convertAll() also accepts from, to, value and offsetBy, but it sets from itself while looping, so there’s little point passing them. Use convertOne() when you want to drive the indexes yourself.
Here are all defaults in one place for copying:
The function will return a plain object:
| Returned object’s key | Type | Description |
|---|---|---|
resultType: String | ||
result | String | Processed string, with all ranges applied |
rangesType: Array of arrays, or null | ||
ranges | Array of arrays, or null | Ranges that were gathered and applied to produce the result. It’s an empty array when nothing needed fixing, and null when str was empty. |
For example:
import { convertAll } from "string-dashes";
console.log(
convertAll("Dashes come in two sizes - the en dash and the em dash.", {
convertEntities: true,
}),
);
// => {
// result: "Dashes come in two sizes — the en dash and the em dash.",
// ranges: [[25, 26, "—"]],
// }
API — convertOne()
The main function convertOne() is imported like this:
It’s a function which takes two input arguments:
| Input argument | Type | Obligatory | Description |
|---|---|---|---|
strType: String Obligatory: yes | |||
str | String | yes | A string to process |
optsType: Plain object Obligatory: yes | |||
opts | Plain object | yes | Obligatory Options Object. |
The Obligatory Options Object has the following shape:
| Key | Type | Default | Obligatory | Description |
|---|---|---|---|---|
fromType: Natural number, string index Default: undefinedObligatory: yes | ||||
from | Natural number, string index | undefined | yes | Index at which the character to process starts. |
toType: Natural number, string index Default: from + 1Obligatory: no | ||||
to | Natural number, string index | from + 1 | no | Index at which the character to process ends. |
valueType: String Default: undefinedObligatory: no | ||||
value | String | undefined | no | Override the value present at str.slice(from, to). Recognised values are -, – and —. |
convertEntitiesType: Boolean Default: falseObligatory: no | ||||
convertEntities | Boolean | false | no | Set to true to insert –/— instead of the raw characters. |
convertDashesType: Boolean Default: trueObligatory: no | ||||
convertDashes | Boolean | true | no | Killswitch. If it’s false, the program does nothing. |
offsetByType: Function Default: undefinedObligatory: no | ||||
offsetBy | Function | undefined | no | If you provide a function, it will be called with a natural number input argument, meaning how much characters to skip next. |
opts.from is obligatory — that’s how you tell the program which character to process. It throws if from is missing, negative, or beyond the end of str.
The function returns ranges: an array of range arrays or null. Later you can use them in ranges-apply to process a string using those ranges (in other words, “to apply those ranges”).
import { convertOne } from "string-dashes";
console.log(
convertOne("Dashes come in two sizes - the en dash and the em dash.", {
from: 25,
convertEntities: true,
}),
);
// => [[25, 26, "—"]]
opts.value
Use opts.value when the character sits in the string encoded, or represented by something other than a literal -, – or —. You tell the program where the “symbol” starts (from) and ends (to), and what it stands for (value); the program then evaluates the surroundings as if a real dash were there:
import { convertOne } from "string-dashes";
console.log(
convertOne("Dashes come in two sizes - the en dash.", {
from: 25,
to: 30,
value: "-", // tell the program indexes 25-30 represent a hyphen
}),
);
// => [[25, 30, "—"]]
That’s how detergent uses this package.
API — defaults
You can import defaults:
It's a plain object:
The main function calculates the options to be used by merging the options you passed with these defaults.
These defaults are applicable for both convertOne() and convertAll() functions.
API — version
You can import version:
API — types
This package is written in TypeScript and exports the following types:
| Type | Description |
|---|---|
OptsType: Opts | |
Opts | The Options Object of both convertAll() and convertOne(), shown above. |
RangeType: Range | |
Range | A single range, re-exported from ranges-apply. |
RangesType: Ranges | |
Ranges | Zero or more Ranges, or null, re-exported from ranges-apply. |
import type { Opts, Range, Ranges } from "string-dashes";