Skip to content

Commit e6b3a1e

Browse files
author
Pavel Petroshenko
authored
Merge pull request #39 from electricimp/develop
2.3.0
2 parents 917c67f + 4a4d588 commit e6b3a1e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+858
-164
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@ node_modules
22
_private
33
.idea
44
.builder-cache
5+
**/.*.swp
6+
**/.*.swo
7+
input
8+
output

README.md

+105-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- [@repeat](#repeat)
1717
- [@if@elseif@else](#if--elseif--else)
1818
- [@error](#error)
19+
- [@warning](#warning)
1920
- [Filters](#filters)
2021
- [Expressions](#expressions)
2122
- [Types](#types)
@@ -25,7 +26,7 @@
2526
- [Member Expressions](#member-expressions)
2627
- [Conditional Expressions](#conditional-expressions)
2728
- [Variables](#variables)
28-
- [Variable Definition Order](#variables-definition-order)
29+
- [Variable Definition Order](#variable-definition-order)
2930
- [\_\_LINE\_\_](#__line__)
3031
- [\_\_FILE\_\_](#__file__)
3132
- [\_\_PATH\_\_](#__path__)
@@ -34,6 +35,8 @@
3435
- [Comments](#comments)
3536
- [Usage](#usage)
3637
- [Running](#running)
38+
- [Including JavaScript Libraries](#including-javascript-libraries)
39+
- [Binding the Context Object Correctly](#binding-the-context-object-correctly)
3740
- [Cache for Remote Includes](#cache-for-remote-includes)
3841
- [Testing](#testing)
3942
- [License](#license)
@@ -350,6 +353,29 @@ Emits an error.
350353
<b>@endif</b>
351354
</pre>
352355

356+
### @warning
357+
358+
<pre>
359+
<b>@warning</b> <i>&lt;message:expression&gt;</i>
360+
</pre>
361+
362+
Emits a warning.
363+
364+
#### Example
365+
366+
<pre>
367+
<b>@if</b> PLATFORM == "platform1"
368+
// platform 1 code
369+
<b>@elseif</b> PLATFORM == "platform2"
370+
// platform 2 code
371+
<b>@elseif</b> PLATFORM == "platform3"
372+
// platform 3 code
373+
<b>@else</b>
374+
<b>@warning</b> "Building for default platform"
375+
// default platform code
376+
<b>@endif</b>
377+
</pre>
378+
353379
## Filters
354380

355381
The `|` operator (filter) allows you to pass a value through any of the supported functions.
@@ -507,6 +533,20 @@ will print the home directory path of the current user of the system where *Buil
507533
- <code>min(<i>&lt;numbers&gt;</i>)</code>
508534
- <code>max(<i>&lt;numbers&gt;</i>)</code>
509535
- <code>abs(<i>&lt;number&gt;</i>)</code>
536+
- String functions: the following string functions, based on the JavaScript methods of the same names, are available under the namespace `S`. The first argument to each function is always the string to be operated on. For documentation on the remaining arguments, please see the documentation for JavaScript string methods [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String).
537+
- <code>S.concat()</code>
538+
- <code>S.endsWith()</code>
539+
- <code>S.includes()</code>
540+
- <code>S.repeat()</code>
541+
- <code>S.split()</code>
542+
- <code>S.startsWith()</code>
543+
- <code>S.substr()</code>
544+
- <code>S.substring()</code>
545+
- <code>S.toLowerCase()</code>
546+
- <code>S.toUpperCase()</code>
547+
- <code>S.trim()</code>
548+
- <code>S.trimLeft()</code>
549+
- <code>S.trimRight()</code>
510550

511551
## Comments
512552

@@ -565,6 +605,70 @@ Lines starting with `@` followed by space or a line break are treated as comment
565605
* <code>--cache</code> or <code>-c</code> &mdash; enable cache for remote files.
566606
* <code>--clear-cache</code> &mdash; remove cache before builder starts running.
567607
* <code>--cache-exclude-list <i>&lt;path_to_file&gt;</i></code> &mdash; path to exclude list file.
608+
* <code>--lib(s) <i>&lt;path_to_file|path_to_directory|glob&gt;</i></code> &mdash; path to JavaScript file to include as libraries
609+
610+
## Including JavaScript Libraries
611+
612+
Builder can accept JavaScript libraries to add functionality to its global namespace. The library should export an object, the properties of which will be merged into the global namespace. For example, to include a function to convert strings to uppercase, define your library file like so:
613+
614+
```js
615+
module.exports = {
616+
upper: (s) => s.toUpperCase()
617+
};
618+
```
619+
620+
Include directives, such as the following example, in your input file:
621+
622+
```
623+
@{upper("warning:")}
624+
@{upper(include("warning.txt"))}
625+
```
626+
627+
Run builder with the option `--lib path/to/your/lib/file`.
628+
629+
### Binding the Context Object Correctly
630+
631+
**Note** Functions called by Builder will be called with their *this* argument set to a Builder context object. Within the context object, Builder [variables](#variables) like `__FILE__`, [functions](#functions) like `max()`, and other included library functions will be made available at the top level. Variables defined in your input code with `@macro` or `@set` will be available under the key *globals*.
632+
633+
Ignoring the binding of *this* may cause unexpected behavior, for example when calling methods on objects. Take the following example library:
634+
635+
```js
636+
class MyClass {
637+
constructor(str) {
638+
this._str = str;
639+
}
640+
641+
getStr() {
642+
return this._str;
643+
}
644+
}
645+
646+
myObject = MyClass("my text");
647+
648+
module.exports = {
649+
myObject
650+
};
651+
```
652+
653+
Attempting to use this library with the directive `@{myObject.getStr()}` will not deliver the expected behavior because *this* in *getStr()* will be set to a Builder context object and not to *myObject*. When calling class methods ensure they have been bound to the correct value of *this*:
654+
655+
```js
656+
class MyClass {
657+
constructor(str) {
658+
this._str = str;
659+
}
660+
661+
getStr() {
662+
return this._str;
663+
}
664+
}
665+
666+
myObject = MyClass("my text");
667+
668+
module.exports = {
669+
getStr: myObject.getStr.bind(myObject)
670+
};
671+
```
568672

569673
## Cache for Remote Includes
570674

package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"pleasebuild": "src/cli.js"
88
},
99
"scripts": {
10-
"test": "node_modules/jasmine/bin/jasmine.js"
10+
"test": "node_modules/jasmine/bin/jasmine.js",
11+
"build": "src/cli.js input"
1112
},
1213
"repository": {
1314
"type": "git",
@@ -22,6 +23,7 @@
2223
"dependencies": {
2324
"clone": "^1.0.2",
2425
"github": "^0.2.4",
26+
"glob": "^7.1.2",
2527
"jsep": "^0.3.1",
2628
"request": "^2.71.0",
2729
"minimatch": "^3.0.4",
@@ -33,6 +35,7 @@
3335
"jasmine": "^2.4.1",
3436
"jasmine-diff-matchers": "^2.0.0",
3537
"jasmine-expect": "^2.0.2",
36-
"log": "^1.4.0"
38+
"log": "^1.4.0",
39+
"fixture-stdout": "^0.2.1"
3740
}
3841
}

spec/AstParser/incorrect-syntax.spec.js

+9
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ describe('Tokenizer', () => {
8686
}
8787
});
8888

89+
it('should detect incorrect @warning syntax', () => {
90+
try {
91+
parser.parse(`@warning`);
92+
fail();
93+
} catch (e) {
94+
expect(e.message).toBe('Syntax error in @warning (main:1)');
95+
}
96+
});
97+
8998
it('should detect incorrect @else syntax', () => {
9099
try {
91100
parser.parse(`@if 1\n@else 0\n@endif`);

spec/Expression/filters.spec.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,22 @@ describe('Expression', () => {
1515
beforeEach(() => {
1616
expression = new Expression();
1717
context = {};
18-
context.max = (args, context) => {
19-
return Math.abs.apply(Math, args);
18+
context.max = function() {
19+
return Math.max.apply(Math, [].slice.call(arguments));
2020
};
2121
});
2222

23-
it('should suppor filter operator with call invocation', () => {
23+
it('should support filter operator with call invocation', () => {
2424
const res = expression.evaluate('5|max(1,2)', context);
2525
expect(res).toBe(5);
2626
});
2727

28-
it('should suppor filter operator without call invocation #1', () => {
28+
it('should support filter operator without call invocation #1', () => {
2929
const res = expression.evaluate('5|max', context);
3030
expect(res).toBe(5);
3131
});
3232

33-
it('should suppor filter operator without call invocation #2', () => {
33+
it('should support filter operator without call invocation #2', () => {
3434
const res = expression.evaluate('5|("m" + "ax")', context);
3535
expect(res).toBe(5);
3636
});

spec/Expression/misc.spec.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ describe('Expression', () => {
1818

1919
// create Math.* function
2020
const mathFunction = (name) => {
21-
return (args, context) => {
21+
return function() {
22+
const args = [].slice.call(arguments);
2223
if (args.length < 1) {
2324
throw new Error('Wrong number of arguments for ' + name + '()');
2425
}

spec/Machine/basic.spec.js

+28
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
require('jasmine-expect');
88

9+
const Fixture = require('fixture-stdout');
10+
const stderrFixture = new Fixture({ stream: process.stderr });
911
const init = require('./init')('main');
1012
const Machine = require('../../src/Machine');
1113

@@ -81,6 +83,32 @@ describe('Machine', () => {
8183
}
8284
});
8385

86+
it('should handle @warning directives', (done) => {
87+
// Our warning message
88+
const text = 'abc';
89+
// What we expect to be logged to STDERR
90+
const yellowTextLine = `\x1b[33m${text}\u001b[39m\n`;
91+
try {
92+
// Capture STDERR messages
93+
stderrFixture.capture(message => {
94+
try {
95+
expect(message).toBe(yellowTextLine);
96+
// Release STDERR
97+
stderrFixture.release();
98+
done();
99+
} catch (e) {
100+
fail(e);
101+
}
102+
// Returning false prevents message actually being logged to STDERR
103+
return false;
104+
});
105+
106+
machine.execute(`@warning "${text}"`);
107+
} catch (e) {
108+
fail(e);
109+
}
110+
});
111+
84112
it('should not allow macro redeclaration', () => {
85113
try {
86114
machine.execute(`@macro A()\n@endmacro\n@macro A()\n@endmacro`);

spec/Machine/init.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ module.exports = (sampleFile) => {
1313
return {
1414

1515
createMachine: () => {
16-
const builder = new Builder();
16+
// File listing libs to include
17+
const libFile = `${path.dirname(sampleFile)}/libs`;
18+
let libs = [];
19+
if (fs.existsSync(libFile)) {
20+
libs = fs.readFileSync(libFile).toString().split('\n').map(l => `${path.dirname(sampleFile)}/${l}`);
21+
}
22+
const builder = new Builder({ libs });
1723
builder.logger = new Log(process.env.SPEC_LOGLEVEL || 'error');
1824
builder.machine.readers.github.username = process.env.SPEC_GITHUB_USERNAME;
1925
builder.machine.readers.github.token = process.env.SPEC_GITHUB_PASSWORD || process.env.SPEC_GITHUB_TOKEN;

spec/Machine/sample-12-spec.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) 2016-2017 Electric Imp
2+
// This file is licensed under the MIT License
3+
// http://opensource.org/licenses/MIT
4+
5+
'use strict';
6+
7+
require('jasmine-expect');
8+
const Fixture = require('fixture-stdout');
9+
10+
const FILE = __dirname + '/../fixtures/sample-12/input.nut';
11+
const init = require('./init')(FILE);
12+
const stdoutFixture = new Fixture({ stream: process.stdout });
13+
14+
describe('Machine', () => {
15+
let machine, result, resultWithLC;
16+
17+
beforeEach(() => {
18+
machine = init.createMachine();
19+
result = init.getResult();
20+
});
21+
22+
it('should exhibit the expeted behaviour when including user-defined Javascript libraries', (done) => {
23+
stdoutFixture.capture(message => {
24+
try {
25+
expect(message).toBe("Hello world!\n");
26+
// Release STDOUT
27+
stdoutFixture.release();
28+
done();
29+
} catch (e) {
30+
fail(e);
31+
}
32+
// Returning false prevents message actually being logged to STDOUT
33+
return false;
34+
});
35+
expect(machine.execute('@include "input.nut"')).toBe(result);
36+
});
37+
});

spec/Machine/sample-13-spec.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) 2016-2017 Electric Imp
2+
// This file is licensed under the MIT License
3+
// http://opensource.org/licenses/MIT
4+
5+
'use strict';
6+
7+
require('jasmine-expect');
8+
const Fixture = require('fixture-stdout');
9+
10+
const FILE = __dirname + '/../fixtures/sample-13/input.nut';
11+
const init = require('./init')(FILE);
12+
13+
describe('Machine', () => {
14+
let machine, result;
15+
16+
beforeEach(() => {
17+
machine = init.createMachine();
18+
result = init.getResult();
19+
});
20+
21+
it('should exhibit the expeted behaviour when including user-defined Javascript libraries', () => {
22+
expect(machine.execute('@include "input.nut"')).toBe(result);
23+
});
24+
});

spec/Machine/sample-14-spec.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) 2016-2017 Electric Imp
2+
// This file is licensed under the MIT License
3+
// http://opensource.org/licenses/MIT
4+
5+
'use strict';
6+
7+
require('jasmine-expect');
8+
const Fixture = require('fixture-stdout');
9+
10+
const FILE = __dirname + '/../fixtures/sample-14/input.nut';
11+
const init = require('./init')(FILE);
12+
13+
describe('Machine', () => {
14+
let machine, result;
15+
16+
beforeEach(() => {
17+
machine = init.createMachine();
18+
result = init.getResult();
19+
});
20+
21+
it('should exhibit the expeted behaviour when including user-defined Javascript libraries', () => {
22+
expect(machine.execute('@include "input.nut"')).toBe(result);
23+
});
24+
});

0 commit comments

Comments
 (0)