├── .gitignore
├── test
├── test.js
├── benchmark.js
├── fixtures
│ └── sample.textile
└── core.js
├── Makefile
├── package.json
├── README.textile
├── bin
└── stextile
├── LICENSE
└── lib
└── stextile.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | *.swo
3 | *.DS_Store
4 | node_modules
5 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | var tests = require(__dirname + '/core')
2 | , textile = require(__dirname + '/../lib/stextile');
3 |
4 | tests.run(textile);
5 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | sample:
2 | @./bin/stextile test/fixtures/sample.textile
3 |
4 | test:
5 | node test/test.js
6 |
7 | benchmark:
8 | node test/benchmark.js
9 |
10 | .PHONY: test
11 |
--------------------------------------------------------------------------------
/test/benchmark.js:
--------------------------------------------------------------------------------
1 | var tests = require(__dirname + '/core')
2 | , Benchmark = require('benchmark')
3 | , suite = new Benchmark.Suite;
4 |
5 | suite.add('textile', function() {
6 | tests.run(require(__dirname + '/../lib/stextile'));
7 | })
8 | .on('cycle', function(event, bench) {
9 | console.log(String(bench));
10 | })
11 | .on('complete', function() {
12 | })
13 | .run(true);
14 |
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stextile",
3 | "description": "Simple textile parser",
4 | "version": "0.0.8",
5 | "author": "Alex R. Young ",
6 | "repository": "git://github.com/alexyoung/stextile",
7 | "main": "./lib/stextile.js",
8 | "bin": { "stextile": "./bin/stextile" },
9 | "devDependencies": { "benchmark": "latest" },
10 | "engines": { "node": ">= 0.4.0" }
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/README.textile:
--------------------------------------------------------------------------------
1 | h3. stextile
2 |
3 | A simple "textile":http://www.textism.com/tools/textile/index.php parser, designed to be good enough to parse the formatting used in DailyJS articles.
4 |
5 | h3. Installation
6 |
7 | npm install stextile
8 |
9 | h3. Usage
10 |
11 | In a Node project:
12 |
13 |
14 | var textile = require('stextile');
15 |
16 | textile('h1. A header');
17 |
18 |
19 | In the shell:
20 |
21 |
22 | $ stextile file.textile
23 |
24 | # HTML printed to stdout
25 |
26 |
27 | h3. TO-DO
28 |
29 | * Footnotes
30 | * Acronyms
31 | * Benchmarks
32 | * Optimisation
33 | * Attributes for inline elements
34 | * Indentation
35 |
36 | h3. Notes
37 |
38 | https://github.com/jgm/peg-markdown/blob/master/markdown_parser.leg
39 | https://github.com/jgarber/redcloth/blob/master/ragel/redcloth_scan.rl
40 |
41 | h3. License
42 |
43 | The MIT License.
44 |
--------------------------------------------------------------------------------
/bin/stextile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var textile;
4 |
5 | try {
6 | textile = require('../lib/stextile');
7 | } catch (err) {
8 | textile = require('stextile');
9 | }
10 |
11 | var args = process.argv.slice(2)
12 | , files = [];
13 |
14 | while (args.length) {
15 | arg = args.shift();
16 | switch (arg) {
17 | case '-h':
18 | case '--help':
19 | console.log('TO-DO');
20 | process.exit(1);
21 | default:
22 | files.push(arg);
23 | }
24 | }
25 |
26 | if (files.length) {
27 | var fs = require('fs');
28 |
29 | while (files.length) {
30 | var file = files.shift();
31 | fs.readFile(file, 'utf8', function(err, data) {
32 | if (err) throw(err);
33 | console.log(textile(data));
34 | });
35 | }
36 | } else {
37 | var buf = '';
38 | process.stdin.setEncoding('utf8');
39 | process.stdin.on('data', function(chunk) {
40 | buf += chunk;
41 | }).on('end', function() {
42 | console.log(textile(buf));
43 | }).resume();
44 | }
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | (The MIT License)
2 |
3 | Copyright (c) 2011 Alex R. Young
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | 'Software'), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/test/fixtures/sample.textile:
--------------------------------------------------------------------------------
1 | h2{color:green}. This is a title
2 |
3 | h3. This is a subhead
4 |
5 | p{color:red}. This is some text of 'dubious character'. Isn't the use of "quotes" just lazy writing -- and theft of 'intellectual property' besides? I think the time has come to see a block quote.
6 |
7 | bq[fr]. This is a block quote. I'll admit it's not the most exciting block quote ever devised.
8 |
9 | Simple list:
10 |
11 | #{color:blue} one
12 | # two
13 | # three
14 |
15 | Multi-level list:
16 |
17 | # one
18 | ## aye
19 | ## bee
20 | ## see
21 | # two
22 | ## x
23 | ## y
24 | # three
25 |
26 | Mixed list:
27 |
28 | * Point one
29 | * Point two
30 | ## Step 1
31 | ## Step 2
32 | ## Step 3
33 | * Point three
34 | ** Sub point 1
35 | ** Sub point 2
36 |
37 |
38 | Well, that went well. How about we insert an old-fashioned hypertext link? Will the quote marks in the tags get messed up? No!
39 |
40 | "This is a link (optional title)":http://www.textism.com
41 |
42 | This is a simple table:
43 |
44 | |A|_B_|C|
45 | |D|*E*|F|
46 |
47 | This table has a rowspan:
48 |
49 | |A|\2. C|
50 | |D|/2. E |F|
51 |
52 | table{border:1px solid black}.
53 | |_. this|_. is|_. a|_. header|
54 | <{background:gray}[en](test). |\2. this is|{background:red;width:200px}. a|^<>{height:200px}. row|
55 | |this|<>{padding:10px}. is|^. another|>(bob#bob). row|
56 |
57 | An image:
58 |
59 | !http://dailyjs.com/images/posts/d3.jpg(optional alt text)!
60 |
61 | # Librarians rule
62 | # Yes they do
63 | # But you knew that
64 |
65 | Some more text of dubious character. Here is some string of CAPITAL letters. Here is something we want to _emphasize_.
66 | That was a linebreak. And something to indicate *strength*. Of course I could use my own HTML tags if I felt like it.
67 |
68 | h3. Coding
69 |
70 | This is some code, "isn't it". Watch those quote marks! Now for some preformatted text:
71 |
72 |
73 |
74 | $text = str_replace("%::%
","",$text);
75 | $text = str_replace("%::%","",$text);
76 | $text = str_replace("%::%","",$text);
77 |
78 |
79 |
80 | This isn't code.
81 |
82 |
83 | So you see, my friends:
84 |
85 | * The time is now
86 | * The time is not later
87 | * The time is not yesterday
88 | * We must act
89 |
90 |
--------------------------------------------------------------------------------
/test/core.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert')
2 | , textile
3 | , tests;
4 |
5 | tests = {
6 | 'run': function(t) {
7 | textile = t;
8 |
9 | for (var key in tests) {
10 | if (/^test/.test(key)) {
11 | tests[key]();
12 | }
13 | }
14 | },
15 |
16 | 'test paragraphs': function() {
17 | assert.equal('A paragraph
\n', textile('A paragraph\n')); 18 | assert.equal('A paragraph
\n', textile('p. A paragraph\n')); 19 | assert.equal('A paragraph
\n', textile('p=. A paragraph\n')); 20 | }, 21 | 22 | 'test headers': function() { 23 | assert.equal('Hello again.
\n', textile('h3. Hello World\n\nHello again.\n')); 26 | }, 27 | 28 | 'test performatted with links': function() { 29 | assert.equal("some code: '{\"key\":\"value\"}', and this is not
\'{"key":"value"}\', and this is not'));
30 | assert.equal("'{\"key\":\"value\"}',
\'{"key":"value"}\','));
31 | assert.equal("'{\"key (example)\":\"value\"}',
\'{"key (example)":"value"}\','));
32 | },
33 |
34 | 'test links': function() {
35 | assert.equal('This is a link: Example and sometimes another example 2
\n', textile('This is a link: "Example":http://example.com/a_and_b and sometimes another "example 2":http://example.com/2')); 36 | 37 | // Links with fullstops 38 | assert.equal('\n', textile('"Example":http://example.com.')); 39 | 40 | // Commas 41 | assert.equal('Example, text
\n', textile('"Example":http://example.com, text')); 42 | 43 | // Links 44 | assert.equal('\n', textile('"Example":http://example.com')); 45 | assert.equal('\n', textile('"Example (Title)":http://example.com')); 46 | 47 | // Multiple links 48 | assert.equal( 49 | '\n', 50 | textile('"Example":http://example.com and "Example 2":http://example.com/2') 51 | ); 52 | }, 53 | 54 | 'test escapeHTML': function() { 55 | assert.equal('\na & p\n\n', textile('
\na & p\n')); 56 | assert.equal('
\n<a>\n\n', textile('
\n\n')); 57 | }, 58 | 59 | 'test bold': function() { 60 | assert.equal('
Some bold text here
\n', textile('Some *bold text* here')); 61 | assert.equal('Some emphasised text here
\n', textile('Some _emphasised text_ here')); 62 | assert.equal('Some added text here
\n', textile('Some +added text+ here')); 63 | assert.equal('Some removed text here
A quote\n', textile('bq. A quote\n')); 80 | assert.equal('
A quote\n', textile('bq(example). A quote\n')); 81 | assert.equal('
A quote\n', textile('bq[en]. A quote\n'));
82 | },
83 |
84 | 'test tables': function() {
85 | assert.equal('| A |
| B |
| A | B |
| C | D |
| A |
| B |
‘It’s clear that the fox said “hello”.’
\n', textile('\'It\'s clear that the fox said "hello".\'\n')); 93 | }, 94 | 95 | 'test preformatted': function() { 96 | assert.equal('This !is!\n code\n', textile('
This !is!\n code')); 97 | assert.equal('
This is code
This is code'));
98 | assert.equal('This !is! code
This !is! code'));
99 | assert.equal('This "is"\n \'code\'\n', textile('
This "is"\n \'code\'')); 100 | assert.equal('
\nThis "is"\n \'code\'\n\n', textile('
\nThis "is"\n \'code\'\n')); 101 | assert.equal( 102 | '
a link here and then weird code \'{"key":"value"}\' etc.
\'{"key":"value"}\' etc.')
104 | );
105 | },
106 |
107 | 'test code and links': function() {
108 | var codeAndLinks = 'I made then just defer to the current promise object, and used apply to call it with arguments. If any beginners find this puzzling, give "arguments":https://developer.mozilla.org/en/JavaScript/Reference/functions_and_function_scope/arguments and "apply":https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/apply a read on MDN. These language features are extremely useful for making flexible APIs.';
109 |
110 | var codeAndLinksHTML = 'I made then just defer to the current promise object, and used apply to call it with arguments. If any beginners find this puzzling, give arguments and apply a read on MDN. These language features are extremely useful for making flexible APIs.





More text
\n
OMG@!?!
\n', textile('OMG@!?!\n')); 145 | 146 | // Similar but valid 147 | assert.equal('
I just finished reading OOP The Good Parts: Message Passing, Duck Typing, Object Composition, and not Inheritance by Nick Fitzgerald. Obvious references to Douglas Crockford’s JavaScript: The Good Parts aside, the article builds on some interesting points by authors like Raganwald that will resonate with many DailyJS readers:
')
161 | );
162 | },
163 |
164 | 'test block-level tags': function() {
165 | // Block-level tags should be left alone
166 | assert.equal(
167 | 'This is a quote\n', 168 | textile('
This is a quote\n') 169 | ); 170 | 171 | assert.equal( 172 | '
\nThis is a quote\n\n', 173 | textile('
\nThis is a quote\n\n') 174 | ); 175 | } 176 | }; 177 | 178 | module.exports = tests; 179 | 180 | -------------------------------------------------------------------------------- /lib/stextile.js: -------------------------------------------------------------------------------- 1 | function Textile() { 2 | // Options 3 | this.indentSize = 2; 4 | 5 | // State 6 | this.inList = 0; 7 | this.listTypes = []; 8 | this.inPreformattedBlock = 0; 9 | this.inExistingBlock = 0; 10 | this.inPreformattedContent = false; 11 | this.inTable = 0; 12 | 13 | // Transforms 14 | this.quotes = { 15 | startDoubleQuote: '“' 16 | , endDoubleQuote: '”' 17 | , startSingleQuote: '‘' 18 | , endSingleQuote: '’' 19 | }; 20 | 21 | this.entities = { 22 | apostrophe: ["'", '’'] 23 | , doubleHyphen: ['--', '—'] 24 | , singleHyphen: ['-', '–'] 25 | , ellipsis: ['\\.\\.\\.', '…'] 26 | }; 27 | } 28 | 29 | Textile.prototype = { 30 | parse: function(text) { 31 | if (!text) return; 32 | 33 | var lines = text.split(/\n|\r\n/) 34 | , line 35 | , result = ''; 36 | 37 | for (var i = 0; i < lines.length; i++) { 38 | line = lines[i]; 39 | 40 | if (this.inList && !this.matchList(line)) { 41 | var listType = this.popListType(); 42 | result += this.indent(this.inPreformattedContent) + '