aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools/license2rtf.js327
-rw-r--r--vcbuild.bat18
2 files changed, 342 insertions, 3 deletions
diff --git a/tools/license2rtf.js b/tools/license2rtf.js
new file mode 100644
index 0000000000..9bf7de42f4
--- /dev/null
+++ b/tools/license2rtf.js
@@ -0,0 +1,327 @@
+
+var assert = require('assert'),
+ Stream = require('stream'),
+ inherits = require('util').inherits;
+
+
+/*
+ * This filter consumes a stream of characters and emits one string per line.
+ */
+function LineSplitter() {
+ var self = this,
+ buffer = "";
+
+ Stream.call(this);
+ this.writable = true;
+
+ this.write = function(data) {
+ var lines = (buffer + data).split(/\r\n|\n\r|\n|\r/);
+ for (var i = 0; i < lines.length - 1; i++) {
+ self.emit('data', lines[i]);
+ }
+ buffer = lines[lines.length - 1];
+ return true;
+ };
+
+ this.end = function(data) {
+ this.write(data || '');
+ if (buffer) {
+ self.emit('data', buffer);
+ }
+ self.emit('end');
+ };
+}
+inherits(LineSplitter, Stream);
+
+
+/*
+ * This filter consumes lines and emits paragraph objects.
+ */
+function ParagraphParser() {
+ var self = this,
+ block_is_license_block = false,
+ block_has_c_style_comment,
+ is_first_line_in_paragraph,
+ paragraph_line_indent,
+ paragraph;
+
+ Stream.call(this);
+ this.writable = true;
+
+ resetBlock(false);
+
+ this.write = function(data) {
+ parseLine(data + '');
+ return true;
+ };
+
+ this.end = function(data) {
+ if (data) {
+ parseLine(data + '');
+ }
+ flushParagraph();
+ self.emit('end');
+ };
+
+ function resetParagraph() {
+ is_first_line_in_paragraph = true;
+ paragraph_line_indent = -1;
+
+ paragraph = {
+ li: '',
+ in_license_block: block_is_license_block,
+ lines: []
+ };
+ }
+
+ function resetBlock(is_license_block) {
+ block_is_license_block = is_license_block;
+ block_has_c_style_comment = false;
+ resetParagraph();
+ }
+
+ function flushParagraph() {
+ if (paragraph.lines.length || paragraph.li) {
+ self.emit('data', paragraph);
+ }
+ resetParagraph();
+ }
+
+ function parseLine(line) {
+ // Strip trailing whitespace
+ line = line.replace(/\s*$/, '');
+
+ // Detect block separator
+ if (/^\s*(=|"){3,}\s*$/.test(line)) {
+ flushParagraph();
+ resetBlock(!block_is_license_block);
+ return;
+ }
+
+ // Strip comments around block
+ if (block_is_license_block) {
+ if (!block_has_c_style_comment)
+ block_has_c_style_comment = /^\s*(\/\*)/.test(line);
+ if (block_has_c_style_comment) {
+ var prev = line;
+ line = line.replace(/^(\s*?)(?:\s?\*\/|\/\*\s|\s\*\s?)/, '$1');
+ if (prev == line)
+ line = line.replace(/^\s{2}/, '');
+ if (/\*\//.test(prev))
+ block_has_c_style_comment = false;
+ } else {
+ // Strip C++ and perl style comments.
+ line = line.replace(/^(\s*)(?:\/\/\s?|#\s?)/, '$1');
+ }
+ }
+
+ // Detect blank line (paragraph separator)
+ if (!/\S/.test(line)) {
+ flushParagraph();
+ return;
+ }
+
+ // Detect separator "lines" within a block. These mark a paragraph break
+ // and are stripped from the output.
+ if (/^\s*[=*\-]{5,}\s*$/.test(line)) {
+ flushParagraph();
+ return;
+ }
+
+ // Find out indentation level and the start of a lied or numbered list;
+ var result = /^(\s*)(\d+\.|\*|-)?\s*/.exec(line);
+ assert.ok(result);
+ // The number of characters that will be stripped from the beginning of
+ // the line.
+ var line_strip_length = result[0].length;
+ // The indentation size that will be used to detect indentation jumps.
+ // Fudge by 1 space.
+ var line_indent = Math.floor(result[0].length / 2) * 2;
+ // The indentation level that will be exported
+ var level = Math.floor(result[1].length / 2);
+ // The list indicator that precedes the actual content, if any.
+ var line_li = result[2];
+
+ // Flush the paragraph when there is a li or an indentation jump
+ if (line_li || (line_indent != paragraph_line_indent &&
+ paragraph_line_indent != -1)) {
+ flushParagraph();
+ paragraph.li = line_li;
+ }
+
+ // Set the paragraph indent that we use to detect indentation jumps. When
+ // we just detected a list indicator, wait
+ // for the next line to arrive before setting this.
+ if (!line_li && paragraph_line_indent != -1) {
+ paragraph_line_indent = line_indent;
+ }
+
+ // Set the output indent level if it has not been set yet.
+ if (paragraph.level === undefined)
+ paragraph.level = level;
+
+ // Strip leading whitespace and li.
+ line = line.slice(line_strip_length);
+
+ if (line)
+ paragraph.lines.push(line);
+
+ is_first_line_in_paragraph = false;
+ }
+}
+inherits(ParagraphParser, Stream);
+
+
+/*
+ * This filter consumes paragraph objects and emits modified paragraph objects.
+ * The lines within the paragraph are unwrapped where appropriate.
+ */
+function Unwrapper() {
+ var self = this;
+
+ Stream.call(this);
+ this.writable = true;
+
+ this.write = function(paragraph) {
+ var lines = paragraph.lines,
+ break_after = [],
+ i;
+
+ for (i = 0; i < lines.length - 1; i++) {
+ var line = lines[i];
+
+ // When a line is really short, the line was probably kept separate for a
+ // reason.
+ if (line.length < 50) {
+ // If the first word on the next line really didn't fit after the line,
+ // it probably was just ordinary wrapping after all.
+ var next_first_word_length = lines[i + 1].replace(/\s.*$/, '').length;
+ if (line.length + next_first_word_length < 60) {
+ break_after[i] = true;
+ }
+ }
+ }
+
+ for (i = 0; i < lines.length - 1; ) {
+ if (!break_after[i]) {
+ lines[i] += ' ' + lines.splice(i + 1, 1)[0];
+ } else {
+ i++;
+ }
+ }
+
+ self.emit('data', paragraph);
+ };
+
+ this.end = function(data) {
+ if (data)
+ self.write(data);
+ self.emit('end');
+ };
+}
+inherits(Unwrapper, Stream);
+
+
+/*
+ * This filter generates an rtf document from a stream of paragraph objects.
+ */
+function RtfGenerator() {
+ var self = this,
+ did_write_anything = false;
+
+ Stream.call(this);
+ this.writable = true;
+
+ this.write = function(paragraph) {
+ if (!did_write_anything) {
+ emitHeader();
+ did_write_anything = true;
+ }
+
+ var li = paragraph.li,
+ level = paragraph.level + (li ? 1 : 0),
+ lic = paragraph.in_license_block;
+
+ var rtf = "\\pard";
+ rtf += '\\sa150\\sl300\\slmult1';
+ if (level > 0)
+ rtf += '\\li' + (level * 240);
+ if (li) {
+ rtf += '\\tx' + (level) * 240;
+ rtf += '\\fi-240';
+ }
+ if (lic)
+ rtf += '\\ri240';
+ if (!lic)
+ rtf += '\\b';
+ if (li)
+ rtf += ' ' + li + '\\tab';
+ rtf += ' ';
+ rtf += paragraph.lines.map(rtfEscape).join('\\line ');
+ if (!lic)
+ rtf += '\\b0';
+ rtf += '\\par\n';
+
+ self.emit('data', rtf);
+ };
+
+ this.end = function(data) {
+ if (data)
+ self.write(data);
+ if (did_write_anything)
+ emitFooter();
+ self.emit('end');
+ };
+
+ function toHex(number, length) {
+ var hex = (~~number).toString(16);
+ while (hex.length < length)
+ hex = '0' + hex;
+ return hex;
+ }
+
+ function rtfEscape(string) {
+ return string
+ .replace(/[\\\{\}]/g, function(m) {
+ return '\\' + m;
+ })
+ .replace(/\t/g, function() {
+ return '\\tab ';
+ })
+ .replace(/[\x00-\x1f\x7f-\xff]/g, function(m) {
+ return '\\\'' + toHex(m.charCodeAt(0), 2);
+ })
+ .replace(/\ufeff/g, '')
+ .replace(/[\u0100-\uffff]/g, function(m) {
+ return '\\u' + toHex(m.charCodeAt(0), 4) + '?';
+ });
+ }
+
+ function emitHeader() {
+ self.emit('data', '{\\rtf1\\ansi\\ansicpg1252\\uc1\\deff0\\deflang1033' +
+ '{\\fonttbl{\\f0\\fswiss\\fcharset0 Tahoma;}}\\fs20\n' +
+ '{\\*\\generator txt2rtf 0.0.1;}\n');
+ }
+
+ function emitFooter() {
+ self.emit('data', '}');
+ }
+}
+inherits(RtfGenerator, Stream);
+
+
+var stdin = process.stdin,
+ stdout = process.stdout,
+ line_splitter = new LineSplitter(),
+ paragraph_parser = new ParagraphParser(),
+ unwrapper = new Unwrapper(),
+ rtf_generator = new RtfGenerator();
+
+stdin.setEncoding('utf-8');
+stdin.resume();
+
+stdin.pipe(line_splitter);
+line_splitter.pipe(paragraph_parser);
+paragraph_parser.pipe(unwrapper);
+unwrapper.pipe(rtf_generator);
+rtf_generator.pipe(stdout);
diff --git a/vcbuild.bat b/vcbuild.bat
index fc4d94ea6d..92eef6feb6 100644
--- a/vcbuild.bat
+++ b/vcbuild.bat
@@ -24,6 +24,7 @@ set nosnapshot=
set test=
set test_args=
set msi=
+set licensertf=
set upload=
set jslint=
@@ -39,6 +40,7 @@ if /i "%1"=="noprojgen" set noprojgen=1&goto arg-ok
if /i "%1"=="nobuild" set nobuild=1&goto arg-ok
if /i "%1"=="nosign" set nosign=1&goto arg-ok
if /i "%1"=="nosnapshot" set nosnapshot=1&goto arg-ok
+if /i "%1"=="licensertf" set licensertf=1&goto arg-ok
if /i "%1"=="test-uv" set test=test-uv&goto arg-ok
if /i "%1"=="test-internet" set test=test-internet&goto arg-ok
if /i "%1"=="test-pummel" set test=test-pummel&goto arg-ok
@@ -46,7 +48,7 @@ if /i "%1"=="test-simple" set test=test-simple&goto arg-ok
if /i "%1"=="test-message" set test=test-message&goto arg-ok
if /i "%1"=="test-all" set test=test-all&goto arg-ok
if /i "%1"=="test" set test=test&goto arg-ok
-if /i "%1"=="msi" set msi=1&goto arg-ok
+if /i "%1"=="msi" set msi=1&set licensertf=1&goto arg-ok
if /i "%1"=="upload" set upload=1&goto arg-ok
if /i "%1"=="jslint" set jslint=1&goto arg-ok
@@ -74,7 +76,7 @@ echo Project files generated.
:msbuild
@rem Skip project generation if requested.
-if defined nobuild goto msi
+if defined nobuild goto sign
@rem Bail out early if not running in VS build env.
if defined VCINSTALLDIR goto msbuild-found
@@ -93,9 +95,19 @@ goto run
msbuild node.sln /m /t:%target% /p:Configuration=%config% /clp:NoSummary;NoItemAndPropertyList;Verbosity=minimal /nologo
if errorlevel 1 goto exit
-if defined nosign goto msi
+:sign
+@rem Skip signing if the `nosign` option was specified.
+if defined nosign goto licensertf
+
signtool sign /a Release\node.exe
+:licensertf
+@rem Skip license.rtf generation if not requested.
+if not defined licensertf goto msi
+
+%config%\node tools\license2rtf.js < LICENSE > %config%\license.rtf
+if errorlevel 1 echo Failed to generate license.rtf&goto exit
+
:msi
@rem Skip msi generation if not requested
if not defined msi goto run