Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions README.textile
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,29 @@ h2. Install
bc. $ npm install textile-js


h2. Options
h2. The @textile@ function

The basic interface mimics "marked":https://github.com/chjj/marked, the popular markdown parser. So if you use that in your project then you can support Textile as well with minimal effort.

Currently, the only supported option is @breaks@ which can be used to enable/disable the default behavior of line-breaking single newlines within blocks.
Currently, the only option imported from marked is @breaks@ which can be used to enable/disable the default behavior of line-breaking single newlines within blocks.

h3. Syntax

bc. textile( textileString [, options] )

|*Argument*|*Type*|*Notes*|
|textileString|@string@|String of textile source to be compiled.|
|"options":#options|@object@|Hash of options. Can also use @textile.setOptions@.|

h3. <a name="options" />Options

|*Member*|*Type*|*Default*|*Notes*|
|break|@boolean@|true|Convert single-line linebreaks to @<br>@.|
|cssClassOriginalLineNumber|@string@|''|Add this CSS class name, to each elements of HTML output, mapped to a the line numbers of input text.
Works only when @showOriginalLineNumber@ option is enabled.|
|lineOffset|@integer@|0|Line number offset of the first char in input text, for @showOriginalLineNumber@ option.|
|showOriginalLineNumber|@boolean@|false|Maps the elements of HTML output, with the line numbers of input text.
An HTML attribute @data-line@ is added to the HTML block elements. It's value is the line number in input text, of the first char of text inside that HTML block.|

h2. Usage

Expand Down
10 changes: 8 additions & 2 deletions bin/textile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ process.argv.slice( 2 ).forEach( function ( m, i, s ) {

// parse arguments
var options = {};
var lib_options = {};
while ( args.length ) {
var arg = args.shift();
if ( arg === '-t' || arg === '--tokens' ) {
Expand All @@ -38,6 +39,11 @@ while ( args.length ) {
else if ( arg === '-o' || arg === '--output' ) {
options.output = args.shift();
}
else if ( arg === '--show-original-line-number' ) {
lib_options.showOriginalLineNumber = true;
} else if ( arg === '--show-original-line-number-line-offset' ) {
lib_options.lineOffset = parseInt( args.shift(), 10);
}
else {
if ( !options.files ) { options.files = []; }
options.files.shift( arg );
Expand All @@ -52,8 +58,8 @@ if ( options.input ) {
// writer
var data = '';
function writeData () {
data = options.tokens ? JSON.stringify( textile.jsonml( data ), null, 2 )
: textile( data );
data = options.tokens ? JSON.stringify( textile.jsonml( data, lib_options ), null, 2 )
: textile( data, lib_options );
if ( options.output ) {
fs.writeFileSync( options.output, data );
}
Expand Down
12 changes: 9 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@ function textile ( txt, opt ) {
// get a throw-away copy of options
opt = merge( merge({}, textile.defaults ), opt || {});
// run the converter
return parseFlow( txt, opt ).map( toHTML ).join( '' );
return parseFlow( txt, opt, opt.lineOffset ).map( toHTML ).join( '' );
};
module.exports = textile;

// options
textile.defaults = {
// single-line linebreaks are converted to <br> by default
'breaks': true
'breaks': true,
// by default, don't map the elements of HTML output, with the line numbers of input text
'showOriginalLineNumber': false,
// line number offset of the first char of input text, for showOriginalLineNumber option
'lineOffset': 0,
// by default, don't set a special CSS class name to each HTML element mapped to an original line number
'cssClassOriginalLineNumber': ''
};
textile.setOptions = textile.setoptions = function ( opt ) {
merge( textile.defaults, opt );
Expand All @@ -35,6 +41,6 @@ textile.jsonml = function ( txt, opt ) {
// get a throw-away copy of options
opt = merge( merge({}, textile.defaults ), opt || {});
// parse and return tree
return [ 'html' ].concat( parseFlow( txt, opt ) );
return [ 'html' ].concat( parseFlow( txt, opt, opt.lineOffset ) );
};
textile.serialize = toHTML;
8 changes: 8 additions & 0 deletions src/ribbon.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ module.exports = function ribbon ( feed ) {
return self;
},

getSlot: () => {
return slot || 0;
},

getPos: () => {
return pos || 0;
},

load: () => {
pos = slot;
feed = org.slice( pos );
Expand Down
17 changes: 16 additions & 1 deletion src/textile/attr.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,22 @@ function parseAttr ( input, element, endToken ) {
return ( remaining === input ) ? undefined : [ input.length - remaining.length, o ];
}

function addLineNumber ( jsonmlEntryAttr, options, charPosToLine, charOffset, charPos ) {
if ( options.showOriginalLineNumber && charPosToLine ) {
charOffset = charOffset || 0;
if ( !jsonmlEntryAttr ) {
jsonmlEntryAttr = {};
}
jsonmlEntryAttr['data-line'] = charPosToLine[ charOffset + charPos ];
if ( options.cssClassOriginalLineNumber ) {
jsonmlEntryAttr['class'] = ( jsonmlEntryAttr['class'] ? jsonmlEntryAttr['class'] + ' ' : '' ) + options.cssClassOriginalLineNumber;
}
}
return jsonmlEntryAttr;
}

module.exports = {
copyAttr: copyAttr,
parseAttr: parseAttr
parseAttr: parseAttr,
addLineNumber: addLineNumber
};
44 changes: 39 additions & 5 deletions src/textile/deflist.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@

const ribbon = require( '../ribbon' );

const { addLineNumber } = require( './attr' );

const reDeflist = /^((?:- (?:[^\n]\n?)+?)+:=(?: *\n[^\0]+?=:(?:\n|$)|(?:[^\0]+?(?:$|\n(?=\n|- )))))+/;
const reItem = /^((?:- (?:[^\n]\n?)+?)+):=( *\n[^\0]+?=:\s*(?:\n|$)|(?:[^\0]+?(?:$|\n(?=\n|- ))))/;

function testDefList ( src ) {
return reDeflist.exec( src );
}

function parseDefList ( src, options ) {
function parseDefList ( src, options, charOffset, charPosToLine ) {
if ( options.showOriginalLineNumber ) {
const removedSrc = src.match( /^\s+/ );
if ( removedSrc && removedSrc[0] ) {
charOffset += removedSrc[0].length;
}
}
src = ribbon( src.trim() );

// late loading to get around the lack of non-circular-dependency support in RequireJS
Expand All @@ -23,19 +31,45 @@ function parseDefList ( src, options ) {

while ( ( m = reItem.exec( src ) ) ) {
// add terms
terms = m[1].split( /(?:^|\n)\- / ).slice( 1 );
terms = m[1].split( /(?:^|\n)\- / );
let localCharOffset = terms[0].length;
terms = terms.slice( 1 );
let separators = [];
if ( options.showOriginalLineNumber ) {
separators = m[1].match( /(?:^|\n)\- /g ).slice( 1 );
}
while ( terms.length ) {
const term = terms.shift();
deflist.push( '\t'
, [ 'dt' ].concat( parsePhrase( terms.shift().trim(), options ) )
, [ 'dt' ].concat(
addLineNumber({}, options, charPosToLine, charOffset, src.getPos() + localCharOffset )
, parsePhrase( term.trim(), options ) )
, '\n'
);
if ( options.showOriginalLineNumber ) {
localCharOffset += term.length;
// perhaps no separator at the end of the list
if ( separators.length ) {
localCharOffset += separators.shift().length;
}
}
}
// add definitions
def = m[2].trim();
if ( options.showOriginalLineNumber ) {
// rebase local char offset, and add +2 for ':=' between term and definition
localCharOffset = m[1].length + 2;

const removedSrc = m[2].match( /^\s+/ );
if ( removedSrc && removedSrc[0] ) {
localCharOffset += removedSrc[0].length;
}
}
deflist.push( '\t'
, [ 'dd' ].concat(
( /=:$/.test( def ) )
? parseFlow( def.slice( 0, -2 ).trim(), options )
addLineNumber({}, options, charPosToLine, charOffset, src.getPos() + localCharOffset )
, ( /=:$/.test( def ) )
? parseFlow( def.slice( 0, -2 ).trim(), options, options.showOriginalLineNumber ? charPosToLine[ ( charOffset || 0 ) + localCharOffset + src.getPos() ] : 0 )
: parsePhrase( def, options )
)
, '\n'
Expand Down
55 changes: 48 additions & 7 deletions src/textile/flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const fixLinks = require( '../fixlinks' );
const { parseHtml, tokenize, parseHtmlAttr, singletons, testComment, testOpenTagBlock } = require( '../html' );

const { parsePhrase } = require( './phrase' );
const { copyAttr, parseAttr } = require( './attr' );
const { copyAttr, parseAttr, addLineNumber } = require( './attr' );
const { testList, parseList } = require( './list' );
const { testDefList, parseDefList } = require( './deflist' );
const { testTable, parseTable } = require( './table' );
Expand Down Expand Up @@ -45,6 +45,7 @@ const reRuler = /^(\-\-\-+|\*\*\*+|___+)(\r?\n\s+|$)/;
const reLinkRef = re.compile( /^\[([^\]]+)\]((?:https?:\/\/|\/)\S+)(?:\s*\n|$)/ );
const reFootnoteDef = /^fn\d+$/;

const reCleanBegin = /^( *\r?\n)+/;

const hasOwn = Object.prototype.hasOwnProperty;
function extend ( target, ...args ) {
Expand Down Expand Up @@ -80,13 +81,52 @@ function paragraph ( s, tag, pba, linebreak, options ) {
return out;
};

function parseFlow ( src, options ) {
function computeCharOffset ( src, options, lineOffset ) {
if ( options.showOriginalLineNumber ) {
lineOffset = lineOffset || 0;

const removedSrc = src.match( reCleanBegin );
if ( removedSrc && removedSrc[0] ) {
lineOffset += ( removedSrc[0].match( /\n/g ) || [] ).length;
}
return lineOffset;
}
else {
return 0;
}
}

function storeCharPosToLine ( src, options, charOffset ) {
// FIXME: don't store all chr ?
if ( options.showOriginalLineNumber ) {
const charPosToLine = [];
const realSrc = src.toString();
for ( const i in realSrc ) {
charPosToLine[ i ] = charOffset;
if ( realSrc[ i ] === '\n' ) {
charOffset++;
}
}
return charPosToLine;
}
else {
return void 0;
}
}

function parseFlow ( src, options, lineOffset ) {
const list = builder();

let linkRefs;
let m;

src = ribbon( src.replace( /^( *\r?\n)+/, '' ) );
// keep as local variable, for nested calls (->block HTML)
const charLineOffset = computeCharOffset( src, options, lineOffset );

src = ribbon( src.replace( reCleanBegin, '' ) );

// keep as local variable, for nested calls (->block HTML)
const charPosToLine = storeCharPosToLine( src, options, charLineOffset );

// loop
while ( src.valueOf() ) {
Expand All @@ -113,6 +153,7 @@ function parseFlow ( src, options ) {
src.advance( pba[0] );
pba = pba[1];
}
pba = addLineNumber( pba, options, charPosToLine, 0, src.getSlot() );
if ( ( m = /^\.(\.?)(?:\s|(?=:))/.exec( src ) ) ) {
// FIXME: this whole copyAttr seems rather strange?
// slurp rest of block
Expand Down Expand Up @@ -272,27 +313,27 @@ function parseFlow ( src, options ) {
// list
if ( ( m = testList( src ) ) ) {
src.advance( m[0] );
list.add( parseList( m[0], options ) );
list.add( parseList( m[0], options, src.getSlot(), charPosToLine ) );
continue;
}

// definition list
if ( ( m = testDefList( src ) ) ) {
src.advance( m[0] );
list.add( parseDefList( m[0], options ) );
list.add( parseDefList( m[0], options, src.getSlot(), charPosToLine ) );
continue;
}

// table
if ( ( m = testTable( src ) ) ) {
src.advance( m[0] );
list.add( parseTable( m[1], options ) );
list.add( parseTable( m[1], options, src.getSlot(), charPosToLine ) );
continue;
}

// paragraph
m = reBlockNormal.exec( src );
list.merge( paragraph( m[1], 'p', undefined, '\n', options ) );
list.merge( paragraph( m[1], 'p', addLineNumber({}, options, charPosToLine, 0, src.getSlot() ), '\n', options ) );
src.advance( m[0] );
}

Expand Down
12 changes: 10 additions & 2 deletions src/textile/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const ribbon = require( '../ribbon' );
const re = require( '../re' );
const merge = require( '../merge' );

const { parseAttr } = require( './attr' );
const { parseAttr, addLineNumber } = require( './attr' );
const { parsePhrase } = require( './phrase' );

const { txlisthd } = require( './re_ext' );
Expand All @@ -23,7 +23,13 @@ function testList ( src ) {
return reList.exec( src );
}

function parseList ( src, options ) {
function parseList ( src, options, charOffset, charPosToLine ) {
if ( options.showOriginalLineNumber ) {
const removedSrc = src.match( /(^|\r?\n)[\t ]+/ );
if ( removedSrc && removedSrc[0] ) {
charOffset++;
}
}
src = ribbon( src.replace( /(^|\r?\n)[\t ]+/, '$1' ) );

const stack = [];
Expand Down Expand Up @@ -58,6 +64,8 @@ function parseList ( src, options ) {
pba = pba[1];
}

pba = addLineNumber( pba, options, charPosToLine, charOffset, src.getPos() );

// list control
if ( /^\.\s*$/.test( m[2] ) ) {
listAttr = pba || {};
Expand Down
15 changes: 12 additions & 3 deletions src/textile/table.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const re = require( '../re' );
const merge = require( '../merge' );
const ribbon = require( '../ribbon' );

const { parseAttr } = require( './attr' );
const { parseAttr, addLineNumber } = require( './attr' );
const { parsePhrase } = require( './phrase' );
const { reIndent } = require( '../jsonml' );

Expand Down Expand Up @@ -55,7 +55,13 @@ function testTable ( src ) {
return reTable.exec( src );
}

function parseTable ( src, options ) {
function parseTable ( src, options, charOffset, charPosToLine ) {
if ( options.showOriginalLineNumber ) {
const removedSrc = src.match( /^\s+/ );
if ( removedSrc && removedSrc[0] ) {
charOffset += removedSrc[0].length;
}
}
src = ribbon( src.trim() );

const rowgroups = [];
Expand Down Expand Up @@ -128,7 +134,10 @@ function parseTable ( src, options ) {

if ( m[2] && ( pba = parseAttr( m[2], 'tr' ) ) ) {
// FIXME: requires "\.\s?" -- else what ?
row.push( pba[1] );
row.push( addLineNumber( pba[1], options, charPosToLine, charOffset, src.getPos() ) );
}
else {
row.push( addLineNumber({}, options, charPosToLine, charOffset, src.getPos() ) );
}

tCurr.push( '\n\t\t', row );
Expand Down
Loading