From 13304194466eca3e85cc5e7cc16f102c2759eeb7 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Wed, 4 Apr 2012 18:06:00 +0200 Subject: [PATCH] Windows: add build step that generates license.rtf from LICENSE --- tools/license2rtf.js | 327 +++++++++++++++++++++++++++++++++++++++++++++++++++ vcbuild.bat | 18 ++- 2 files changed, 342 insertions(+), 3 deletions(-) create mode 100644 tools/license2rtf.js diff --git a/tools/license2rtf.js b/tools/license2rtf.js new file mode 100644 index 0000000..9bf7de4 --- /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 f6d0fef..bb53210 100644 --- a/vcbuild.bat +++ b/vcbuild.bat @@ -22,6 +22,7 @@ set nosnapshot= set test= set test_args= set msi= +set licensertf= set upload= :next-arg @@ -36,6 +37,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 @@ -43,7 +45,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 echo Warning: ignoring invalid command line option `%1`. @@ -75,7 +77,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 @@ -94,9 +96,19 @@ goto run msbuild node.sln /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 -- 2.7.4