summaryrefslogtreecommitdiff
path: root/tools/license2rtf.js
diff options
context:
space:
mode:
authorBert Belder <bertbelder@gmail.com>2012-04-04 18:06:00 +0200
committerBert Belder <bertbelder@gmail.com>2012-04-04 18:14:21 +0200
commit2728dcc95bfaf9a4a084159e6f615238700c2060 (patch)
tree9779bd363454821e953330bb213889673ce5ec9f /tools/license2rtf.js
parent930fabe43f83343e7f1a41cbfa506cd08be6bfa5 (diff)
downloadnode-new-2728dcc95bfaf9a4a084159e6f615238700c2060.tar.gz
Windows: add build step that generates license.rtf from LICENSE
Diffstat (limited to 'tools/license2rtf.js')
-rw-r--r--tools/license2rtf.js327
1 files changed, 327 insertions, 0 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);