Skip to content

Commit fc091c5

Browse files
authored
Merge pull request #374 from stasm/fluent-dedent
Add the fluent-dedent package
2 parents 427f85d + da76f93 commit fc091c5

14 files changed

+533
-0
lines changed

fluent-dedent/.esdoc.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"source": "./src",
3+
"destination": "../html/dedent",
4+
"plugins": [
5+
{
6+
"name": "esdoc-standard-plugin"
7+
},
8+
{
9+
"name": "esdoc-ecmascript-proposal-plugin",
10+
"option": {
11+
"objectRestSpread": true,
12+
"asyncGenerators": true
13+
}
14+
}
15+
]
16+
}

fluent-dedent/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/index.js
2+
/compat.js

fluent-dedent/.npmignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.nyc_output
2+
coverage
3+
docs
4+
test
5+
makefile

fluent-dedent/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Changelog
2+
3+
## Unreleased
4+
5+
This is the first release of `@fluent/dedent` as an independent package. It's
6+
based on the `ftl` template tag from the `fluent` package, but the behavior
7+
has changed and the code has been refactored.
8+
9+
The behavior in this version is largely based on the [multiline strings
10+
specification in Swift][1]. See the `README.md` for more details.
11+
12+
[1]: https://docs.swift.org/swift-book/LanguageGuide/StringsAndCharacters.html

fluent-dedent/README.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# @fluent/dedent
2+
3+
`@fluent/dedent` provides a template literal tag to dedent Fluent code.
4+
Fluent Syntax is indentation-sensitive, and `@fluent/dedent` offers a
5+
convenient way to include Fluent snippets in source code keeping the current
6+
level of indentation and without compromising the readability.
7+
8+
9+
## Installation
10+
11+
`@fluent/dedent` can be used both on the client-side and the server-side. You can
12+
install it from the npm registry or use it as a standalone script (as the
13+
`FluentDedent` global).
14+
15+
npm install @fluent/dedent
16+
17+
18+
## How to use
19+
20+
`@fluent/dedent`'s default export is meant to be used as a template literal
21+
tag. By convention, the tag is often called `ftl`.
22+
23+
```javascript
24+
import ftl from "@fluent/dedent";
25+
26+
let messages = ftl`
27+
hello = Hello, world!
28+
welcome = Welcome, {$userName}!
29+
`;
30+
```
31+
32+
The position of the closing backtick defines how much indent will be removed
33+
from each line of the content. If the indentation is not sufficient in any of
34+
the non-blank lines of the content, a `RangeError` is thrown.
35+
36+
```javascript
37+
import ftl from "@fluent/dedent";
38+
39+
let messages = ftl`
40+
hello = Hello, world!
41+
welcome = Welcome, {$userName}!
42+
`;
43+
44+
// → RangeError("Insufficient indentation in line 2.")
45+
```
46+
47+
Content must start on a new line and must end on a line of its own. The
48+
closing delimiter must appear on a new line. The first and the last line of
49+
the input will be removed from the output. If any of them contains
50+
non-whitespace characters, a `RangeError` is thrown.
51+
52+
```javascript
53+
import ftl from "@fluent/dedent";
54+
55+
let message1 = "hello = Hello, world!";
56+
let message2 = ftl`
57+
hello = Hello, world!
58+
`;
59+
60+
assert(message1 === message2);
61+
```
62+
63+
If you wish to include the leading or trailing line breaks in the output, put
64+
extra blank lines in the input.
65+
66+
```javascript
67+
import ftl from "@fluent/dedent";
68+
69+
let message = ftl`
70+
71+
hello = Hello, world!
72+
73+
`;
74+
75+
assert(message === "\nhello = Hello, world!\n");
76+
```
77+
78+
79+
## Compatibility
80+
81+
For legacy browsers, the `compat` build has been transpiled using Babel's [env
82+
preset][].
83+
84+
```javascript
85+
import ftl from '@fluent/dedent/compat';
86+
```
87+
88+
89+
## Learn more
90+
91+
Find out more about Project Fluent at [projectfluent.org][], including
92+
documentation of the Fluent file format ([FTL][]), links to other packages and
93+
implementations, and information about how to get involved.
94+
95+
96+
[env preset]: https://babeljs.io/docs/plugins/preset-env/
97+
[projectfluent.org]: http://projectfluent.org
98+
[FTL]: http://projectfluent.org/fluent/guide/

fluent-dedent/makefile

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
PACKAGE := @fluent/dedent
2+
GLOBAL := FluentDedent
3+
4+
include ../common.mk
5+
6+
build: index.js compat.js
7+
8+
index.js: $(SOURCES)
9+
@rollup $(CURDIR)/src/index.js \
10+
--config $(ROOT)/bundle_config.js \
11+
--banner "/* $(PACKAGE)@$(VERSION) */" \
12+
--amd.id $(PACKAGE) \
13+
--name $(GLOBAL) \
14+
--output.file $@
15+
@echo -e " $(OK) $@ built"
16+
17+
compat.js: $(SOURCES)
18+
@rollup $(CURDIR)/src/index.js \
19+
--config $(ROOT)/compat_config.js \
20+
--banner "/* $(PACKAGE)@$(VERSION) */" \
21+
--amd.id $(PACKAGE) \
22+
--name $(GLOBAL) \
23+
--output.file $@
24+
@echo -e " $(OK) $@ built"
25+
26+
clean:
27+
@rm -f $(PACKAGE).js compat.js
28+
@rm -rf .nyc_output coverage
29+
@echo -e " $(OK) clean"

fluent-dedent/package.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "@fluent/dedent",
3+
"description": "A template literal tag for dedenting Fluent code",
4+
"version": "0.1.0",
5+
"homepage": "https://projectfluent.org",
6+
"author": "Mozilla <[email protected]>",
7+
"license": "Apache-2.0",
8+
"contributors": [
9+
{
10+
"name": "Staś Małolepszy",
11+
"email": "[email protected]"
12+
}
13+
],
14+
"directories": {
15+
"lib": "./src"
16+
},
17+
"main": "./index.js",
18+
"module": "./src/index.js",
19+
"repository": {
20+
"type": "git",
21+
"url": "https://github.com/projectfluent/fluent.js.git"
22+
},
23+
"keywords": [
24+
"dedent",
25+
"fluent",
26+
"ftl"
27+
],
28+
"engines": {
29+
"node": ">=8.9.0"
30+
}
31+
}

fluent-dedent/src/index.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// A blank line may contain spaces and tabs.
2+
const RE_BLANK = /^[ \t]*$/;
3+
4+
/**
5+
* Template literal tag for dedenting Fluent code.
6+
*
7+
* Strip the indent of the last line from each line of the input. Remove the
8+
* first and the last line from the output. The snippet must start on a new
9+
* line and it must end on a line of its own, with the closing delimiter on a
10+
* next line.
11+
*
12+
* @param {Array<string>} strings
13+
* @param {...any} values
14+
* @returns string
15+
*/
16+
export default function ftl(strings, ...values) {
17+
let code = strings.reduce((acc, cur) => acc + values.shift() + cur);
18+
let lines = code.split("\n");
19+
let [first, commonIndent] = [lines.shift(), lines.pop()];
20+
21+
if (!RE_BLANK.test(first)) {
22+
throw new RangeError("Content must start on a new line.");
23+
}
24+
if (!RE_BLANK.test(commonIndent)) {
25+
throw new RangeError("Closing delimiter must appear on a new line.");
26+
}
27+
28+
function dedent(line, idx) {
29+
let lineIndent = line.slice(0, commonIndent.length);
30+
if (lineIndent.length === 0) {
31+
// Empty blank lines are preserved even if technically they are not
32+
// indented at all. This also short-circuits the dedentation logic when
33+
// commonIndent.length is 0, i.e. when all indents should be kept.
34+
return line;
35+
}
36+
if (lineIndent !== commonIndent) {
37+
// The indentation of the line must match commonIndent exacty.
38+
throw new RangeError(`Insufficient indentation in line ${idx + 1}.`);
39+
}
40+
// Strip commonIndent.
41+
return line.slice(commonIndent.length);
42+
}
43+
44+
return lines.map(dedent).join("\n");
45+
}

fluent-dedent/test/blank_test.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"use strict";
2+
3+
import assert from "assert";
4+
import ftl from "../src/index";
5+
6+
suite("blank lines", function() {
7+
test("leading", function() {
8+
assert.equal(
9+
ftl`
10+
11+
foo
12+
`,
13+
"\nfoo"
14+
);
15+
});
16+
17+
test("middle", function() {
18+
assert.equal(
19+
ftl`
20+
foo
21+
22+
bar
23+
`,
24+
"foo\n\nbar"
25+
);
26+
});
27+
28+
test("trailing", function() {
29+
assert.equal(
30+
ftl`
31+
foo
32+
33+
`,
34+
"foo\n"
35+
);
36+
});
37+
38+
test("containing the same amount of spaces as the common indent", function() {
39+
assert.equal(
40+
ftl`
41+
42+
`,
43+
""
44+
);
45+
});
46+
47+
test("containing too few spaces", function() {
48+
assert.throws(
49+
() => ftl`
50+
51+
`,
52+
/Insufficient indentation in line 1/
53+
);
54+
});
55+
56+
test("containing too many spaces", function() {
57+
assert.equal(
58+
ftl`
59+
60+
`,
61+
" "
62+
);
63+
});
64+
});

fluent-dedent/test/content_test.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"use strict";
2+
3+
import assert from "assert";
4+
import ftl from "../src/index";
5+
6+
suite("content lines", function() {
7+
test("no indent", function () {
8+
assert.equal(
9+
ftl`
10+
foo
11+
bar
12+
`,
13+
"foo\nbar"
14+
);
15+
});
16+
17+
test("zero indent", function () {
18+
assert.equal(
19+
ftl`
20+
foo
21+
bar
22+
`,
23+
" foo\n bar"
24+
);
25+
});
26+
27+
test("small indent", function () {
28+
assert.equal(
29+
ftl`
30+
foo
31+
bar
32+
`,
33+
" foo\nbar"
34+
);
35+
});
36+
37+
test("same indent", function () {
38+
assert.equal(
39+
ftl`
40+
foo
41+
bar
42+
`,
43+
"foo\nbar"
44+
);
45+
});
46+
47+
test("larger indent", function () {
48+
assert.throws(
49+
() => ftl`
50+
foo
51+
bar
52+
`,
53+
/Insufficient indentation in line 2/
54+
);
55+
});
56+
});

0 commit comments

Comments
 (0)