├── .gitignore ├── .npmignore ├── CONTRIB.org ├── LICENSE ├── OrgModeParser.njsproj ├── OrgModeParser.sln ├── README-WIN64.md ├── README.org ├── RELEASE_HISTORY ├── bin ├── installationTest └── releaseVersion ├── examples ├── basic-example.js ├── example-org-file-serving.js ├── org-query-example.js ├── org-query-example2.js └── site-publisher │ ├── doPublish.el │ ├── org │ ├── index.org │ ├── page2.org │ └── unlinkedpage.org │ ├── publish.sh │ └── publisher.org ├── fast-install.sh ├── lib └── org-mode-parser.js ├── package-lock.json ├── package.json ├── runVows ├── runVowsTest.cmd └── test ├── bug3DrawerConfusion.org ├── colon_mistake.org ├── drawerAndArchiveTag.org ├── fullHtml.jade ├── htmlTest.org ├── includeTest.org ├── issue11-empty-head.org ├── loggingUsageExampleTest.js ├── no-header.org ├── orgmodeTest.org ├── parserTest.js ├── performanceTests.js ├── tableTest.org ├── to-html-test.js ├── treeLevel.org └── treeLevelComplex.org /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *~ 3 | .monitor 4 | setup.sh 5 | obj/ 6 | bin/Microsoft.NodejsTools.WebRole.dll 7 | renderTest.html 8 | .vs/ 9 | org-parser-debug.log 10 | npm-debug.log 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *~ 2 | oldpythonparser.py 3 | bin/ 4 | -------------------------------------------------------------------------------- /CONTRIB.org: -------------------------------------------------------------------------------- 1 | * 2017 Contributors 2 | 1. Jinwoo Lee "Create Date objects with correct months" 3 | 2. Ben Miller "create makelistSync function" 4 | 3. Eric Doughty-Papassideris "tolerate whitespace after drawer block 5 | start" 6 | 7 | 8 | * 2016 Contributors 9 | OrgMode Parser major contributors: 10 | 1. aviav 11 | 2. Giovanni Giorgi 12 | 3. guozhen 13 | 4. Kev Zettler 14 | 5. Matt Lavin 15 | 6. natto 16 | 7. nomosyn 17 | 8. thesebas 18 | 19 | 20 | 21 | For a nice view see 22 | https://github.com/daitangio/org-mode-parser/graphs/contributors 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Giovanni Agostino Andrea Giorgi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /OrgModeParser.njsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | 2.0 6 | {18f88025-5e18-4aad-917d-9bfd915b6c86} 7 | 8 | ShowAllFiles 9 | 10 | . 11 | . 12 | {3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD} 13 | 11.0 14 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 15 | OrgModeParser 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | False 49 | True 50 | 0 51 | / 52 | http://localhost:48022/ 53 | False 54 | True 55 | http://localhost:1337 56 | False 57 | 58 | 59 | 60 | 61 | 62 | 63 | CurrentPage 64 | True 65 | False 66 | False 67 | False 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | False 77 | False 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /OrgModeParser.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "OrgModeParser", "OrgModeParser.njsproj", "{18F88025-5E18-4AAD-917D-9BFD915B6C86}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {18F88025-5E18-4AAD-917D-9BFD915B6C86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {18F88025-5E18-4AAD-917D-9BFD915B6C86}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {18F88025-5E18-4AAD-917D-9BFD915B6C86}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {18F88025-5E18-4AAD-917D-9BFD915B6C86}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /README-WIN64.md: -------------------------------------------------------------------------------- 1 | # Installation under windows 2 | 3 | # With Visual Studio Community 2015 4 | 5 | 1. Install the nodejs tools plugin for visual studio: 6 | https://github.com/Microsoft/nodejstools/ 7 | 2. Open the OrgModeParser.sln file 8 | 3. Under Tools/External Tools register the runVowsTest.cmd 9 | 4. Drink a beer 10 | 11 | # Without Visual Studio 2015 12 | 13 | 1. Download NodeJS (64bit suggested) 14 | 2. Install the dependencies GLOBALLY: 15 | 16 | npm install -g vows@0.8.1 17 | npm install -g underscore@1.8.3 18 | 19 | 4. run the test via the runVowsTest.cmd 20 | 21 | Performance on SonyVaio Intel-i5: 22 | Query Parser build time of 1280 Nodes:50ms 23 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE:README.org 2 | * A Nodejs org mode parser module 3 | ** "So What?" 4 | This node.js module implements an [[http://orgmode.org/][org-mode]] file format parser. 5 | Org-mode is a cool Emacs package that lets you structure 6 | information in a nice way, and export it in html, latex, pdf and so 7 | on. 8 | ** What does org-mode-parser do that I couldn't do before? 9 | I was unable to find a JavaScript parser for org-mode. Now we have 10 | one. Org-mode is so useful you will start writing in org-mode 11 | instead of text files for everything needing a bit of structuring. 12 | 13 | It is also _XML-free_ and yes, we like it :) 14 | ** What are further benefits? 15 | A lot of unit testing (over 100) and a pretty fast parser (see 16 | below). Also, it has minimal requirements. 17 | ** Where can I find more news? 18 | Take a look at http://gioorgi.com/tag/org-mode-parser/ for latest 19 | news 20 | ** Installation 21 | The full installation can be obtained via the npm package 22 | repository. 23 | On a shell which can run node, try out these lines: 24 | #+BEGIN_SRC shell :tangle fast-install.sh 25 | curl http://nodejs.org/dist/v0.10.28/node-v0.10.28.tar.gz | tar xzvf - 26 | (cd node-v0.10.28/ ; ./configure --prefix=$HOME ; make && make install) 27 | curl --insecure https://www.npmjs.org/install.sh | bash 28 | npm install org-mode-parser 29 | #+END_SRC 30 | or launch fast-install.sh 31 | ** Usage example: 32 | # Look at 33 | # http://orgmode.org/manual/Code-block-specific-header-arguments.html 34 | # for the syntax of BEGIN_SRC :tangle. Anyway, org-babel-tangle 35 | # will export this source 36 | #+BEGIN_SRC js -n -r :tangle examples/basic-example.js 37 | var org=require('../lib/org-mode-parser'); 38 | org.makelist("README.org", function (nodelist){ 39 | // Here nodelist is a list of Orgnode objects (ref:putyourcode) 40 | console.dir(nodelist[0]); 41 | }); 42 | #+END_SRC 43 | * API 44 | The parser's main entry point is the makelist function, which accepts 45 | a filename and a callback function. makelist() will pass to the 46 | function the list of parsed nodes as first parameter, as described in 47 | the [[opening example]]. 48 | 49 | You can optionally build a query object called OrgQuery to easily 50 | select subtree, search tags, et cetera: 51 | 52 | #+BEGIN_SRC javascript -n -r :tangle examples/org-query-example.js 53 | var org=require('../lib/org-mode-parser'); 54 | org.makelist("./test/treeLevel.org",function(nodes){ 55 | var ofd=new org.OrgQuery(nodes); 56 | // ofd is a complex object for use in querying and so on 57 | console.log(ofd.selectTag('complex').first().toOrgString()); 58 | }); 59 | #+END_SRC 60 | 61 | Supported methods of OrgQuery are: 62 | + selectSubtree(node) 63 | Extract the nodes hierarchically below the given input 64 | + selectTag(tagString) 65 | + sortBy 66 | + reject(function) 67 | + rejectTag(tagName) 68 | + toArray() 69 | + each(functionToPassEach) 70 | + random() 71 | Extract a random element 72 | + toHtml(options) 73 | Generate a fair html rendering. 74 | It also support pug (jade) template system. 75 | This code is not meant to replace org-mode export functionality, 76 | but can be an handy friend. 77 | For usage example see 78 | test/to-html-test.js 79 | 80 | See the unit test section 'basicLibraries OrgQuery-Complex' on 81 | test/parseTest.js for more usage examples, and do not miss the [[FAQ]] 82 | ** Known Limitations 83 | The input must be a well-formed org-mode file. Parser can detect some 84 | corruptions, but it does not provide a complete sanity check. 85 | 86 | Every text must have a header. 87 | Parser does not support "no-headed" inputs. 88 | Anyway, empty headers are supported (see github Issue #11) 89 | 90 | * Design Goals 91 | ** API Stability 92 | In general, every API component described in the [[API]] section is 93 | here to stay. Compatibility will be retained as far as possible in 94 | future releases. 95 | ** Performance 96 | At the time of writing, the parser is pretty fast. On a Linux 97 | virtual machine, we get about 20.000 nodes per seconds. We will 98 | keep an eye on performance. 99 | 100 | You are welcome to help us stress test the parser and find its true 101 | limits. 102 | ** Minimize dependency 103 | Org-mode-parser depends only on two packages, underscore and 104 | vows. Vows dependency is used only for regression tests, so the 105 | parser really depends only on underscore. 106 | ** Tests are documentation 107 | Take a look at the examples/ directory for some tiny examples. 108 | Please look at test/parserTest.js file for API usage examples. 109 | Tests are commented and pretty self-explanatory: they are the 110 | primary source for correctness of this module. 111 | * FAQ 112 | ** Where can I find stable packages? 113 | On npm repository. [[https://github.com/daitangio/org-mode-parser/tree/master][The master branch on GitHub]] is the development 114 | version, so use it at your own risk. 115 | ** Who is my best friend? 116 | OrgQuery is a very handy object (see below), because it allows you 117 | to filter nodes in a structured way. Use it instead of 118 | hand-parsing. 119 | ** How can I get rid of archived nodes? 120 | Use the OrgQuery.rejectArchived() method 121 | ** Are undeclared drawers parsed? 122 | Yes, but org mode wants them to be declared (see par 2.9 Drawers on 123 | documentation). Thus, it is best to not rely on undeclared drawers, 124 | because the parser could change in the future to be more 125 | stringent. Also, undeclared drawers are not indented! 126 | ** Querying Questions 127 | *** How can I find all the subnodes of the node tagged releaseNotes and query it? 128 | #+BEGIN_SRC javascript -n -r :tangle examples/org-query-example2.js 129 | var org=require('../lib/org-mode-parser'); 130 | org.makelist("./README.org",function(nl){ 131 | var q=new org.OrgQuery(nl); 132 | var subtree=q.selectSubtree(q.selectTag('releaseNotes').first()); 133 | console.log("Dev version is:"+subtree.selectTag('dev').first().headline); 134 | }); 135 | #+END_SRC 136 | ** Do you support SETUPFILE and INCLUDE? 137 | No, at least not at the moment. 138 | 139 | * Github 140 | https://github.com/daitangio/org-mode-parser 141 | * Development HOWTO 142 | Globally install vows and try out something like: 143 | 144 | #+BEGIN_SRC shell :tangle ./bin/run-test-suite.sh 145 | npm install -g vows@0.7.0 146 | NODE_PATH=$(dirname $(which node))/../lib/node_modules:. ./bin/testme 147 | #+END_SRC 148 | * Release command sequence / Kitchensink :kitchensink: 149 | At the time of writing, the github repository is the master code 150 | repository 151 | 1. Check the package.json version 152 | 2. Issue the following commands: 153 | #+BEGIN_SRC shell 154 | ./bin/releaseVersion.sh ORG_MODE_PARSER_0.0.6 155 | #+END_SRC 156 | 157 | 158 | ** Known bugs in progress 159 | Fixing a known bug on vows which is creating false positive on tests 160 | https://github.com/vowsjs/vows/issues/187 161 | 162 | "vows":">=0.5.11" worked, then an incompatibile change was made on 163 | vows 164 | 165 | Fixes #11 nodelist does not include headers with no text 166 | -------------------------------------------------------------------------------- /RELEASE_HISTORY: -------------------------------------------------------------------------------- 1 | # -*- mode: org ; mode: visual-line; -*- -* 2 | 3 | * RELEASE NOTES :releaseNotes: 4 | ** ORG_MODE_PARSER_0.1.5 5 | Code name: Reika 6 | + Support for basic INCLUDE statement 7 | Include is very powerful and expressive 8 | 9 | ** ORG_MODE_PARSER_0.1.4 10 | + Node v8.11.1 compatibility test 11 | + Bug fixes (Issue 17) 12 | 13 | ** ORG_MODE_PARSER_0.1.3 14 | Code name: Beauty 15 | 16 | + Fixed empty header issue 17 | + Code Cleanup 18 | + Fixed vows bugs, unit tests works flawless 19 | + Migration from jade to pug2 (new name of jade) 20 | + Added optional logging framework (winston) 21 | + Started evaluting integration with 22 | https://github.com/mooz/org-js/ 23 | 24 | *** KNOWN MINOR PROBLEMS 25 | On Node 7.x vows 0.8 fails on some tests (to-html tests). Anyway it seems only a 26 | a problem of unit testing. 27 | 28 | 29 | 30 | ** ORG_MODE_PARSER_0.1.2 31 | Code name: Tetzuya 32 | 33 | + Simple html rendering engine. 34 | Jade is an optional requirement if you want to use it as template 35 | engine. 36 | It is required on-demand. 37 | Anyway we suggest to install it 38 | + Support for source block #+begin_src / #+end_src 39 | + Quickcheck for stronger unit test (only embrional setup) 40 | + Fixes by contributors: 41 | 1) Support for closed timestamp (thesebas/feature/closed-timestamp) 42 | 2) data-language addition (by guozhen) 43 | 3) [fix] don't parse lien when processing source code (by guozhen) 44 | 4) Style/language pass on README.org (by aviav) 45 | 5) fix typo in README.org (by kevzettler) 46 | 6) fix up tortureTest to not crash the parser in the malformed 47 | property drawer (by natto) 48 | 7) Merge branch 'client-side-rendering' into api/extension 49 | (giorgi + natto) 50 | + Nodejs 4.x+ required (do not tested below, should work anyway) 51 | + Added CONTTRIB file 52 | 53 | Potential break-change: 54 | Now unknown directive are *retained* 55 | 56 | ** ORG_MODE_PARSER_0.1.1 57 | Republish (0.1.0 never see light) 58 | ** ORG_MODE_PARSER_0.1.0 59 | + Visual Studio 2015 compatibility 60 | + Tested on node v0.12.2 61 | ** ORG_MODE_PARSER_0.0.9 62 | + API Change: IllegalArgumentException and ParseError disappears. They are replaced with simple Error object 63 | because in node 0.10.x the toString() of the old objects was wrong. 64 | /Parsing error are targeted to human beings, so avoid trapping them 65 | for complex recovery puropse/ 66 | ** ORG_MODE_PARSER_0.0.8 67 | + Added the examples/site-publisher example 68 | + Tested against latest node version 69 | + Removed direct dependecy from vows, only used for testing 70 | ** ORG_MODE_PARSER_0.0.7 71 | *** Stringent selectSubtree API (BUGFIX). 72 | anOrgQuery.selectSubtree(i) now will accept only these types of i objects: 73 | + A Orgnode 74 | + A 1-size OrgQuery collection 75 | + empty/null 76 | 77 | /Keep in mind the following rule for understanding the behavior:/ 78 | 79 | q.selectSubtree(emptyNodeList) === q.selectSubtree(null) == q.selectSubtree() === q 80 | 81 | which is a bit naif, but it will not break the existing API. 82 | 83 | *** Magic avoided 84 | Evaluted the option of omitting first() on mono results, but API gets dirty. 85 | For play with it, see commit tag dev_orgquery_one_node_merge (6dd58da5e3a90e3f651bba4949cbe7b95155bc6b) 86 | and serch for the use of the "mergeFrom" method, now disabled. 87 | 88 | ** ORG_MODE_PARSER_0.0.6 89 | Minor documentation fixes 90 | 91 | ** ORG_MODE_PARSER_0.0.5 92 | 1. Addedd support for generic :DRAWER: syntax 93 | 2. Archive tag is supported 94 | Missed: 95 | + links/ Footnotes are still completly missed 96 | + Ordered/Bulletin list are still missed, 97 | but org-mode will present it in a nice way anyway 98 | 99 | ** ORG_MODE_PARSER_0.0.4 100 | 1) Added new OrgQuery methods: 101 | 1. sortBy 102 | 2. reject 103 | 3. toArray 104 | 4. each 105 | 2) :PROPERTIES: without :END: generates an error now. 106 | The parser is quite weak, but can detect this simple case. 107 | BUGFIXES: 108 | + OrgQuery had a bug, and collected nodes could not be unique in some rare situations. 109 | Now we relay on underscore library for generating unique id 110 | + Added a set of stronger guards on constructors 111 | 112 | ** ORG_MODE_PARSER_0.0.3 113 | 1) Added the ability to regenerate the Orgnode as string using the method 114 | toOrgString() 115 | Be carefully, the method is still experimental and do not emit: 116 | a) Comments 117 | b) SCHEDULE,DEADLINE and CLOCK directive 118 | 2) Added the OrgQuery object, for doing queries like 119 | + subtree extraction with .selectSubtree 120 | + tag-based searches with selectTag 121 | Even if the OrgQuery try to play nice, it is not yet an array, so 122 | avoid using it directly with _.each(...) 123 | 124 | 125 | *** KNOWN LIMITATIONS 126 | 1) Comments are stripped off during parsing. 127 | 2) Special directive starting with '#+' are mostly ignored during the parsing, 128 | for instance #+AUTHOR etc 129 | 3) Tables are not parsed at all. 130 | 4) In org-mode tags cannot have "-" character in name. They are split in subwords. 131 | The parser allow this instead, so be careful when editing by hand org files. 132 | 5) properties can have "-" but this will force 133 | you to access them with the array syntax instead of the dot notation, so we 134 | strongly suggest to avoid "-" and special java character in property names. 135 | Relay on "_", for instance. 136 | 137 | ** ORG_MODE_PARSER_0.0.2 138 | 1) SCHEDULE,DEADLINE and CLOCK directives now are correctly parsed 139 | 2) Added a performance watchdog to track slowdowns 140 | 3) Added the ability to return performance data via makelist 141 | 4) Started restructuring parser for better performance. 142 | 5) Minor API Change: null is the default value for tag,priority,scheduled, deadline 143 | when not set. 144 | e.tags.existingtag is true if existingtag is there. 145 | Anyway is better to use 146 | "existingtag" in e.tags 147 | which is a better syntax 148 | ** ORG_MODE_PARSER_0.0.1 149 | First revision 150 | -------------------------------------------------------------------------------- /bin/installationTest: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e -x 3 | echo Testing install 4 | cdir=$(pwd) 5 | tempdir=/tmp/$$temp 6 | #npm install . -g -d 7 | mkdir $tempdir 8 | cd $tempdir 9 | # Cygwin pactch: 10 | # /cygdrive/c/giorgi/code/org-mode-parser/../org-mode-parser 11 | cdir2=$(echo $cdir | awk '{gsub(/\/cygdrive\/c\//,"c:/"); print}') 12 | npm install ${cdir2}/../org-mode-parser -d 13 | npm ls 14 | echo "a=require('org-mode-parser')" >tester.js 15 | node tester.js || echo "FAILED" 16 | #npm uninstall org-mode-parser -g -d 17 | cd $cdir 18 | pwd 19 | rm -rf $tempdir 20 | #rm $(find . -name "*~") 21 | -------------------------------------------------------------------------------- /bin/releaseVersion: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Internal housekeeping only 3 | # RESEE DOES NOT WORK VERY WELL 4 | # Syntax: 5 | # bin/releaseVersion [push] 6 | # Without publish only checks 7 | set -e -u 8 | binDir=$(dirname $0) 9 | tagVersion=$( grep version $binDir/../package.json | cut -d \" -f 4 ) 10 | echo Ready for release $tagVersion 11 | echo Check the status 12 | #git commit --dry-run -a -m "Riepilogo" || exit 2000 13 | vows --spec -v --cover-plain test/*.js 14 | $(dirname $0)/installationTest || exit 1000 15 | set +u 16 | if [ "$1" == "push" ]; then 17 | read -e -p 'Proceed (y/n)?' -i y reply 18 | test "$reply" != "y" && exit 19 | echo Commit... 20 | git commit -a -v -m "Delivered tag revision $tagVersion" || echo "?Nothing to commit" 21 | git tag -a -m "Revision $tagVersion" $tagVersion 22 | git tag 23 | git push -v --tags 24 | git push -v 25 | npm publish 26 | fi 27 | -------------------------------------------------------------------------------- /examples/basic-example.js: -------------------------------------------------------------------------------- 1 | 2 | var org=require('../lib/org-mode-parser'); 3 | org.makelist("README.org", function (nodelist){ 4 | // Here nodelist is a list of Orgnode objects 5 | console.dir(nodelist[0]); 6 | }); 7 | -------------------------------------------------------------------------------- /examples/example-org-file-serving.js: -------------------------------------------------------------------------------- 1 | 2 | /*** 3 | * A very very simple example of rendering of a org file in a 4 | * browsable web site. 5 | * The rendering is fair simple, 6 | * and the data structure needs improvements. 7 | * Anyway, it rocks, try out with 8 | * org-mode-parser>nodemon examples/example-org-file-serving.js ./README.org 9 | */ 10 | var http = require('http'); 11 | var path = require('path'); 12 | var fs = require('fs'); 13 | var util=require('util'); 14 | var _=require("underscore"); 15 | 16 | var orgParser=require("../lib/org-mode-parser"); 17 | 18 | 19 | var orgNodes=orgParser.makelist(process.argv[2], function (nodes){ 20 | 21 | 22 | function renderNavigation(node,vfilePath){ 23 | var content=""; 24 | //webNextNode 25 | if(node.webNextNode){ 26 | content +=" Next "+ node.webNextNode.webPath; 27 | content +="
"; 28 | } 29 | 30 | content+="

Sub Nodes

"; 31 | _.each(_.keys(path2node),function (pathx){ 32 | if(pathx.match(vfilePath)){ 33 | content+=''+pathx+'
'; 34 | } 35 | }); 36 | content +="
TOP"; 37 | return content; 38 | } 39 | 40 | function renderNode(node,vfilePath){ 41 | var content=node.headline+"
"; 42 | content+="
"+node.body+"
"; 43 | // Find out sub paths... 44 | content+=renderNavigation(node,vfilePath); 45 | return content; 46 | } 47 | 48 | 49 | 50 | 51 | console.log("OK, Read:"+nodes.length+" Nodes"); 52 | // Build an associative array, using headline or special property weburl 53 | var path2node=new Array(); 54 | var handyUrlList=[]; 55 | function mapNode(fatherString,n){ 56 | var path=fatherString+"/"; 57 | if("weburl" in n.properties){ 58 | path+=n.properties["weburl"]; 59 | }else{ 60 | path+=n.headline.toLowerCase() 61 | .replace(/\s/g,'-') 62 | //.replace(/the|and|of/g,"") 63 | .replace(/["'?!*+():,;./]/g,"").replace(/-+/g,"-"); 64 | } 65 | if(path in path2node){ 66 | path+="2"; 67 | console.log("WARN: DUPLICATION..."+path); 68 | } 69 | path2node[path]=n; 70 | n.webPath=path; 71 | handyUrlList.push(path); 72 | return path; 73 | } 74 | var query= new orgParser.OrgQuery(nodes); 75 | 76 | 77 | function processLevel(collection,levelToScan,fatherPath){ 78 | _.each(collection,function (n,i){ 79 | // Process required level and then recurse 80 | if(n.level==levelToScan){ 81 | 82 | var path=mapNode(fatherPath,n); 83 | console.log(levelToScan+' '+ path+"\t=>\t"+n.headline); 84 | var subtree=query.selectSubtree(n); 85 | processLevel(subtree.toArray(),levelToScan+1, path); 86 | } 87 | }); 88 | 89 | } 90 | processLevel(query.toArray(),1,""); 91 | 92 | // Builds webPrevNode,webNextNode 93 | var prevNode=null, nextNode=null; 94 | _.each(nodes, function(n){ 95 | n.webPrevNode=prevNode; 96 | prevNode=n; 97 | if(n.webPrevNode){ 98 | n.webPrevNode.webNextNode=n; 99 | } 100 | }); 101 | 102 | 103 | 104 | //var defaultUrl=handyUrlList[0]; 105 | //console.log("Index:"+defaultUrl); 106 | 107 | http.createServer(function (request, response) { 108 | 109 | console.log('request starting... Dest is:'+request.url); 110 | 111 | var vfilePath = request.url; 112 | 113 | // if (vfilePath == '/'){ 114 | // vfilePath = defaultUrl; 115 | // } 116 | 117 | 118 | if( vfilePath in path2node){ 119 | var node=path2node[vfilePath]; 120 | var content=renderNode(node,vfilePath); 121 | response.writeHead(200, { 'Content-Type': 'text/html' }); 122 | response.end(content, 'utf-8'); 123 | }else{ 124 | // Emit a list... 125 | var content="Path not found. Try out:
"; 126 | _.each(handyUrlList, function (k){ 127 | content +="

"+k+"

"; 128 | content +=renderNavigation(path2node[k],k); 129 | }); 130 | response.writeHead(200, { 'Content-Type': 'text/html' }); 131 | response.end(content, 'utf-8'); 132 | } 133 | 134 | 135 | }).listen(8080); 136 | 137 | console.log('Server running at http://127.0.0.1:8080/'); 138 | 139 | }); 140 | -------------------------------------------------------------------------------- /examples/org-query-example.js: -------------------------------------------------------------------------------- 1 | 2 | var org=require('../lib/org-mode-parser'); 3 | org.makelist("./test/treeLevel.org",function(nodes){ 4 | var ofd=new org.OrgQuery(nodes); 5 | // ofd is a complex object wiith let you do query and so on 6 | console.log(ofd.selectTag('complex').first().toOrgString()); 7 | }); 8 | -------------------------------------------------------------------------------- /examples/org-query-example2.js: -------------------------------------------------------------------------------- 1 | 2 | var org=require('../lib/org-mode-parser'); 3 | org.makelist("./README.org",function(nl){ 4 | var q=new org.OrgQuery(nl); 5 | var subtree=q.selectSubtree(q.selectTag('releaseNotes').first()); 6 | console.log("Dev version is:"+subtree.selectTag('dev').first().headline); 7 | }); 8 | -------------------------------------------------------------------------------- /examples/site-publisher/doPublish.el: -------------------------------------------------------------------------------- 1 | 2 | ; The following code is needed if org-mode is installed under .emacs.d 3 | ; as in my config. 4 | ;;(add-to-list 'load-path "~/.emacs.d/org-7.7/lisp") 5 | 6 | (require 'org-publish) 7 | (setq org-publish-project-alist 8 | '( 9 | 10 | ;; ... add all the components here (see below)... 11 | ("org-notes" 12 | :base-directory "~/org-mode-parser/examples/site-publisher/org" 13 | :base-extension "org" 14 | :publishing-directory "~/org-mode-parser/examples/site-publisher/public_html" 15 | :recursive t 16 | :publishing-function org-publish-org-to-html 17 | :headline-levels 4 ; Just the default for this project. 18 | :auto-preamble t 19 | 20 | :auto-sitemap t ; Generate sitemap.org automagically... 21 | :sitemap-filename "sitemap.org" ; ... call it sitemap.org (it's the default)... 22 | :sitemap-title "Sitemap" ; ... with title 'Sitemap'. 23 | :sitemap-function org-publish-org-sitemap 24 | ) 25 | 26 | ("org-static" 27 | :base-directory "~/org-mode-parser/examples/site-publisher/org" 28 | :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf" 29 | :publishing-directory "~/org-mode-parser/examples/site-publisher/public_html" 30 | :recursive t 31 | :publishing-function org-publish-attachment 32 | ) 33 | 34 | ("nodejsWebSiteExample" :components ("org-notes" "org-static")) 35 | 36 | 37 | )) 38 | 39 | ;; Issue the publish command. 40 | (org-publish (assoc "nodejsWebSiteExample" org-publish-project-alist)) 41 | (message "Site published") 42 | ;; To publish all projects... 43 | ;;(org-publish-all) 44 | -------------------------------------------------------------------------------- /examples/site-publisher/org/index.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: ~/org-mode-parser/examples/site-publisher/publisher.org 2 | #+TITLE: Index Example 3 | 4 | * Index Example 5 | Hi, I am an example of the index of the site publisher 6 | Use the org-insert-export-options-template command to generte the export template 7 | #+INCLUDE: sitemap.org 8 | 9 | [[file:./page2.org]] 10 | 11 | Only linked pages are on sitemap 12 | 13 | ** Heading 2 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/site-publisher/org/page2.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: ~/org-mode-parser/examples/site-publisher/publisher.org 2 | #+TITLE: Page2 3 | * Page 2 example 4 | Example of page2 5 | -------------------------------------------------------------------------------- /examples/site-publisher/org/unlinkedpage.org: -------------------------------------------------------------------------------- 1 | #+SETUPFILE: ~/org-mode-parser/examples/site-publisher/publisher.org 2 | #+TITLE: I will be on sitemap 3 | * Unlinked page test 4 | I hope so 5 | #+INCLUDE: sitemap.org 6 | -------------------------------------------------------------------------------- /examples/site-publisher/publish.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/bash 3 | time emacs --batch --script doPublish.el 4 | -------------------------------------------------------------------------------- /examples/site-publisher/publisher.org: -------------------------------------------------------------------------------- 1 | -*- mode: org ; mode: visual-line; -*- -* 2 | * Publisher File: Introduction 3 | 4 | The best way of publishing a org-mode based web site is to use the org-mode html publishing feature. 5 | So nodejs will end up 6 | + serving static stuff 7 | + monitoring files and regenerating them with emacs 8 | 9 | 10 | This file generate a set of files and scripts to batch-publish the org file inside the org directory. 11 | To try it out, issue 12 | bash publish.sh 13 | command from the shell 14 | 15 | * How it works 16 | To re-generate the configs and the script issue the emacs command: 17 | org-babel-tangle 18 | when visiting this file. 19 | 20 | 21 | The code layout is taken from 22 | http://orgmode.org/worg/org-tutorials/org-publish-html-tutorial.html 23 | 24 | The website is stored in the org directory 25 | The generated files are stored in public_html 26 | 27 | The org mode publisher is optimized and will not regenerate unmodified files. 28 | This is very handy because will avoid slowing down the system 29 | 30 | ** Config files 31 | 32 | 33 | 34 | #+BEGIN_SRC sh -n -r :tangle publish.sh 35 | #!/bin/bash 36 | time emacs --batch --script doPublish.el 37 | #+END_SRC 38 | 39 | #+BEGIN_SRC elisp -n -r :tangle doPublish.el 40 | ; The following code is needed if org-mode is installed under .emacs.d 41 | ; as in my config. 42 | ;;(add-to-list 'load-path "~/.emacs.d/org-7.7/lisp") 43 | 44 | (require 'org-publish) 45 | (setq org-publish-project-alist 46 | '( 47 | 48 | ;; ... add all the components here (see below)... 49 | ("org-notes" 50 | :base-directory "~/org-mode-parser/examples/site-publisher/org" 51 | :base-extension "org" 52 | :publishing-directory "~/org-mode-parser/examples/site-publisher/public_html" 53 | :recursive t 54 | :publishing-function org-publish-org-to-html 55 | :headline-levels 4 ; Just the default for this project. 56 | :auto-preamble t 57 | 58 | :auto-sitemap t ; Generate sitemap.org automagically... 59 | :sitemap-filename "sitemap.org" ; ... call it sitemap.org (it's the default)... 60 | :sitemap-title "Sitemap" ; ... with title 'Sitemap'. 61 | :sitemap-function org-publish-org-sitemap 62 | ) 63 | 64 | ("org-static" 65 | :base-directory "~/org-mode-parser/examples/site-publisher/org" 66 | :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf" 67 | :publishing-directory "~/org-mode-parser/examples/site-publisher/public_html" 68 | :recursive t 69 | :publishing-function org-publish-attachment 70 | ) 71 | 72 | ("nodejsWebSiteExample" :components ("org-notes" "org-static")) 73 | 74 | 75 | )) 76 | 77 | ;; Issue the publish command. 78 | (org-publish (assoc "nodejsWebSiteExample" org-publish-project-alist)) 79 | (message "Site published") 80 | ;; To publish all projects... 81 | ;;(org-publish-all) 82 | 83 | 84 | 85 | 86 | 87 | #+END_SRC 88 | 89 | ** Publish config shared by all org files 90 | 91 | #+TITLE: PublishConfig 92 | #+AUTHOR: Giovanni Giorgi 93 | #+EMAIL: jj@gioorgi.com 94 | #+DATE: 2011-10-19 Wed 95 | #+DESCRIPTION: 96 | #+KEYWORDS: 97 | #+LANGUAGE: en 98 | #+OPTIONS: H:3 num:t toc:t \n:nil @:t ::t |:t ^:t -:t f:t *:t <:t 99 | #+OPTIONS: TeX:t LaTeX:t skip:nil d:nil todo:t pri:nil tags:not-in-toc 100 | #+INFOJS_OPT: view:content toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js 101 | #+EXPORT_SELECT_TAGS: export 102 | #+EXPORT_EXCLUDE_TAGS: noexport 103 | #+LINK_UP: 104 | #+LINK_HOME: 105 | #+XSLT: 106 | -------------------------------------------------------------------------------- /fast-install.sh: -------------------------------------------------------------------------------- 1 | 2 | curl http://nodejs.org/dist/v0.10.28/node-v0.10.28.tar.gz | tar xzvf - 3 | curl --insecure https://www.npmjs.org/install.sh | bash 4 | npm install org-mode-parser 5 | 6 | ./bin/releaseVersion.sh ORG_MODE_PARSER_0.0.6 7 | -------------------------------------------------------------------------------- /lib/org-mode-parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Giovanni Giorgi jj@gioorgi.com 3 | * For version and example, take a look to: 4 | * http://gioorgi.com/org-mode-parser 5 | * 6 | * 7 | * 8 | * Initally based on Charles Cave's OrgNode python parser (charlesweb@optusnet.com.au) 9 | http://members.optusnet.com.au/~charles57/GTD 10 | * 11 | * 12 | * Permission is hereby granted, free of charge, to any person 13 | * obtaining a copy of this software and associated documentation 14 | * files (the "Software"), to deal in the Software without 15 | * restriction, including without limitation the rights to use, copy, 16 | * modify, merge, publish, distribute, sublicense, and/or sell copies 17 | * of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be 21 | * included in all copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 27 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 28 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 29 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | * SOFTWARE. 31 | */ 32 | 33 | (function (){ 34 | var util=require('util'); 35 | var _=require("underscore"); 36 | var fs=require("fs"); 37 | 38 | 39 | var debug=function (){}; 40 | 41 | function enableDebug(winstonLogger){ 42 | if(winstonLogger){ 43 | debug=winstonLogger.debug; 44 | debug("Org Mode winston logger Activated"); 45 | }else { 46 | // backward compatibility 47 | debug=util.debug; 48 | debug("enableDebug(winstonLogger) is a preferred way of configuring OrgMode Logging"); 49 | } 50 | } 51 | function disableDebug(){ 52 | debug("Disabiling debug logging"); 53 | debug=function (){}; 54 | } 55 | exports.enableDebug=enableDebug; 56 | exports.disableDebug=disableDebug; 57 | 58 | //enableDebug(); 59 | 60 | // Low level functions 61 | var asLines=function (data){ 62 | return data.split(/\n/); 63 | }; 64 | exports.asLines=asLines; 65 | 66 | // Object Merging 67 | // http://stackoverflow.com/questions/5055746/cloning-an-object-in-node-js 68 | // This particular version copy all properties of source on dest: 69 | // dest.mergeFrom(source) 70 | // overriding every object. 71 | // Object.defineProperty(Object.prototype, "mergeFrom", { 72 | // enumerable: false, 73 | // value: function(from) { 74 | // var props = Object.getOwnPropertyNames(from); 75 | // var dest = this; 76 | // props.forEach(function(name) { 77 | // var destination = Object.getOwnPropertyDescriptor(from, name); 78 | // Object.defineProperty(dest, name, destination); 79 | // }); 80 | // this.mergeMark="Merged "+props.length+" props"; 81 | // this.mergedObject=true; 82 | // return this; 83 | // } 84 | // }); 85 | 86 | // End Low Level 87 | 88 | // EXCEPTIONS: canged from 0.8 for better node integration 89 | var IllegalArgumentException=function(m){ 90 | return new Error("IllegalArgumentException: "+ m); 91 | }; 92 | //exports.IllegalArgumentException=IllegalArgumentException; 93 | 94 | 95 | 96 | var ParseError=function(m){ 97 | return new Error("ParseError: "+ m); 98 | }; 99 | //exports.ParseError=ParseError; 100 | 101 | 102 | var Orgnode=(function () 103 | { 104 | /** 105 | * Create an Orgnode object given the parameters of level (as the 106 | raw asterisks), headline text (including the TODO tag), and 107 | first tag. The makelist routine postprocesses the list to 108 | identify TODO tags and updates headline and todo fields. 109 | 110 | */ 111 | function Orgnode(level, headline, body, tag, alltags,drawerArray){ 112 | if(arguments.length!=6){ 113 | throw IllegalArgumentException("Orgnode requires exactly 6 input parameters. Input:"+ 114 | JSON.stringify(_.toArray(arguments))); 115 | } 116 | // alltags MUST BE a collection if present... 117 | if(alltags){ 118 | if(!_.isArray(alltags)){ 119 | throw IllegalArgumentException("alltags parameter must be an array"); 120 | } 121 | } 122 | // drawerArray is present must be a coolection! 123 | if(drawerArray){ 124 | if( !_.isObject(drawerArray)){ 125 | //console.dir(arguments); 126 | throw IllegalArgumentException("drawerArray parameter must be an array"); 127 | } 128 | } 129 | 130 | this.key=_.uniqueId('orgNode_')+"."+level; 131 | this.level = level.length; 132 | this.headline = headline; 133 | this.body = body; 134 | this.tag = tag ; // The first tag in the list 135 | this.tags = {}; // All tags in the headline 136 | this.todo = null; 137 | this.priority = null; // empty of A, B or C 138 | this.scheduled = null; // Scheduled date 139 | this.deadline = null; // Deadline date 140 | this.properties = {}; 141 | 142 | this.drawer=drawerArray; 143 | 144 | _.each(alltags,function (t){ 145 | this.tags[t]=true; 146 | },this); 147 | 148 | 149 | 150 | }; 151 | 152 | Orgnode.prototype.toOrgString = function (){ 153 | //console.dir(this); 154 | var n=''; 155 | for(var i=1; i<=this.level;i++){ 156 | n+='*'; 157 | } 158 | if(this.todo) {n+=' '+this.todo;}; 159 | n+=' '; 160 | if(this.priority){ 161 | n+= '[#' + this.priority + '] '; 162 | } 163 | n+=this.headline; 164 | // tags will start in column 62 165 | while(n.length<=61){ 166 | n+=' '; 167 | } 168 | // Tag expansion 169 | var tagKeys=_.keys(this.tags); 170 | if(tagKeys.length>0){ 171 | _.each(tagKeys, function(t){ 172 | n+=':'+t; 173 | }); 174 | n+=':'; 175 | } 176 | n+='\n'; 177 | // PROPERTIES 178 | var pList=''; 179 | _.each(this.properties,function (v,k){ 180 | pList+=":"+k+":"+v+"\n"; 181 | }); 182 | if(pList!==''){ 183 | n+=":PROPERTIES:\n"+pList+":END:\n"; 184 | } 185 | // Still missed: SCHEDULED, DEADLINE 186 | n+=this.body; 187 | // If last line has a double \n\n remove one of them... 188 | var i2=n.length-1-1; 189 | if(n[i2]===n[i2+1] && n[i2]==='\n'){ 190 | n=n.slice(0,i2+1); 191 | } 192 | return n; 193 | }; 194 | 195 | Orgnode.prototype.isArchived = function (){ 196 | return this.tags['ARCHIVE'] === true; 197 | }; 198 | 199 | return Orgnode; 200 | })(); 201 | 202 | 203 | /*Process a matching regexp on the given line, using the provided function 204 | */ 205 | function processRline(myregexp,line,func){ 206 | var matchParams=line.match(myregexp); 207 | if(matchParams){ 208 | func(matchParams,line); 209 | return true; 210 | }else{ 211 | return false; 212 | } 213 | } 214 | 215 | 216 | 217 | 218 | 219 | /** 220 | * Parse the input, builds a set of orgnode objects and live happy 221 | */ 222 | var parseBigString=function (data){ 223 | 224 | 225 | // We allocate here a bunch of regular expression to avoid 226 | // redelaring them every time 227 | // 228 | var scheduledRE=/SCHEDULED:\s+<([0-9]+)\-([0-9]+)\-([0-9]+)/; 229 | var deadlineRE=/DEADLINE:\s*<(\d+)\-(\d+)\-(\d+)/; 230 | var closedRE=/CLOSED:\s*<(\d+)\-(\d+)\-(\d+)\s*\w+/; 231 | 232 | var drawerFinderRE=/\s*:([a-zA-Z_0-9]+):\s*$/; 233 | var property_startRE=/\s*:PROPERTIES:/; 234 | var drawerENDRE=/\s*:END:/; 235 | var singlePropertyMatcherRE=/^\s*:(.*?):\s*(.*?)\s*$/; 236 | // CLock variants we must at least detect: 237 | // CLOCK: [2011-10-04 Tue 16:08]--[2011-10-04 Tue 16:09] => 0:01 238 | // CLOCK: [2011-10-04 Tue 16:41] 239 | var clockLineDetectionRE=/^\s*CLOCK:\s*\[[-0-9]+\s+.*\d:\d\d\]/; 240 | var todos = {} ; // populated from ; #+SEQ_TODO line 241 | todos['TODO'] = '' ; // default values 242 | todos['DONE'] = '' ; // default values 243 | 244 | 245 | // Split in lines and go 246 | var linesWithComments=asLines(data); 247 | var fileLines=[]; 248 | 249 | var unknownDirectives=[]; 250 | 251 | // Pre ripping #+ lines... 252 | var SEQ_TODO_RE=/^#[+]SEQ_TODO:\s*(.*)/; 253 | // #+DRAWERS: DRAWER_ONE DRAWER_TWO Drawer1 Drawer2 254 | var drawersDeclaration=/#[+]DRAWERS:(.*)$/; 255 | var ripSpacedElements=/([A-Z]+(\(\w\))?)/g; 256 | 257 | 258 | // Code to detect comment and directives. 259 | // #+directive is a directive not a comment 260 | var commentLines_RE=/^#[^+](.*)/i; 261 | var directive_RE=/^#[+]([a-z_]).*/i; 262 | // var directive_RE = function(d) {return new RegExp("^#\\+"+d+":\\s*(.*)", "i")}; 263 | 264 | /** This pre-parsing code 265 | * strip away org-mode comments. 266 | * Also, it pre-collect SEQ_TODO evil directive (!) to be able to recognize them later. 267 | * Unknown directive are retained (because you could need to parse them later) 268 | */ 269 | for(var index=0; index 0) console.dir(unknownDirectives); 310 | 311 | var level = 0 ; 312 | var heading = ""; 313 | var bodytext = ""; 314 | var tag1 = null ; // The first tag enclosed in :: 315 | var alltags = [] ; // list of all tags in headline 316 | var sched_date = ''; 317 | var deadline_date = ''; 318 | var closed_date = ''; 319 | var nodelist = []; 320 | var propdict = {}; 321 | 322 | 323 | var drawerArray={}; 324 | 325 | var insideSrcBlock=false; 326 | var begin_src_RE = /^#\+begin_src\s+(.*)/i; 327 | var end_src_RE = /^#\+end_src\s*/i; 328 | /** 329 | * While parsing drawers, the cycle will push forward the 330 | * index variable. 331 | * Remember to do unit test on case limits, like unexpected end of file... 332 | */ 333 | for(var index=0; index =1) { 428 | tag1=alltags[0]; 429 | } 430 | 431 | debug("TAGS:"+JSON.stringify(alltags)); 432 | } 433 | 434 | }else{ 435 | //we are processing a non-heading line 436 | //debug("No-heading line..."); 437 | var startProp=line.match(property_startRE); 438 | var endDrawer=line.match(drawerENDRE); 439 | var isAScheduledOrDeadlineOrClosedLine=line.match(scheduledRE) || line.match(deadlineRE) || line.match(closedRE); 440 | var isAClockLine=line.match(clockLineDetectionRE); 441 | var isSpecial=isAScheduledOrDeadlineOrClosedLine || isAClockLine; 442 | var isDrawerStart=line.match(drawerFinderRE); 443 | 444 | var isSimpleText= 445 | line[0]!='#' && (!startProp) && (!endDrawer) && 446 | !isDrawerStart && 447 | !isSpecial; 448 | 449 | 450 | if(isSimpleText){ 451 | // Simple line 452 | bodytext += line+"\n"; 453 | // FAST EXIT 454 | continue; 455 | } 456 | 457 | 458 | 459 | if(isSpecial){ 460 | //console.log("...?"+line); 461 | // Example SCHEDULED: <2011-12-31 Sat> 462 | var scheduledStuff=line.match(scheduledRE); 463 | if(scheduledStuff){ 464 | //debug("Matched:"+line); 465 | // new Date(year, month, day, hours, minutes, seconds, ms) 466 | sched_date= new Date(scheduledStuff[1], scheduledStuff[2] - 1,scheduledStuff[3], 467 | 0,0,0,0); 468 | } 469 | var deadlineStuff=line.match(deadlineRE); 470 | if(deadlineStuff){ 471 | deadline_date= new Date(deadlineStuff[1], deadlineStuff[2] - 1,deadlineStuff[3], 472 | 0,0,0,0); 473 | } 474 | var closedStuff=line.match(closedRE); 475 | if(closedStuff){ 476 | closed_date= new Date(closedStuff[1], closedStuff[2] - 1,closedStuff[3], 477 | 0,0,0,0); 478 | } 479 | if(isAClockLine){ 480 | // For the meantime we do nothing... 481 | // in the future we should be able to manage it 482 | } 483 | }else{ 484 | // Drawer/Property parser STARTS HERE 485 | if(startProp || isDrawerStart){ 486 | if(startProp){ 487 | // Property Parser 488 | while(!endDrawer){ 489 | // Update line and flags 490 | index++; 491 | line=fileLines[index]; 492 | endDrawer=line.match(drawerENDRE); 493 | var prop_srch = line.match(singlePropertyMatcherRE); 494 | if(prop_srch){ 495 | if(!endDrawer) { 496 | propdict[prop_srch[1]] = prop_srch[2]; 497 | } 498 | }else{ 499 | throw ParseError("No property inside a property drawer:"+line); 500 | } 501 | } 502 | }else{ 503 | if(isDrawerStart /*&& (!endDrawer)*/){ 504 | //Drawer Parser 505 | var drawerName=isDrawerStart[1]; 506 | var drawerContent=""; 507 | // Drawer parsing cycle 508 | while(!endDrawer && (index < fileLines.length-1)){ 509 | index++; 510 | line=fileLines[index]; 511 | endDrawer=line.match(drawerENDRE); 512 | if(!endDrawer){ 513 | // Drawer could be indented... rip it... 514 | drawerContent+=line.trim()+"\n"; 515 | } 516 | } 517 | if(!endDrawer && index==(fileLines.length-1)){ 518 | // We are at the end without nothing... Please Fire... 519 | debug('Drawer Parse Error on line:'+line); 520 | throw ParseError(' DRAWER :'+drawerName+': Found but :END: missed.'); 521 | } 522 | drawerArray[drawerName]=drawerContent; 523 | debug( 524 | 'Processed Drawer...'+heading+ ' ' +drawerName+ " OK"); 525 | } 526 | } 527 | // Ends here, index is pointing to endDrawer and the for will 528 | // increase it right 529 | continue; 530 | } 531 | // Drawer / Property parser ENDS here 532 | 533 | 534 | // Special Stuff 535 | // [whacked modification] 536 | // hacky version, stick source block processor onto end 537 | // this should be tagged in the regex blocks above for 538 | // faster output, but currently, less memory consumption 539 | /* 540 | if(line.indexOf("_src") > -1){ 541 | bodytext += line+"\n"; 542 | continue; 543 | }*/ 544 | // [/whacked modification] 545 | 546 | } 547 | 548 | 549 | } 550 | //debug(index+" " +line+ " OK"); 551 | 552 | }//for 553 | // write out last node 554 | var thisNode = new Orgnode(level, heading, bodytext, tag1, alltags,drawerArray); 555 | thisNode.properties=propdict; 556 | if(sched_date) 557 | thisNode.scheduled=sched_date; 558 | if (deadline_date) 559 | thisNode.deadline=deadline_date; 560 | // GG: Review this merge 561 | if (closed_date) 562 | thisNode.closed=closed_date; 563 | 564 | debug("Collecting Last Node:"+thisNode.headline); 565 | nodelist.push( thisNode ); 566 | 567 | 568 | 569 | // Using the list of TODO keywords found in the file 570 | // process the headings searching for TODO keywords 571 | // Heading will be modified to track down the TODO keywords right now 572 | _.each(nodelist,function(n){ 573 | //debug(JSON.stringify(n)); 574 | var h=n.headline; 575 | var todoSrch = h.match(/([A-Z]+)\s(.*?)$/); 576 | if(todoSrch){ 577 | var key=todoSrch[1]; 578 | if(key in todos){ 579 | n.headline=todoSrch[2]; 580 | n.todo=key; 581 | } 582 | } 583 | var prioritySearch= n.headline.match(/^\[\#(A|B|C)\] (.*?)$/); 584 | if (prioritySearch) { 585 | n.priority=prioritySearch[1]; 586 | n.headline=prioritySearch[2]; 587 | } 588 | if(h!==n.headline){ 589 | debug("Headline... "+h+" -> "+n.headline); 590 | } 591 | 592 | }); 593 | return nodelist; 594 | 595 | }; 596 | 597 | exports.parseBigString=parseBigString; 598 | 599 | 600 | var makelistFromStringWithPerformance=function(data,processFunction, passPerformanceAlso){ 601 | var startTime=Date.now(); // Millisecond acc 602 | var orgNodesList; 603 | try { 604 | orgNodesList=parseBigString(data); 605 | } catch (x) { 606 | debug("PARSE FAILED:"+x); 607 | throw x; 608 | } 609 | 610 | try { 611 | if(passPerformanceAlso){ 612 | // In milliseconds... 613 | var timeTaken=Date.now()-startTime; 614 | var secondsTaken=timeTaken/1000; 615 | var result={ 616 | totalTime:timeTaken, 617 | msPerNode:(timeTaken/orgNodesList.length), 618 | totalNodes:orgNodesList.length, 619 | nodesPerSeconds: 1000*(orgNodesList.length/timeTaken) 620 | }; 621 | processFunction(orgNodesList,result); 622 | }else{ 623 | processFunction(orgNodesList); 624 | } 625 | 626 | } catch (x) { 627 | console.log("makelist(): Error during Call of 'processFunction'"+JSON.stringify(x)); 628 | throw x; 629 | } 630 | 631 | }; 632 | 633 | var makelist=function(fileName, processFunction, passPerformanceAlso, sync){ 634 | if (sync) { 635 | return makelistNodeSync(fileName, passPerformanceAlso); 636 | } else { 637 | // This function translates the callback behavior expected from 638 | // makelistNode into the callback behavior expected from this function 639 | var translateFunc = function (err, list) { 640 | if (err) { 641 | throw err; 642 | } 643 | 644 | processFunction(list); 645 | }; 646 | 647 | makelistNode(fileName, translateFunc, passPerformanceAlso); 648 | } 649 | }; 650 | 651 | var makelistSync = function(fileName, passPerformanceAlso) { 652 | return makelist(fileName, undefined, passPerformanceAlso, true); 653 | } 654 | 655 | var makelistWithPerformance = function (fileName,processFunction){ 656 | makelist(fileName,processFunction,true); 657 | }; 658 | 659 | var makelistNodeCallback = function(err, data, processFunction, passPerformanceAlso) { 660 | if (err) { 661 | processFunction(err); 662 | return; 663 | } 664 | 665 | var successFunction = function (list) { 666 | processFunction(null, list); 667 | }; 668 | 669 | try { 670 | makelistFromStringWithPerformance(data,successFunction,passPerformanceAlso); 671 | } catch (err) { 672 | processFunction(err); 673 | } 674 | } 675 | 676 | var makelistNode = function(fileName, processFunction, passPerformanceAlso){ 677 | fs.readFile(fileName, 'utf-8',function (err,data){ 678 | makelistNodeCallback(err, data, processFunction, passPerformanceAlso); 679 | }); 680 | }; 681 | 682 | var makelistNodeSync = function(fileName, passPerformanceAlso) { 683 | try { 684 | var data = fs.readFileSync(fileName, 'utf-8'); 685 | } catch (err) { 686 | return err; 687 | } 688 | var result; 689 | var processFunction = function(err, list) { 690 | result = list; 691 | }; 692 | makelistNodeCallback(null, data, processFunction, passPerformanceAlso); 693 | return result; 694 | } 695 | 696 | 697 | // A new object for overall information, subtree-querying 698 | var OrgQuery=(function () 699 | { 700 | /** 701 | * An OrgQuery is used to group and summarize a set of information from a list of nodes 702 | * It would also provide a subtree slicing method set. 703 | * TODO: Compress and optimize in only one cycle! 704 | * TODO2: Must return an ARRAY LIKE OBJECT! 705 | * 706 | */ 707 | function OrgQuery(nodes,masterHeadlineOrNull){ 708 | // Safety Argument checkings... 709 | 710 | if(_.isNull(nodes) || _.isUndefined(nodes)){ 711 | throw IllegalArgumentException("First arguments {nodes} cannot be Null or Undefined" ); 712 | } 713 | 714 | _.each(nodes,function (n,index){ 715 | if( ! (n instanceof Orgnode)){ 716 | throw IllegalArgumentException("Argument "+(n+1)+" is not an Orgnode. Input:"+JSON.stringify(arguments)); 717 | } 718 | }); 719 | // ----- 720 | this.fileTags={}; 721 | this.allNodes=nodes; 722 | if(masterHeadlineOrNull){ 723 | this.description=masterHeadlineOrNull; 724 | }else{ 725 | //this.description=null; 726 | } 727 | var mySubtreeMap={}; 728 | var myFileTags=this.fileTags; 729 | _.each(nodes,function(n){ 730 | _.each(n.tags,function(v,k){ 731 | myFileTags[k]=true; 732 | }); 733 | }); 734 | // Build the subtree mapping... 735 | // For every node, build a subtree list... 736 | var indexer=0; 737 | _.each(nodes,function(n){ 738 | indexer++; 739 | var myMap=[]; 740 | var nodeLevel=n.level; 741 | // Slice below the node... 742 | var toTest=nodes.slice(indexer); 743 | var checkIndex=0; 744 | var subNodeCandidate=toTest[checkIndex]; 745 | while(checkIndexnodeLevel) 748 | { 749 | myMap.push(subNodeCandidate); 750 | checkIndex++; 751 | subNodeCandidate=toTest[checkIndex]; 752 | } 753 | //console.log(n.headline); 754 | //console.dir(myMap); 755 | mySubtreeMap[n.key]=new OrgQuery(myMap,n.headline); 756 | }); 757 | this.subTreeMap=mySubtreeMap; 758 | this.length=this.allNodes.length; 759 | 760 | 761 | // 0.0.7 merge enhance avoided for the meantime 762 | if(this.allNodes.length==1){ 763 | this.monoNode=true; 764 | }else{ 765 | this.monoNode=false; 766 | } 767 | }; 768 | /** Return always a OrgQuery object */ 769 | OrgQuery.prototype.selectSubtree = function (nodeObj){ 770 | if(nodeObj!==null && nodeObj!==undefined){ 771 | // Accepted inputs...are only Orgnode and OrgQuery 772 | // OrgQuery force selection of first object 773 | var isOrgQueryCollection=nodeObj instanceof OrgQuery; 774 | if(!(nodeObj instanceof Orgnode) && 775 | !(isOrgQueryCollection) 776 | || 777 | (isOrgQueryCollection && nodeObj.length>1) 778 | ) 779 | { 780 | throw IllegalArgumentException("selectSubtree argument is illegal:"+ 781 | JSON.stringify(arguments)); 782 | } 783 | 784 | if(nodeObj instanceof Orgnode){ 785 | return this.subTreeMap[nodeObj.key]; 786 | }else{ 787 | // Added on version 0.0.7 788 | var queryObj=nodeObj; 789 | if(queryObj.length>1){ 790 | throw IllegalArgumentException("Passed more then one node:"+JSON.stringify(arguments)); 791 | }else{ 792 | if(queryObj.length==1){ 793 | return this.subTreeMap[queryObj.first().key]; 794 | }else{ 795 | return this.allNodes; 796 | } 797 | 798 | } 799 | } 800 | 801 | }else{ 802 | //?return this.selectSubtree(this.first()); 803 | return this.allNodes; 804 | } 805 | 806 | }; 807 | OrgQuery.prototype.selectTag= function(tagName){ 808 | var nodes=[] ; 809 | _.each(this.allNodes, function(n){ 810 | if( tagName in n.tags){ 811 | nodes.push(n); 812 | } 813 | }); 814 | return new OrgQuery(nodes); 815 | }; 816 | 817 | // Private manipulation function 818 | 819 | // Ordering and rejecting, returning ALWAYS a OrgQuery object 820 | OrgQuery.prototype.sortBy=function (fSort){ 821 | var newNodeList=_.sortBy(this.allNodes,fSort); 822 | return new OrgQuery(newNodeList); 823 | }; 824 | 825 | OrgQuery.prototype.reject=function (rejector){ 826 | var newNodeList=_.reject(this.allNodes,rejector); 827 | return new OrgQuery(newNodeList); 828 | }; 829 | 830 | OrgQuery.prototype.rejectTag=function (tagName){ 831 | return this.reject(function(n){ 832 | return n.tags[tagName]===true; 833 | }); 834 | }; 835 | 836 | OrgQuery.prototype.rejectArchived=function(tagName){ 837 | return this.rejectTag('ARCHIVE'); 838 | }; 839 | 840 | 841 | OrgQuery.prototype.first= function(tagName){ 842 | return this.allNodes[0]; 843 | }; 844 | 845 | OrgQuery.prototype.toOrgString= function(){ 846 | var s=''; 847 | _.each(this.allNodes, function(n){ 848 | s+=n.toOrgString(); 849 | }); 850 | return s; 851 | }; 852 | OrgQuery.prototype.toArray=function(){ 853 | return _.toArray(this.allNodes); 854 | }; 855 | OrgQuery.prototype.each=function(f_toCall){ 856 | _.each(this.toArray(),f_toCall); 857 | return this; 858 | }; 859 | 860 | OrgQuery.prototype.random=function(){ 861 | var randomIndex=Math.round(Math.random()*(this.length-1)); 862 | //console.log("HI:"+randomIndex+ " "+this.length); 863 | return this.allNodes[randomIndex]; 864 | }; 865 | 866 | /** GG derivated by 867 | * https://github.com/whacked/org-mode-parser 868 | * The render try to conform to org mode rendering (somewhat). 869 | * It is html-5 enhanced 870 | * .title { text-align: center; } 871 | .todo { font-family: monospace; color: red; } 872 | .done { color: green; } 873 | .tag { background-color: #eee; font-family: monospace; 874 | padding: 2px; font-size: 80%; font-weight: normal; } 875 | 876 | todo and tag logic css class are used on heading marked with todo 877 | */ 878 | OrgQuery.prototype.toHtml = function(options) { 879 | 880 | var self = this; 881 | self.MAX_HEADLINE_LEVEL = 6; 882 | self.OL = 'ol'; 883 | self.UL = 'ul'; 884 | self.DL = 'dl'; 885 | self.Table = "table"; 886 | 887 | // See http://www.markhansen.co.nz/javascript-optional-parameters/ 888 | var options = options || {}; 889 | var fullHtml = options.fullHtml || false; // include header/footer? 890 | var pugTemplatePath=options.pugTemplatePath || ""; 891 | 892 | function render_headline(node) { 893 | // org-mode render like

894 | // we are more compact 895 | var r="", level; 896 | if(node.level <= self.MAX_HEADLINE_LEVEL) { 897 | level=node.level; 898 | }else{ 899 | level=self.MAX_HEADLINE_LEVEL; 900 | } 901 | r= "0){ 908 | r+=" tag"; 909 | _.each(Object.keys(node.tags), function t(t){ 910 | r+=" tag-"+t; 911 | }); 912 | 913 | } 914 | r+="\" >"+node.headline; 915 | 916 | r+=""; 917 | return r; 918 | } 919 | function paragraphparser(paragraph) { 920 | var artn = []; 921 | var lastgroup = null; 922 | var lsline = paragraph.split("\n"); 923 | for(var i = 0, n = lsline.length; i < n; ++i) { 924 | var parsed = lineparser(lsline[i]); 925 | if(parsed[0] && lastgroup === null) { 926 | artn.push("<"+parsed[0]+">"); 927 | } 928 | artn.push(parsed[1]); 929 | if(lastgroup && (parsed[0] !== lastgroup || i == n-1)) { 930 | artn.push(""); 931 | lastgroup = null; 932 | } else { 933 | lastgroup = parsed[0]; 934 | } 935 | } 936 | return artn.join("\n"); 937 | } 938 | function lineparser(line) { 939 | // horizontal rule 940 | var line1 = line.replace(/-{5,}\s*$/, '
'); 941 | if(line1 != line) { 942 | return [null, line1]; 943 | } 944 | 945 | // simple markup 946 | if (self.processCode) { 947 | // end source 948 | line1 = line.replace(/^#\+end_src\s*/i, ''); 949 | if (line1 !== line) { 950 | self.processCode = false; 951 | } 952 | return [null, line1]; 953 | } else { 954 | // begin source 955 | // include lang spec 956 | // GG guozhen added data-language 957 | line1 = line.replace(/^#\+begin_src\s+([^ ]+)(.*)/i, ''); 958 | if (line1 !== line) { 959 | self.processCode = true; 960 | return [null, line1]; 961 | } 962 | } 963 | var content = ""; 964 | if (self.processTable) { 965 | line1 = line.split("|"); 966 | if (line1[0] === line) { 967 | // process end 968 | self.processTable = false; 969 | } else { 970 | if (!line.replace(/^\|-+\+-+\|/, "")) { 971 | return [self.Table, null]; 972 | } 973 | 974 | content = ""; 975 | line1.forEach(function(item) { 976 | if (item) { 977 | content += '' + item + ''; 978 | } 979 | }); 980 | content = '' + content + ''; 981 | 982 | return [self.Table, content]; 983 | } 984 | } else { 985 | line1 = line.split("|"); 986 | if (line1[0] !== line) { 987 | content = ""; 988 | line1.forEach(function(item) { 989 | if (item) { 990 | content += '' + item + ''; 991 | } 992 | }); 993 | content = '' + content + ''; 994 | 995 | self.processTable = true; 996 | line1 = '' + content + ''; 997 | 998 | return [self.Table, line1]; 999 | } 1000 | } 1001 | 1002 | 1003 | 1004 | 1005 | line = line 1006 | // code 1007 | .replace(/=([^=]+?)=/g, '$1') 1008 | // verbatim 1009 | .replace(/~([^~]+?)~/g, '$1') 1010 | // italic 1011 | .replace(/\/(.+?)\//g, '$1') 1012 | // bold 1013 | .replace(/\*([^\*]+?)\*/g, '$1') 1014 | // underline 1015 | .replace(/_([^_]+?)_/g, '$1') 1016 | // image file, double bracket 1017 | .replace(/\[\[file:\s*([^ ]+)\.(?:PNG|JPG|BMP|GIF|TIFF|SVG)\]\]/i, '$1') 1018 | // image file, no bracket 1019 | .replace(/(?:^|[^[])file:([^ ]+)\.(?:PNG|JPG|BMP|GIF|TIFF|SVG)(?:[^\]]|$)/i, '$1') 1020 | // hyperlink with description 1021 | .replace(/\[\[(?:file:)?(.*?)\]\[(.*?)\]\]/i, '$1') 1022 | // hyperlink without description 1023 | .replace(/\[\[(?:file:)?(.*?)\]\]/i, '$1'); 1024 | 1025 | 1026 | // lists 1027 | //// definition list 1028 | //// ordered 1029 | line1 = line.replace(/\d+[\.)] (.+)/, '
  • $1
  • '); 1030 | if(line1 != line) return [self.OL, line1]; 1031 | //// unordered 1032 | line1 = line.replace(/^[-+] (.+)/, '
  • $1
  • '); 1033 | if(line1 != line) return [self.UL, line1]; 1034 | line1 = line.replace(/- +([^ ]+) :: (.+)/, '
    $1
    $2
    '); 1035 | if(line1 != line) return [self.DL, line1]; 1036 | 1037 | return [null, line1]; 1038 | } 1039 | 1040 | //console.log(this) 1041 | 1042 | var html ; 1043 | // see https://pugjs.org/api/getting-started.html 1044 | if(options.fullHtml){ 1045 | var pug = require('pug'); 1046 | var template=pug.compileFile(pugTemplatePath, { 1047 | pretty:true, 1048 | compileDebug:true, 1049 | globals: [] 1050 | //add filename e cache? 1051 | }); 1052 | var locals={ 1053 | 1054 | }; 1055 | /** Pug exposed fields 1056 | * Push data to pug for further rendering: 1057 | * 1058 | */ 1059 | html = template({ 1060 | nodes: this.allNodes, 1061 | Generator: 'NodeJS OrgModeParser' 1062 | }); 1063 | }else { 1064 | html=""; 1065 | for(var i = 0; i < this.allNodes.length; ++i) { 1066 | node = this.allNodes[i]; 1067 | if(node.headline) { 1068 | html += render_headline(node); 1069 | } 1070 | //console.dir(node); 1071 | html += "

    " + node.body.split("\n\n").map(paragraphparser).join("

    ") + "

    "; 1072 | 1073 | } 1074 | 1075 | } 1076 | return html; 1077 | }; /*toHTML */ 1078 | 1079 | 1080 | return OrgQuery; 1081 | })(); 1082 | 1083 | 1084 | 1085 | exports.Orgnode=Orgnode; 1086 | exports.makelistWithPerformance=makelistWithPerformance; 1087 | exports.makelistFromStringWithPerformance=makelistFromStringWithPerformance; 1088 | exports.makelist=makelist; 1089 | exports.makelistSync=makelistSync; 1090 | exports.makelistNode=makelistNode; 1091 | 1092 | exports.OrgQuery=OrgQuery; 1093 | // Emacs mumbo jumbo extensions. Company-mode give a bit of autocomplete spriz! 1094 | // Local variables: 1095 | // mode: company 1096 | // End: 1097 | })(); 1098 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "org-mode-parser", 3 | "version": "0.1.4", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/babel-types": { 8 | "version": "7.0.4", 9 | "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.4.tgz", 10 | "integrity": "sha512-WiZhq3SVJHFRgRYLXvpf65XnV6ipVHhnNaNvE8yCimejrGglkg38kEj0JcizqwSHxmPSjcTlig/6JouxLGEhGw==" 11 | }, 12 | "@types/babylon": { 13 | "version": "6.16.4", 14 | "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.4.tgz", 15 | "integrity": "sha512-8dZMcGPno3g7pJ/d0AyJERo+lXh9i1JhDuCUs+4lNIN9eUe5Yh6UCLrpgSEi05Ve2JMLauL2aozdvKwNL0px1Q==", 16 | "requires": { 17 | "@types/babel-types": "*" 18 | } 19 | }, 20 | "acorn": { 21 | "version": "3.3.0", 22 | "resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", 23 | "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" 24 | }, 25 | "acorn-globals": { 26 | "version": "3.1.0", 27 | "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", 28 | "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", 29 | "requires": { 30 | "acorn": "^4.0.4" 31 | }, 32 | "dependencies": { 33 | "acorn": { 34 | "version": "4.0.13", 35 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", 36 | "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" 37 | } 38 | } 39 | }, 40 | "align-text": { 41 | "version": "0.1.4", 42 | "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", 43 | "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", 44 | "requires": { 45 | "kind-of": "^3.0.2", 46 | "longest": "^1.0.1", 47 | "repeat-string": "^1.5.2" 48 | } 49 | }, 50 | "asap": { 51 | "version": "2.0.6", 52 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", 53 | "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" 54 | }, 55 | "async": { 56 | "version": "1.0.0", 57 | "resolved": "http://registry.npmjs.org/async/-/async-1.0.0.tgz", 58 | "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" 59 | }, 60 | "babel-runtime": { 61 | "version": "6.26.0", 62 | "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", 63 | "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", 64 | "requires": { 65 | "core-js": "^2.4.0", 66 | "regenerator-runtime": "^0.11.0" 67 | } 68 | }, 69 | "babel-types": { 70 | "version": "6.26.0", 71 | "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", 72 | "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", 73 | "requires": { 74 | "babel-runtime": "^6.26.0", 75 | "esutils": "^2.0.2", 76 | "lodash": "^4.17.4", 77 | "to-fast-properties": "^1.0.3" 78 | } 79 | }, 80 | "babylon": { 81 | "version": "6.18.0", 82 | "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", 83 | "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" 84 | }, 85 | "balanced-match": { 86 | "version": "1.0.0", 87 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 88 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 89 | "dev": true 90 | }, 91 | "brace-expansion": { 92 | "version": "1.1.11", 93 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 94 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 95 | "dev": true, 96 | "requires": { 97 | "balanced-match": "^1.0.0", 98 | "concat-map": "0.0.1" 99 | } 100 | }, 101 | "camelcase": { 102 | "version": "1.2.1", 103 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", 104 | "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" 105 | }, 106 | "center-align": { 107 | "version": "0.1.3", 108 | "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", 109 | "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", 110 | "requires": { 111 | "align-text": "^0.1.3", 112 | "lazy-cache": "^1.0.3" 113 | } 114 | }, 115 | "character-parser": { 116 | "version": "2.2.0", 117 | "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", 118 | "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", 119 | "requires": { 120 | "is-regex": "^1.0.3" 121 | } 122 | }, 123 | "clean-css": { 124 | "version": "4.2.1", 125 | "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", 126 | "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", 127 | "requires": { 128 | "source-map": "~0.6.0" 129 | } 130 | }, 131 | "cliui": { 132 | "version": "2.1.0", 133 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", 134 | "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", 135 | "requires": { 136 | "center-align": "^0.1.1", 137 | "right-align": "^0.1.1", 138 | "wordwrap": "0.0.2" 139 | } 140 | }, 141 | "colors": { 142 | "version": "1.0.3", 143 | "resolved": "http://registry.npmjs.org/colors/-/colors-1.0.3.tgz", 144 | "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" 145 | }, 146 | "concat-map": { 147 | "version": "0.0.1", 148 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 149 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 150 | "dev": true 151 | }, 152 | "constantinople": { 153 | "version": "3.1.2", 154 | "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz", 155 | "integrity": "sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw==", 156 | "requires": { 157 | "@types/babel-types": "^7.0.0", 158 | "@types/babylon": "^6.16.2", 159 | "babel-types": "^6.26.0", 160 | "babylon": "^6.18.0" 161 | } 162 | }, 163 | "core-js": { 164 | "version": "2.6.0", 165 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.0.tgz", 166 | "integrity": "sha512-kLRC6ncVpuEW/1kwrOXYX6KQASCVtrh1gQr/UiaVgFlf9WE5Vp+lNe5+h3LuMr5PAucWnnEXwH0nQHRH/gpGtw==" 167 | }, 168 | "cycle": { 169 | "version": "1.0.3", 170 | "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", 171 | "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" 172 | }, 173 | "decamelize": { 174 | "version": "1.2.0", 175 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 176 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" 177 | }, 178 | "diff": { 179 | "version": "1.0.8", 180 | "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.8.tgz", 181 | "integrity": "sha1-NDJ2MI7Jkbe8giZ+1VvBQR+XFmY=", 182 | "dev": true 183 | }, 184 | "doctypes": { 185 | "version": "1.1.0", 186 | "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", 187 | "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" 188 | }, 189 | "esutils": { 190 | "version": "2.0.2", 191 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 192 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" 193 | }, 194 | "eyes": { 195 | "version": "0.1.8", 196 | "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", 197 | "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" 198 | }, 199 | "fs.realpath": { 200 | "version": "1.0.0", 201 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 202 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 203 | "dev": true 204 | }, 205 | "function-bind": { 206 | "version": "1.1.1", 207 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 208 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 209 | }, 210 | "glob": { 211 | "version": "7.1.3", 212 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 213 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 214 | "dev": true, 215 | "requires": { 216 | "fs.realpath": "^1.0.0", 217 | "inflight": "^1.0.4", 218 | "inherits": "2", 219 | "minimatch": "^3.0.4", 220 | "once": "^1.3.0", 221 | "path-is-absolute": "^1.0.0" 222 | } 223 | }, 224 | "has": { 225 | "version": "1.0.3", 226 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 227 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 228 | "requires": { 229 | "function-bind": "^1.1.1" 230 | } 231 | }, 232 | "inflight": { 233 | "version": "1.0.6", 234 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 235 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 236 | "dev": true, 237 | "requires": { 238 | "once": "^1.3.0", 239 | "wrappy": "1" 240 | } 241 | }, 242 | "inherits": { 243 | "version": "2.0.3", 244 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 245 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 246 | "dev": true 247 | }, 248 | "is-buffer": { 249 | "version": "1.1.6", 250 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 251 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 252 | }, 253 | "is-expression": { 254 | "version": "3.0.0", 255 | "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", 256 | "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=", 257 | "requires": { 258 | "acorn": "~4.0.2", 259 | "object-assign": "^4.0.1" 260 | }, 261 | "dependencies": { 262 | "acorn": { 263 | "version": "4.0.13", 264 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", 265 | "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" 266 | } 267 | } 268 | }, 269 | "is-promise": { 270 | "version": "2.1.0", 271 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", 272 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" 273 | }, 274 | "is-regex": { 275 | "version": "1.0.4", 276 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 277 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 278 | "requires": { 279 | "has": "^1.0.1" 280 | } 281 | }, 282 | "isstream": { 283 | "version": "0.1.2", 284 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 285 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 286 | }, 287 | "js-stringify": { 288 | "version": "1.0.2", 289 | "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", 290 | "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" 291 | }, 292 | "jstransformer": { 293 | "version": "1.0.0", 294 | "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", 295 | "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", 296 | "requires": { 297 | "is-promise": "^2.0.0", 298 | "promise": "^7.0.1" 299 | } 300 | }, 301 | "kind-of": { 302 | "version": "3.2.2", 303 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 304 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 305 | "requires": { 306 | "is-buffer": "^1.1.5" 307 | } 308 | }, 309 | "lazy-cache": { 310 | "version": "1.0.4", 311 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", 312 | "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" 313 | }, 314 | "lodash": { 315 | "version": "4.17.11", 316 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", 317 | "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" 318 | }, 319 | "longest": { 320 | "version": "1.0.1", 321 | "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", 322 | "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" 323 | }, 324 | "minimatch": { 325 | "version": "3.0.4", 326 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 327 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 328 | "dev": true, 329 | "requires": { 330 | "brace-expansion": "^1.1.7" 331 | } 332 | }, 333 | "object-assign": { 334 | "version": "4.1.1", 335 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 336 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 337 | }, 338 | "once": { 339 | "version": "1.4.0", 340 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 341 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 342 | "dev": true, 343 | "requires": { 344 | "wrappy": "1" 345 | } 346 | }, 347 | "path-is-absolute": { 348 | "version": "1.0.1", 349 | "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 350 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 351 | "dev": true 352 | }, 353 | "path-parse": { 354 | "version": "1.0.6", 355 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 356 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" 357 | }, 358 | "promise": { 359 | "version": "7.3.1", 360 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", 361 | "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", 362 | "requires": { 363 | "asap": "~2.0.3" 364 | } 365 | }, 366 | "pug": { 367 | "version": "2.0.3", 368 | "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.3.tgz", 369 | "integrity": "sha1-ccuoJTfJWl6rftBGluQiH1Oqh44=", 370 | "requires": { 371 | "pug-code-gen": "^2.0.1", 372 | "pug-filters": "^3.1.0", 373 | "pug-lexer": "^4.0.0", 374 | "pug-linker": "^3.0.5", 375 | "pug-load": "^2.0.11", 376 | "pug-parser": "^5.0.0", 377 | "pug-runtime": "^2.0.4", 378 | "pug-strip-comments": "^1.0.3" 379 | } 380 | }, 381 | "pug-attrs": { 382 | "version": "2.0.3", 383 | "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.3.tgz", 384 | "integrity": "sha1-owlflw5kFR972tlX7vVftdeQXRU=", 385 | "requires": { 386 | "constantinople": "^3.0.1", 387 | "js-stringify": "^1.0.1", 388 | "pug-runtime": "^2.0.4" 389 | } 390 | }, 391 | "pug-code-gen": { 392 | "version": "2.0.1", 393 | "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-2.0.1.tgz", 394 | "integrity": "sha1-CVHsgyJddNjPxHan+Zolm199BQw=", 395 | "requires": { 396 | "constantinople": "^3.0.1", 397 | "doctypes": "^1.1.0", 398 | "js-stringify": "^1.0.1", 399 | "pug-attrs": "^2.0.3", 400 | "pug-error": "^1.3.2", 401 | "pug-runtime": "^2.0.4", 402 | "void-elements": "^2.0.1", 403 | "with": "^5.0.0" 404 | } 405 | }, 406 | "pug-error": { 407 | "version": "1.3.2", 408 | "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.2.tgz", 409 | "integrity": "sha1-U659nSm7A89WRJOgJhCfVMR/XyY=" 410 | }, 411 | "pug-filters": { 412 | "version": "3.1.0", 413 | "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-3.1.0.tgz", 414 | "integrity": "sha1-JxZVVbwEwjbkqisDZiRt+gIbYm4=", 415 | "requires": { 416 | "clean-css": "^4.1.11", 417 | "constantinople": "^3.0.1", 418 | "jstransformer": "1.0.0", 419 | "pug-error": "^1.3.2", 420 | "pug-walk": "^1.1.7", 421 | "resolve": "^1.1.6", 422 | "uglify-js": "^2.6.1" 423 | } 424 | }, 425 | "pug-lexer": { 426 | "version": "4.0.0", 427 | "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-4.0.0.tgz", 428 | "integrity": "sha1-IQwYRX7y4XYCQnQMXmR715TOwng=", 429 | "requires": { 430 | "character-parser": "^2.1.1", 431 | "is-expression": "^3.0.0", 432 | "pug-error": "^1.3.2" 433 | } 434 | }, 435 | "pug-linker": { 436 | "version": "3.0.5", 437 | "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-3.0.5.tgz", 438 | "integrity": "sha1-npp65ABWgtAn3uuWsAD4juuDoC8=", 439 | "requires": { 440 | "pug-error": "^1.3.2", 441 | "pug-walk": "^1.1.7" 442 | } 443 | }, 444 | "pug-load": { 445 | "version": "2.0.11", 446 | "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.11.tgz", 447 | "integrity": "sha1-5kjlftET/iwfRdV4WOorrWvAFSc=", 448 | "requires": { 449 | "object-assign": "^4.1.0", 450 | "pug-walk": "^1.1.7" 451 | } 452 | }, 453 | "pug-parser": { 454 | "version": "5.0.0", 455 | "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-5.0.0.tgz", 456 | "integrity": "sha1-45Stmz/KkxI5QK/4hcBuRKt+aOQ=", 457 | "requires": { 458 | "pug-error": "^1.3.2", 459 | "token-stream": "0.0.1" 460 | } 461 | }, 462 | "pug-runtime": { 463 | "version": "2.0.4", 464 | "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.4.tgz", 465 | "integrity": "sha1-4XjhvaaKsujArPybztLFT9iM61g=" 466 | }, 467 | "pug-strip-comments": { 468 | "version": "1.0.3", 469 | "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.3.tgz", 470 | "integrity": "sha1-8VWVkiBu3G+FMQ2s9K+0igJa9Z8=", 471 | "requires": { 472 | "pug-error": "^1.3.2" 473 | } 474 | }, 475 | "pug-walk": { 476 | "version": "1.1.7", 477 | "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.7.tgz", 478 | "integrity": "sha1-wA1cUSi6xYBr7BXSt+fNq+QlMfM=" 479 | }, 480 | "regenerator-runtime": { 481 | "version": "0.11.1", 482 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", 483 | "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" 484 | }, 485 | "repeat-string": { 486 | "version": "1.6.1", 487 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", 488 | "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" 489 | }, 490 | "resolve": { 491 | "version": "1.9.0", 492 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", 493 | "integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==", 494 | "requires": { 495 | "path-parse": "^1.0.6" 496 | } 497 | }, 498 | "right-align": { 499 | "version": "0.1.3", 500 | "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", 501 | "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", 502 | "requires": { 503 | "align-text": "^0.1.1" 504 | } 505 | }, 506 | "source-map": { 507 | "version": "0.6.1", 508 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 509 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 510 | }, 511 | "stack-trace": { 512 | "version": "0.0.10", 513 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 514 | "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" 515 | }, 516 | "to-fast-properties": { 517 | "version": "1.0.3", 518 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", 519 | "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" 520 | }, 521 | "token-stream": { 522 | "version": "0.0.1", 523 | "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", 524 | "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=" 525 | }, 526 | "uglify-js": { 527 | "version": "2.8.29", 528 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", 529 | "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", 530 | "requires": { 531 | "source-map": "~0.5.1", 532 | "uglify-to-browserify": "~1.0.0", 533 | "yargs": "~3.10.0" 534 | }, 535 | "dependencies": { 536 | "source-map": { 537 | "version": "0.5.7", 538 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 539 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" 540 | } 541 | } 542 | }, 543 | "uglify-to-browserify": { 544 | "version": "1.0.2", 545 | "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", 546 | "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", 547 | "optional": true 548 | }, 549 | "underscore": { 550 | "version": "1.9.1", 551 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", 552 | "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" 553 | }, 554 | "void-elements": { 555 | "version": "2.0.1", 556 | "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", 557 | "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" 558 | }, 559 | "vows": { 560 | "version": "0.8.2", 561 | "resolved": "https://registry.npmjs.org/vows/-/vows-0.8.2.tgz", 562 | "integrity": "sha1-aR95qybM3oC6cm3en+yOc9a88us=", 563 | "dev": true, 564 | "requires": { 565 | "diff": "~1.0.8", 566 | "eyes": "~0.1.6", 567 | "glob": "^7.1.2" 568 | } 569 | }, 570 | "window-size": { 571 | "version": "0.1.0", 572 | "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", 573 | "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" 574 | }, 575 | "winston": { 576 | "version": "2.4.4", 577 | "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.4.tgz", 578 | "integrity": "sha512-NBo2Pepn4hK4V01UfcWcDlmiVTs7VTB1h7bgnB0rgP146bYhMxX0ypCz3lBOfNxCO4Zuek7yeT+y/zM1OfMw4Q==", 579 | "requires": { 580 | "async": "~1.0.0", 581 | "colors": "1.0.x", 582 | "cycle": "1.0.x", 583 | "eyes": "0.1.x", 584 | "isstream": "0.1.x", 585 | "stack-trace": "0.0.x" 586 | } 587 | }, 588 | "with": { 589 | "version": "5.1.1", 590 | "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz", 591 | "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=", 592 | "requires": { 593 | "acorn": "^3.1.0", 594 | "acorn-globals": "^3.0.0" 595 | } 596 | }, 597 | "wordwrap": { 598 | "version": "0.0.2", 599 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", 600 | "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" 601 | }, 602 | "wrappy": { 603 | "version": "1.0.2", 604 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 605 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 606 | "dev": true 607 | }, 608 | "yargs": { 609 | "version": "3.10.0", 610 | "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", 611 | "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", 612 | "requires": { 613 | "camelcase": "^1.0.2", 614 | "cliui": "^2.1.0", 615 | "decamelize": "^1.0.0", 616 | "window-size": "0.1.0" 617 | } 618 | } 619 | } 620 | } 621 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "org-mode-parser", 3 | "description": "A parser for the Emacs org-mode package. DRAWER and archive tag supported. Stronger API", 4 | "homepage": "http://gioorgi.com/org-mode-parser", 5 | "keywords": [ 6 | "parser", 7 | "util", 8 | "org-mode", 9 | "orgmode", 10 | "emacs" 11 | ], 12 | "license": "MIT", 13 | "author": "Giovanni Giorgi ", 14 | "contributors": [], 15 | "main": "./lib/org-mode-parser", 16 | "version": "0.1.4", 17 | "directories": { 18 | "test": "./test" 19 | }, 20 | "scripts": { 21 | "test": "vows --spec -v test/*.js" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git://github.com/daitangio/org-mode-parser.git" 26 | }, 27 | "engines": { 28 | "node": ">=6.9.2" 29 | }, 30 | "dependencies": { 31 | "pug": "^2.0.0-beta6", 32 | "underscore": "^1.8.3", 33 | "winston": "^2.3.0" 34 | }, 35 | "devDependencies": { 36 | "vows": "^0.8.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /runVows: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # nodemon $0 3 | # export NODE_PATH=$(dirname $(which node))/../lib/node_modules 4 | #vows --spec -v --cover-plain test/*.js 5 | vows test/*.js 6 | # GG The watch mode seems quite broken 7 | # while sleep 0 ; do 8 | # npm install 9 | # vows --spec -v --cover-plain test/*.js 10 | # sleep 6 11 | # done 12 | -------------------------------------------------------------------------------- /runVowsTest.cmd: -------------------------------------------------------------------------------- 1 | @echo Running test unit 2 | REM npm install -g vows 3 | vows --spec -v --cover-plain test/*.js 4 | -------------------------------------------------------------------------------- /test/bug3DrawerConfusion.org: -------------------------------------------------------------------------------- 1 | * Python 2 | ** time 3 | #+BEGIN_SRC python 4 | from time import strftime 5 | strftime("%Y-%m-%d %H:%M:%S") 6 | => '2014-06-06 13:50:52' 7 | #+END_SRC -------------------------------------------------------------------------------- /test/colon_mistake.org: -------------------------------------------------------------------------------- 1 | ****** Feature: create and register new wordpress-powered (source) jekyll blog :toblog: 2 | yaya, cool 3 | markdown here 4 | if the headline has a : in it, it will confuse the parser and it will think the headline is actually a tag. 5 | 6 | 7 | * Simple tag :simple: 8 | * Simple tag double :one:double: 9 | -------------------------------------------------------------------------------- /test/drawerAndArchiveTag.org: -------------------------------------------------------------------------------- 1 | * Node with a generic drawer 2 | :PROPERTIES: 3 | :expectedNodes: 6 4 | :archivedNodes:2 5 | :END: 6 | you can define also the drawer using the following directive.... 7 | #+DRAWERS: DRAWER_ONE DRAWER_TWO Drawer1 Drawer2 8 | :DRAWER_ONE: 9 | I am drawer one, with one line 10 | :END: 11 | * A second drawer 12 | :DRAWER_TWO: 13 | Different alignment baby 14 | Declared drawer cannot have different alignment, as far as we know 15 | :END: 16 | *** A very complex drawer case :indentedDrawerTest: 17 | :Drawer1: 18 | Nice to meet you, unindented I hope 19 | :END: 20 | :Drawer2: 21 | Another one 22 | :END: 23 | *** Another complex case :complexDrawerMix: 24 | In this header, drawer are mixed in a fancy way 25 | :Drawer1: 26 | Drawer1 content 27 | :END: 28 | Test below drawer1 29 | Text above drawer2 30 | :Drawer2: 31 | Drawer2 content 32 | :END: 33 | Text below drawer2 34 | * Archived node :ARCHIVE: 35 | This node is archived baby 36 | * Archived node with drawer :ARCHIVE: 37 | :DRAWER_ONE: 38 | I am drawer inside an archived node 39 | :END: 40 | -------------------------------------------------------------------------------- /test/fullHtml.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | p Example of expansion of header of #{nodes.length} nodes 4 | p Index 5 | ol 6 | each n in nodes 7 | li 8 | a(href='#'+n.headline) 9 | span= n.headline 10 | p Body 11 | ol 12 | each n in nodes 13 | li 14 | a(name='' + n.headline)= n.headline 15 | p Body for this node: 16 | p= n.body 17 | p Generated by 18 | span= Generator -------------------------------------------------------------------------------- /test/htmlTest.org: -------------------------------------------------------------------------------- 1 | #+TITLE: test.org 2 | #+AUTHOR: myauthor 3 | #+EMAIL: myemail@mylocation.com 4 | #+DATE: 2012-02-02 Thu 5 | #+DESCRIPTION: 6 | #+KEYWORDS: 7 | #+LANGUAGE: en 8 | #+OPTIONS: H:3 num:t toc:t \n:nil @:t ::t |:t ^:t -:t f:t *:t <:t 9 | #+OPTIONS: TeX:t LaTeX:t skip:nil d:nil todo:t pri:nil tags:not-in-toc 10 | #+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js 11 | #+EXPORT_SELECT_TAGS: export 12 | #+EXPORT_EXCLUDE_TAGS: noexport 13 | #+LINK_UP: 14 | #+LINK_HOME: 15 | #+XSLT: 16 | 17 | this text doesn't render 18 | 19 | above is the default options template (=C-c C-e t=) 20 | 21 | * TODO TODO Test 22 | Must be the first node because no tag is used 23 | 24 | # a comment 25 | 26 | * odd behavior {}[]() ::: /\ 27 | 28 | headline above doesn't render anything after the :::, might be because it looks like tag syntax 29 | 30 | removing the token below or using non-underscore separators breaks the parser: 31 | 32 | :fix_me: 33 | 34 | it turns out the parser balked because of the property drawer usage 35 | below, used to be :PROPERTIES:, now named :MYDRAWER:; formerly the 36 | parser balked because it expected property lists within the property 37 | drawer 38 | 39 | # another comment 40 | 41 | * markup test 42 | 43 | ***** subsubsubsubheading 44 | 45 | *bold* inline *bold* with text *bold* 46 | inline _underline_ with text 47 | inline /italic!/ with text 48 | inline =code= with text 49 | inline ~verbatim~ with text 50 | 51 | HR should appear below: 52 | -------- 53 | HR should appear above: 54 | 55 | 56 | --- 57 | 58 | [[file:ruby.org][ruby]] 59 | 60 | interspersed [[file:ruby.org][ruby]] link 61 | 62 | *** subheading with drawer 63 | :MYDRAWER: 64 | inside the drawer 65 | :END: 66 | outside the drawer 67 | 68 | **** subheading with tags :unexpected:sailboat:spacecat:mushroom: 69 | 70 | *** definition list 71 | - we :: like U 72 | - you :: like wii 73 | 74 | ** source code :sourceCode: 75 | 76 | #+begin_src emacs-lisp 77 | (let ((greeting "hello, world") 78 | (name :spacecat) 79 | (food :candybar)) 80 | (insert (format "%s! I am %s and I eat %s" greeting name food))) 81 | 82 | #+end_src 83 | 84 | * Tag test :tagTest1: 85 | Simple tag test 86 | * TODO Tag master and TODO Test :tagTest2: 87 | This node test the TODO and tag css class. 88 | # author: giovanni giorgi 89 | * an image. 90 | 91 | just the filepath (shows in iimage mode, exports to img): 92 | 93 | file:img/org-mode-logo.png 94 | 95 | with bracket (shows in iimage mode, exports to *text*): 96 | 97 | [file:img/org-mode-logo.png] 98 | 99 | double bracket (shows in iimage mode, exports to img): 100 | 101 | [[file:img/org-mode-logo.png]] 102 | 103 | alt text (doesn't show in iimage mode, exports to /link/): 104 | 105 | [[file:img/org-mode-logo.png][org mode logo]] 106 | -------------------------------------------------------------------------------- /test/includeTest.org: -------------------------------------------------------------------------------- 1 | * Include files 2 | 3 | Include other files during export. For example, to include your .emacs file, you could use: 4 | 5 | #+INCLUDE: "./orgmodeTest.org" src emacs-lisp 6 | The first parameter is the file name to include. The optional second parameter specifies the block type: ‘example’, ‘export’ or p‘src’). The option third parameter specifies the source code language to use for formatting the contents. This is relevant to both ‘export’ and ‘src’ block types. 7 | 8 | If an include file is specified as having a markup language, Org neither checks for valid syntax nor changes the contents in any way. For ‘example’ and ‘src’ blocks, Org code-escapes the contents before inclusion. 9 | 10 | Unsupported: 11 | #+INCLUDE: "~/my-book/chapter2.org" :minlevel 1 12 | #+INCLUDE: "~/.emacs" :lines "5-10" 13 | ^^^^^ Include lines 5 to 10, 10 excluded 14 | -------------------------------------------------------------------------------- /test/issue11-empty-head.org: -------------------------------------------------------------------------------- 1 | * Test 2 | hello 3 | * 4 | Empty header above 5 | * wow 6 | cool 7 | -------------------------------------------------------------------------------- /test/loggingUsageExampleTest.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'), 2 | assert = require('assert'), 3 | _=require("underscore"), 4 | util=require('util'), 5 | winston=require('winston'); 6 | 7 | var orgParser=require("../lib/org-mode-parser"); 8 | 9 | // var logger = new winston.Logger({ 10 | // level: 'debug', 11 | // transports: [ 12 | // new (winston.transports.Console)(), 13 | // new (winston.transports.File)({ filename: './org-parser-debug.log' }) 14 | // ] 15 | // }); 16 | winston.loggers.add('tester',{ 17 | console: { 18 | level: 'debug', 19 | colorize:true, 20 | label:'vows-test' 21 | } 22 | }); 23 | 24 | winston.loggers.add('orgmodeparser',{ 25 | console: { 26 | level: 'debug', 27 | colorize:false, 28 | label:'orgmode' 29 | } 30 | }); 31 | 32 | 33 | var mylog=winston.loggers.get('tester'); 34 | 35 | vows.describe('OrgMode Logging demo').addBatch({ 36 | '2017':{ 37 | 'Logging': { 38 | topic: function (){ 39 | mylog.debug("Starting debug test"); 40 | orgParser.enableDebug(winston.loggers.get("orgmodeparser")); 41 | orgParser.makelist("./test/issue11-empty-head.org",this.callback); 42 | }, 43 | 'test-log1': function (n,unused){ 44 | mylog.debug('Disabling debugging'); 45 | orgParser.disableDebug(); 46 | } 47 | } 48 | 49 | } 50 | }).export(module); 51 | // Local variables: 52 | // mode: js2 53 | // mode: company 54 | // End: 55 | -------------------------------------------------------------------------------- /test/no-header.org: -------------------------------------------------------------------------------- 1 | Text without header should be loaded anyway 2 | ** Sub header here 3 | Sub header without heading 4 | -------------------------------------------------------------------------------- /test/orgmodeTest.org: -------------------------------------------------------------------------------- 1 | * TODO Test Node 1 2 | Test file 3 | Total nodes: 10 4 | This a TODO Node 5 | Do not touch the body of Section 3 because it is used in the test 6 | Also the SEQ_TODO right here is used on the test 7 | #+STARTUP: showstars 8 | #+STARTUP: showall 9 | #+SEQ_TODO: TODO(t) STARTED(s) WAITING(w) DONE(d) CANCELLED(c) 10 | * STARTED [#B] Node With Priority B, a custom keyword and a tag :veryHard: 11 | :PROPERTIES: 12 | :okIamHard:for sure 13 | :END: 14 | * Section 3 with SCHEDULED 15 | SCHEDULED: <2011-12-31 Sat> 16 | This section has a schedule for the end of 2012, a very bad date someone said. 17 | Trust no one 18 | * Section 4 with a deadline 19 | DEADLINE: <2011-04-23 Tue> 20 | I have a deadline on Giovanni Giorgi Birth date 21 | * Section 5 with schedule and deadline too 22 | DEADLINE: <2011-11-30 Wed> SCHEDULED: <2011-11-01 Tue> 23 | * Section 6 with a lot of bad stuff inside it :tagged:withMoreThenOneTag:yeah 24 | DEADLINE: <2111-10-04 Tue> SCHEDULED: <2011-11-30 Wed> 25 | CLOCK: [2011-10-04 Tue 16:08]--[2011-10-04 Tue 16:09] => 0:01 26 | This node is used to stress the parsing algorithm with a very complex node 27 | to validate. This node has plenty of stuff inside and will likely stress the entire 28 | parsing procedure as well. 29 | It has also a 'clock' entry. The clock entry is used for measuting time taken dealing with this entry 30 | and could also be 'truncated' like in the section below 31 | * Section 7 is like section 6 but only with a starting CLOCK 32 | CLOCK: [2011-10-04 Tue 16:41] 33 | Text expected for Section 7 34 | * DONE Section 8 with schedule, deadline and closed 35 | DEADLINE: <2011-11-30 Wed> SCHEDULED: <2011-11-01 Tue> CLOSED: <2011-11-29> 36 | * Test Tree :test:testRoot: 37 | ** Unit Test Subtree Section :test:eventsLits:testTreeRoot: 38 | :PROPERTIES: 39 | :nondevoapparireNelBody:yes 40 | :END: 41 | Questa è una sezione di test. per il test di subtree 42 | Fa anche il test di non visibilità properties nel body 43 | *** Tree1 senza figli :testTree1: 44 | Test di SubTree1 che non ha figli 45 | *** Tree2 con un figlio :testTree2: 46 | Test di SubTree2 che ha un figlio 47 | **** Tree 2's subTree :testTree2:testTree2_1:unique: 48 | 49 | 50 | -------------------------------------------------------------------------------- /test/parserTest.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'), 2 | assert = require('assert'), 3 | _=require("underscore"), 4 | util=require('util'); 5 | 6 | var orgParser=require("../lib/org-mode-parser"); 7 | 8 | // To understand how the parser works, uncomment the following: 9 | // orgParser.enableDebug(); 10 | 11 | 12 | // Create a Test Suite 13 | vows.describe('OrgMode Tests').addBatch({ 14 | 'basicLibraries':{ 15 | /* 16 | 'quickcheck1':{ 17 | // Very stupid, only for getting quickcheck on track 18 | 'qc1':function() { 19 | 20 | function randomParser(body) { 21 | var n=orgParser.parseBigString("* Very Stupid\nData Line1\n"+body); 22 | return n[0].body.match("Data Line1\n.*"); 23 | } 24 | 25 | // Check a bunch of random strings... 26 | assert.equal(true,qc.forAll(randomParser,qc.arbString)); 27 | } 28 | },*/ 29 | 'meta-example':{ 30 | 'binding':function(){ 31 | var f=function(){return this;}; 32 | assert.equal(false,f.call(false)); 33 | // Sigh... 34 | assert.notEqual(null,f.call(null)); 35 | // Using bind... 36 | var bind=function(f,this_context){ 37 | return function() {return f.apply(this_context,arguments);}; 38 | }; 39 | 40 | assert.equal("A", bind(f,"A")()); 41 | assert.equal("B", bind(f,"B")()); 42 | }, 43 | 'binding2':function(){ 44 | var f=function(){return this;}; 45 | /* Object.bind(f,{...} 46 | * is used to bind the hashtable to the environment 47 | */ 48 | assert.equal( Object.bind(f,"A")(), "A"); 49 | 50 | //assert.equal(1,2); 51 | }, 52 | 'binding3':function(){ 53 | var f=function(){return this;}; 54 | var thisEnv={'k':'v'}; 55 | assert.equal( Object.bind(f,thisEnv)(), thisEnv); 56 | }, 57 | 'array push':function(){ 58 | var a=[]; 59 | a.push("1"); 60 | assert.equal(a[0],"1"); 61 | }, 62 | 'regexp test':function(){ 63 | assert.isTrue(/a/.test("a")); 64 | } 65 | }, //meta-examp 66 | 'lowlevelFunctions':{ 67 | 'simpleCycle':function(){ 68 | var collector=0; 69 | _.each([1, 2, 3], function(num){ collector+=num; }); 70 | assert.equal(collector,6); 71 | }, 72 | 'groupBy':function(){ 73 | var groupedStuff= 74 | _.groupBy([1.3, 2.1, 2.4], function(num){ return Math.floor(num); }); 75 | assert.equal( 1.3 , groupedStuff[1][0] ); 76 | }, 77 | 'asLines works without ending CR':function(){ 78 | var lines=orgParser.asLines("1\n2"); 79 | //console.dir(lines); 80 | assert.equal(lines[0],"1"); 81 | }, 82 | 'asLines works with ending CR':function(){ 83 | var lines=orgParser.asLines("1\n2\n"); 84 | //console.dir(lines); 85 | assert.equal(lines[0],"1"); 86 | }, 87 | 'asLines return an array':function(){ 88 | var lines=orgParser.asLines("a\nab\nabc"); 89 | for(var l in lines){ 90 | //console.dir(lines[l]); 91 | } 92 | }, 93 | 'simple matching': function(){ 94 | var q="abaco b c".match(/(\w+)/g); 95 | assert.equal(q[0],'abaco'); 96 | assert.equal(q[1],'b'); 97 | assert.equal(q[2],'c'); 98 | }, 99 | 'Orgnode object':function(){ 100 | new orgParser.Orgnode("*", "Test", "", undefined, undefined,undefined); 101 | }, 102 | 'Orgnode direct accessors':function(){ 103 | var n=new orgParser.Orgnode("*", "Test", "", undefined, undefined,undefined ); 104 | var ptest={ 105 | a:1 106 | }; 107 | assert.isTrue(n.properties!==ptest); 108 | n.properties=ptest; 109 | assert.isTrue(n.properties===ptest); 110 | //console.dir(n); 111 | }, 112 | 'parse function of one header':function(){ 113 | var n=orgParser.parseBigString("* Very Stupid\nData Line1"); 114 | //console.dir(n); 115 | assert.equal(n[0].headline,"Very Stupid"); 116 | }, 117 | 'parse function of one header with body':function(){ 118 | var n=orgParser.parseBigString("* Very Stupid\nData Line1"); 119 | assert.equal(n[0].body,"Data Line1\n"); 120 | }, 121 | 'null is the default value for tag,priority,scheduled, deadline when not set.': function(){ 122 | var e=orgParser.parseBigString("* Less Stupid\nData Line1")[0]; 123 | assert.isNull(e.tag); 124 | assert.isNull(e.priority); 125 | assert.isNull(e.scheduled); 126 | assert.isNull(e.deadline); 127 | }, 128 | 'empty is the default for tags when no tags set':function(){ 129 | var e=orgParser.parseBigString("* Less Stupid\nData Line1")[0]; 130 | assert.isEmpty(e.tags); 131 | }, 132 | 'tags are coerched to true':function(){ 133 | var e=orgParser.parseBigString("* Less Stupid :monotag:\nData Line1")[0]; 134 | assert.isNotEmpty(e.tags); 135 | assert.isNotNull(e.tags.monotag); 136 | 137 | assert.isTrue("monotag" in e.tags); 138 | // Slight non-obious api 139 | if(!e.tags.monotag){ 140 | throw new Error("Tags are coerced to false in the if"); 141 | }else{ 142 | assert.isTrue(e.tags.monotag); 143 | } 144 | }, 145 | 'tag parsing of first tag':function(){ 146 | var n=orgParser.parseBigString( 147 | "* Test Tree :test:testRoot:" 148 | ); 149 | //console.dir(n); 150 | assert.equal(n[0].headline,"Test Tree"); 151 | assert.equal(n[0].tag,"test"); 152 | }, 153 | 'tag parsing of other tags':function(){ 154 | var n=orgParser.parseBigString( 155 | "* Test Tree :test:testRoot:" 156 | ); 157 | //console.dir(n); 158 | assert.isTrue("testRoot" in n[0].tags); 159 | }, 160 | 'bastard tag parsing works':function(){ 161 | var n=orgParser.parseBigString("* Quiz DB :noexport:webintro:")[0]; 162 | assert.equal(n.tags['noexport'],true); 163 | 164 | 165 | }, 166 | 'parser ignore comments':function(){ 167 | var n1=orgParser.parseBigString( 168 | "* Test Tree :test:testRoot:\n"+ 169 | "# Comment\n"+ 170 | "Data" 171 | )[0]; 172 | assert.equal(n1.body,"Data\n"); 173 | }, 174 | 'properties parsing works':function(){ 175 | var n1=orgParser.parseBigString( 176 | "* Test Tree :test:testRoot:\n"+ 177 | "# Comment\n"+ 178 | ":PROPERTIES:\n"+ 179 | ":simple:yes\n"+ 180 | ":END:\n"+ 181 | "First line of Data\n"+ 182 | "Second line of data\n" 183 | )[0]; 184 | //assert.equal(n1.body,"Data\n"); 185 | assert.equal(n1.properties["simple"],"yes"); 186 | // No simple proprerty in the body but... 187 | assert.isNull(n1.body.match(/simple/g)); 188 | assert.isNotNull(n1.body.match(/line/g)); 189 | }, 190 | 'test toOrgString heading simple case':function(){ 191 | var demoStuff= 192 | "** Test Tree :test:testRoot:\n"+ 193 | "First line of Data\n"; 194 | var n=orgParser.parseBigString(demoStuff)[0]; 195 | assert.equal(n.toOrgString(),demoStuff); 196 | }, 197 | 'test toOrgString heading with TODO':function(){ 198 | var demoStuff= 199 | "* TODO Test Tree :test:testRoot:\n"+ 200 | "First line of Data\n"; 201 | var n=orgParser.parseBigString(demoStuff)[0]; 202 | assert.equal(n.toOrgString(),demoStuff); 203 | }, 204 | 'test toOrgString heading with TODO and priorities':function(){ 205 | var demoStuff= 206 | "*** TODO [#A] Test Tree :test:testRoot:\n"+ 207 | "First line of Data\n"; 208 | var n=orgParser.parseBigString(demoStuff)[0]; 209 | assert.equal(n.toOrgString(),demoStuff); 210 | }, 211 | 212 | 'test toOrgString emits properties':function(){ 213 | var demoStuff= 214 | "** Test Tree :test:testRoot:\n"+ 215 | ":PROPERTIES:\n"+ 216 | ":simple:yes\n"+ 217 | ":END:\n"+ 218 | "First line of Data\n"; 219 | var n=orgParser.parseBigString(demoStuff)[0]; 220 | assert.equal(n.toOrgString(),demoStuff); 221 | } 222 | }, 223 | 'End to end Simple Parsing':{ 224 | topic: function (){ 225 | orgParser.makelist("./test/treeLevel.org",this.callback); 226 | }, 227 | 'three node parser is right':function(n,unused){ 228 | //console.dir(n); 229 | assert.equal(n.length, 5); 230 | }, 231 | 'headers are right':function(n,u){ 232 | assert.equal(n[0].headline,"Level one"); 233 | assert.equal(n[1].headline,"Level two"); 234 | assert.equal(n[2].headline,"Level three"); 235 | }, 236 | 'tags are right':function(n,u){ 237 | assert.equal(n[0].tag,"data"); 238 | assert.isTrue("1" in n[0].tags); 239 | 240 | assert.equal(n[1].tag,"nodata"); 241 | assert.equal(n[2].tag,"data"); 242 | assert.isTrue("last" in n[2].tags); 243 | } 244 | 245 | }, 246 | 'End to end Parser':{ 247 | topic: function(){ 248 | orgParser.makelist("./test/orgmodeTest.org", this.callback); 249 | }, 250 | 251 | 'nodes parsing count is right':function(nodeList,u){ 252 | assert.equal(13,nodeList.length); 253 | }, 254 | 'first node heading is right':function(nodeList,u){ 255 | assert.equal(nodeList[0].headline,"Test Node 1"); 256 | //console.dir(nodeList); 257 | }, 258 | 'first node has also a todo keyword':function(nodeList,u) { 259 | //console.dir(nodeList[0]); 260 | assert.equal(nodeList[0].todo,"TODO"); 261 | }, 262 | 'node 2 has priority B and a STARTED Keyword':function(nodeList,u){ 263 | var n=nodeList[1]; 264 | //console.dir(n); 265 | assert.equal(n.todo,"STARTED"); 266 | assert.equal(n.priority,"B"); 267 | assert.equal(n.tag,"veryHard"); 268 | assert.equal(n.properties['okIamHard'],'for sure'); 269 | }, 270 | 'Node 3 is scheduled':function(nl,u){ 271 | assert.equal(nl[2].headline,"Section 3 with SCHEDULED"); 272 | assert.isNotNull(nl[2].scheduled); 273 | }, 274 | 'Node 4 has deadline':function (nl,u){ 275 | assert.isNotNull(nl[3].deadline); 276 | }, 277 | 'Node 5 has deadline and schedule':function (nl,u){ 278 | assert.equal(nl[4].headline,"Section 5 with schedule and deadline too"); 279 | assert.isNotNull(nl[4].schedule); 280 | assert.isNotNull(nl[4].deadline); 281 | }, 282 | 'Node 8 is DONE and has deadline, schedule and closed':function (nl,u){ 283 | assert.equal(nl[7].headline,"Section 8 with schedule, deadline and closed"); 284 | assert.isNotNull(nl[7].schedule); 285 | assert.isNotNull(nl[7].deadline); 286 | assert.isNotNull(nl[7].closed); 287 | assert.equal(nl[7].todo,"DONE"); 288 | }, 289 | 'Node 3 body is right':function(nl,u){ 290 | assert.equal(nl[2].headline,"Section 3 with SCHEDULED"); 291 | assert.equal(nl[2].body,"This section has a schedule for the end of 2012, a very bad date someone said.\nTrust no one\n"); 292 | }, 293 | 'CLOCK: FULL directive is ignored':function(nl,u){ 294 | assert.equal(nl[5].headline,"Section 6 with a lot of bad stuff inside it"); 295 | //console.dir(nl[5].body); 296 | assert.isNull(nl[5].body.match('CLOCK')); 297 | }, 298 | 'CLOCK: Partial directive is ignored':function(nl,u){ 299 | assert.equal(nl[6].headline,"Section 7 is like section 6 but only with a starting CLOCK"); 300 | //console.dir(nl[6].body); 301 | assert.isNull(nl[6].body.match('CLOCK')); 302 | }, 303 | 'Heading and subheading level works as expected':function(nodeList,u){ 304 | var l1=nodeList.length-5; 305 | var n1=nodeList[l1]; 306 | assert.equal(n1.headline,"Test Tree"); 307 | assert.equal(n1.level,1); 308 | assert.equal(nodeList[l1+1].level,2); 309 | assert.equal(nodeList[l1+2].level,3); 310 | 311 | assert.equal(nodeList[l1+3].headline,"Tree2 con un figlio"); 312 | assert.equal(nodeList[l1+3].level,3); 313 | 314 | // Last subtree 315 | assert.equal(nodeList[l1+4].level,4); 316 | } 317 | 318 | }, 319 | 320 | 'OrgQuery':{ 321 | topic: function (){ 322 | var thisOfTest=this; 323 | orgParser.makelist("./test/treeLevel.org",function(nodes){ 324 | var ofd=new orgParser.OrgQuery(nodes); 325 | thisOfTest.callback(ofd, nodes); 326 | }); 327 | }, 328 | 'subtree without params return all nodes':function(ofd,u){ 329 | assert.equal(ofd.selectSubtree().length,5); 330 | assert.equal(ofd.selectSubtree(null).length,5); 331 | }, 332 | 'subtree works/1':function(ofd,nodes){ 333 | var n1=ofd.selectSubtree()[0]; 334 | var subtreeN1=ofd.selectSubtree(n1); 335 | //console.dir(subtreeN1); 336 | assert.equal(subtreeN1.length,2); 337 | assert.equal(ofd.selectSubtree(nodes[1]).length,1); 338 | }, 339 | 'subtree returns an OrgQuery ':function(ofd,nodes){ 340 | var subtree=ofd.selectSubtree(nodes[0]); 341 | var levelTwo=subtree.allNodes[0]; 342 | assert.equal(levelTwo.headline,"Level two"); 343 | var monosubtreeLevel3=subtree.selectSubtree(levelTwo); 344 | assert.equal(monosubtreeLevel3.length,1); 345 | }, 346 | 'selectTag works/1':function(ofd,nodes){ 347 | var queryResult=ofd.selectTag('data'); 348 | assert.equal(queryResult.length,2); 349 | assert.equal(queryResult.allNodes[0].headline,'Level one'); 350 | assert.equal(queryResult.allNodes[1].headline,'Level three'); 351 | }, 352 | 'selectTag works/2':function(ofd,nodes){ 353 | var queryResult=ofd.selectTag('1'); 354 | assert.equal(queryResult.length,2); 355 | assert.equal(queryResult.allNodes[0].headline,'Level one'); 356 | assert.equal(queryResult.allNodes[1].headline,'This is a huge node with a lot of data and tags'); 357 | }, 358 | 'each works':function(ofd,nodes){ 359 | var titles=[]; 360 | ofd.selectTag('data').each(function f(elem){ 361 | titles.push(elem.headline); 362 | }); 363 | assert.equal(titles[0],'Level one'); 364 | assert.equal(titles[1],'Level three'); 365 | } 366 | 367 | }, 368 | 'OrgQuery-Complex':{ 369 | topic: function (){ 370 | var thisOfTest=this; 371 | orgParser.makelist("./test/treeLevelComplex.org",function(nodes){ 372 | var ofd=new orgParser.OrgQuery(nodes); 373 | thisOfTest.callback(ofd, nodes); 374 | }); 375 | }, 376 | 'test complex subtree':function(ofd,nodes){ 377 | var nodesWithMeta=ofd.selectTag('has').selectTag('sublevel').selectTag('property'); 378 | var headersToTest=nodes[0].properties["headersToTest"]; 379 | assert.equal(nodesWithMeta.length, headersToTest); 380 | //console.dir(nodesWithMeta.first()); 381 | // WARN: nodesWithMeta is NOT AN ARRAY (yet) so you must use 382 | // this syntax or the provided each() method... 383 | _.each(nodesWithMeta.toArray(), function(n){ 384 | var expectedData=n.properties["expected-sub-levels"]; 385 | //console.log("? "+expectedData+" For "+n.headline); 386 | //console.dir(ofd.selectSubtree(n)); 387 | assert.equal(ofd.selectSubtree(n).length,expectedData); 388 | }); 389 | }, 390 | 'test toOrgString with reparsing':function (ofd,nodes){ 391 | var str=ofd.toOrgString(); 392 | // Deadly test: we force eating its own dog food here 393 | var expectedSize=ofd.length; 394 | var reparsed=orgParser.parseBigString(str); 395 | assert.equal(reparsed.length,expectedSize); 396 | var ofdReparsed=new orgParser.OrgQuery(reparsed); 397 | assert.equal(ofdReparsed.toOrgString(),str); 398 | } 399 | //'test ordering':function(ofd,nodes) 400 | }, 401 | 'OrgQuery-sorting and rejecting':{ 402 | 'sorting':function(){ 403 | var ofd=new orgParser.OrgQuery( 404 | orgParser.parseBigString("* 2\n* 1\n* 8")); 405 | var sorted= 406 | ofd.sortBy(function (n){ return n.headline;}).toArray(); 407 | assert.equal(sorted[0].headline,"1"); 408 | assert.equal(sorted[2].headline,"8"); 409 | }, 410 | 'rejecting':function(){ 411 | orgParser.makelist("./test/treeLevel.org",function(nodes){ 412 | var ofd=new orgParser.OrgQuery(nodes); 413 | var newCollection=ofd.reject(function (n){ 414 | return n.level!==3; 415 | }); 416 | var n1=newCollection.toArray()[0]; 417 | assert.equal(n1.headline,"Level three"); 418 | }); 419 | }, 420 | 'rejectTag':function(){ 421 | orgParser.makelist("./test/treeLevel.org",function(nodes){ 422 | var ofd=new orgParser.OrgQuery(nodes); 423 | var firstHeader=ofd.rejectTag('complex').rejectTag('last').rejectTag('nodata'); 424 | assert.equal(firstHeader.length,1); 425 | assert.equal(firstHeader.first().headline,"Level one"); 426 | }); 427 | }, 428 | 'random test 1':function(){ 429 | orgParser.makelist("./test/treeLevel.org",function(nodes){ 430 | var ofd=new orgParser.OrgQuery([ nodes[0] ]); 431 | //console.dir(ofd.random()); 432 | assert.equal(ofd.random().key,nodes[0].key); 433 | }); 434 | }, 435 | 'random test 2':function(){ 436 | orgParser.makelist("./test/treeLevel.org",function(nodes){ 437 | var ofd=new orgParser.OrgQuery([ nodes[0], nodes[1] ]); 438 | var collector={}; 439 | // Given 2 value, the probability x launch will get only one of them is 440 | // (1/2)^x 441 | // so for a 99% prob we catch all... 442 | // (1-(1/4096))*100 443 | // x=12 444 | _.each(_.range(12),function(){ 445 | var r=ofd.random(); 446 | collector[r.key]=r; 447 | }); 448 | //console.dir(_.toArray(collector)); 449 | assert.equal(ofd.length,_.toArray(collector).length); 450 | }); 451 | }, 452 | 'random test 3':function(){ 453 | // Given 1000 values the probability two launch give the same value is 454 | // 1/10000 i.e. 0,01% 455 | rList=[]; 456 | _.each(_.range(10000), function (fakeNumber){ 457 | rList.push( 458 | new orgParser.Orgnode("*", "Test "+fakeNumber, 459 | "", undefined, undefined,undefined)); 460 | }); 461 | var ofd=new orgParser.OrgQuery( rList ); 462 | var collector={}; 463 | _.each(_.range(2),function(){ 464 | var r=ofd.random(); 465 | collector[r.key]=r; 466 | }); 467 | assert.equal(_.toArray(collector).length,2); 468 | } 469 | } 470 | 471 | 472 | } //basic 473 | 474 | 475 | }).export(module); // Export it 476 | 477 | vows.describe('OrgMode API Stability').addBatch({ 478 | 'API 0.0.5':{ 479 | 'rejectArchived':function(){ 480 | assert.isFalse(_.isUndefined(orgParser.OrgQuery.prototype.rejectArchived)); 481 | }, 482 | 'drawer must be an array otherwise raise error':function(){ 483 | assert.throws(function(){ 484 | new orgParser.Orgnode("firstExtraParam","*", "Test", "", 485 | undefined, "Cheating on drawer associative array"); 486 | },Error); 487 | 488 | } 489 | }, 490 | 'API respect 0.0.4 specification':{ 491 | 'Basic Objects Exists':function(){ 492 | assert.isFalse(_.isUndefined(orgParser.makelist)); 493 | }, 494 | 495 | 496 | 'OrgQuery.prototype.selectSubtree Exists':function(){ 497 | assert.isFalse(_.isUndefined(orgParser.OrgQuery.prototype.selectSubtree)); 498 | }, 499 | 500 | 'OrgQuery.prototype.selectTag Exists':function(){ 501 | assert.isFalse(_.isUndefined(orgParser.OrgQuery.prototype.selectTag)); 502 | }, 503 | 504 | 'OrgQuery.prototype.sortBy Exists':function(){ 505 | assert.isFalse(_.isUndefined(orgParser.OrgQuery.prototype.sortBy)); 506 | }, 507 | 508 | 'OrgQuery.prototype.reject Exists':function(){ 509 | assert.isFalse(_.isUndefined(orgParser.OrgQuery.prototype.reject)); 510 | }, 511 | 512 | 'OrgQuery.prototype.rejectTag Exists':function(){ 513 | assert.isFalse(_.isUndefined(orgParser.OrgQuery.prototype.rejectTag)); 514 | }, 515 | 516 | 'OrgQuery.prototype.toArray Exists':function(){ 517 | assert.isFalse(_.isUndefined(orgParser.OrgQuery.prototype.toArray)); 518 | }, 519 | 520 | 'OrgQuery.prototype.each Exists':function(){ 521 | assert.isFalse(_.isUndefined(orgParser.OrgQuery.prototype.each)); 522 | }, 523 | 524 | 'OrgQuery.prototype.random Exists':function(){ 525 | assert.isFalse(_.isUndefined(orgParser.OrgQuery.prototype.random)); 526 | } 527 | } 528 | }).export(module); 529 | 530 | vows.describe('OrgMode BUGS').addBatch({ 531 | '0.0.3 BUGS':{ 532 | 'key is not always unique on leaf nodes': function(){ 533 | var n=orgParser.parseBigString( 534 | "* Key test\nData Line1\nData Line2\n* Header 2\nData Line of header2\n* Header 3"); 535 | //console.dir(n); 536 | keyCollection={}; 537 | _.each(n,function(elem){ 538 | keyCollection[elem.key]=true; 539 | }); 540 | assert.equal(_.toArray(keyCollection).length,n.length); 541 | }, 542 | 'keys must be unique even with different sources':function (){ 543 | var n1=orgParser.parseBigString( 544 | "* Key test\nData Line1\nData Line2\n* Header 2\nData Line of header2\n* Header 3"); 545 | var n2=orgParser.parseBigString( 546 | "* Key test\nData Line1\nData Line2\n* Header 2\nData Line of header2\n* Header 3"); 547 | keyCollection={}; 548 | //console.dir(n1); 549 | //console.dir(n2); 550 | _.each(n1,function(elem){ 551 | keyCollection[elem.key]=true; 552 | }); 553 | _.each(n2,function(elem){ 554 | keyCollection[elem.key]=true; 555 | }); 556 | assert.equal(_.toArray(keyCollection).length,(n1.length+n2.length)); 557 | 558 | }, 559 | 'Orgnode constructor validate parameters/1 on arity':function(){ 560 | assert.throws(function(){ 561 | new orgParser.Orgnode("firstExtraParam","*", "Test", "", undefined, undefined,undefined); 562 | },Error); 563 | 564 | }, 565 | 'Orgnode constructor validate parameters/2 on types':function(){ 566 | assert.throws(function(){ 567 | new orgParser.Orgnode("*", "Test", "", undefined, new Object,undefined); 568 | },Error); 569 | 570 | }, 571 | ':PROPERTIES: without :END: generate an error':function(){ 572 | var fx=function(){ 573 | orgParser.parseBigString( 574 | "* Test Tree :test:testRoot:\n"+ 575 | "# Comment\n"+ 576 | ":PROPERTIES:\n"+ 577 | ":simple:yes\n"+ 578 | /*":END:\n"+ INTENTIONALLY REMOVED*/ 579 | "First line of Data\n"+ 580 | "Second line of data\n" 581 | );}; 582 | assert.throws(fx,orgParser.ParseError); 583 | }, 584 | 'OrgQuery had a weak constructor': 585 | { 586 | 587 | topic:function(){ 588 | return (function(){new orgParser.OrgQuery(null,"Bof","unused");}); 589 | }, 590 | 'OrgQuery does not accept wrong arrays.../1':function(){ 591 | assert.throws(function(){ 592 | new orgParser.OrgQuery(["Cheating", "a lot"]); 593 | },Error); 594 | }, 595 | '.... even if I cheat... /2':function(){ 596 | assert.throws(function(){ 597 | new orgParser.OrgQuery([ 598 | new orgParser.Orgnode("*", "Test", "", undefined,undefined), 599 | "Cheating", "a bit"]); 600 | },Error); 601 | }, 602 | 603 | "The 'Cannot read property 'length' of null' is not thrown": 604 | function(f){ 605 | try{ 606 | f(); 607 | } catch (x) { 608 | util.isError(x); 609 | assert.isFalse(x instanceof TypeError); 610 | // util.debug(x); 611 | // GOT, expected 612 | assert.equal(""+x, 613 | "Error: IllegalArgumentException: First arguments {nodes} cannot be Null or Undefined"); 614 | //assert.isTrue(x instanceof orgParser.IllegalArgumentException); 615 | } 616 | 617 | } 618 | 619 | // API CHANGE: 620 | , "An Error Object is thrown":function(f){ 621 | assert.throws(f,Error); 622 | } 623 | //, "IllegalArgumentException Exists":function (){ 624 | // assert.isFalse(_.isUndefined(orgParser.IllegalArgumentException)); 625 | // }, 626 | // "ParseError Exists":function(){ 627 | // assert.isFalse(_.isUndefined(orgParser.ParseError)); 628 | // } 629 | 630 | } 631 | 632 | } 633 | }).export(module); 634 | 635 | //test/tableTest.org 636 | 637 | // vows.describe('OrgMode 0.0.7').addBatch({ 638 | 639 | // 'OrgQuery API improvements /1':{ 640 | 641 | // 'You cannot push undefined to OrgQuery':function (){ 642 | 643 | // assert.throws(function(){ 644 | // new orgParser.OrgQuery(undefined); 645 | // },orgParser.IllegalArgumentException); 646 | // }, 647 | // 'You cannot fool selectSubtree /1 with wrong input-types':function(){ 648 | 649 | // assert.throws(function(){ 650 | // new orgParser.OrgQuery([]).selectSubtree([]); 651 | // },orgParser.IllegalArgumentException); 652 | 653 | 654 | // }, 655 | // 'SelectSubtree Work nicely':function(){ 656 | 657 | // orgParser.makelist("./README.org", function (nl){ 658 | // var q=new orgParser.OrgQuery(nl); 659 | // var subtree=q.selectSubtree(q.selectTag('releaseNotes').first()); 660 | // //console.dir(subtree); 661 | // assert.isTrue(subtree.length>1); 662 | // }); 663 | // }, 664 | // 'Subtree will work with 1-sized collection as input':function(){ 665 | // orgParser.makelist("./README.org", function (nl){ 666 | // var q=new orgParser.OrgQuery(nl); 667 | // var rn=q.selectTag('releaseNotes'); 668 | // assert.equal(rn.length,1); 669 | // var subtree=q.selectSubtree(rn); 670 | // assert.isTrue(subtree.length>5); 671 | // // var subtree=q.selectSubtree(q.selectTag('releaseNotes')); 672 | // // assert.isFalse(_.isUndefined(subtree) && _.isNull(subtree)); 673 | // // assert.isTrue(subtree.length>5); 674 | // // //console.dir(subtree); 675 | // }); 676 | // } 677 | 678 | // 'OrgQuery API improvements /2': { 679 | // topic: function(){ 680 | // var myCallback=this.callback; 681 | // orgParser.makelist("./test/orgmodeTest.org", function (nl){ 682 | // myCallback(new orgParser.OrgQuery(nl),nl); 683 | // }); 684 | // }, 685 | // 'q.selectSubtree(emptyNodeList) === q.selectSubtree() === q':function(q,n){ 686 | // var bastardEmptyQuery=q.selectTag('nonExistent'); 687 | // assert.equal(bastardEmptyQuery.length,0); 688 | // var resultOfABadMix=q.selectSubtree(bastardEmptyQuery); 689 | // assert.equal(resultOfABadMix.length, q.length); 690 | // }, 691 | // 'Subtree will throw error on more then one elments collections as input':function(q,n){ 692 | // assert.throws(function(){q.selectSubtree(q); }, orgParser.llegalArgumentException); 693 | // } 694 | // } 695 | 696 | // } //Refining Subtree api 697 | 698 | // }).export(module); 699 | 700 | vows.describe('OrgMode 0.0.5').addBatch({ 701 | 702 | 'generic :DRAWER: syntax':{ 703 | topic: function (){ 704 | orgParser.makelist("./test/drawerAndArchiveTag.org",this.callback); 705 | }, 706 | 'DRAWER_ONE exists':function(nodes, u){ 707 | var n1=nodes[0]; 708 | assert.isTrue(n1.drawer.DRAWER_ONE!==null); 709 | }, 710 | 'DRAWER_ONE has content':function(nodes, u){ 711 | var n1=nodes[0]; 712 | assert.equal( 713 | n1.drawer.DRAWER_ONE,"I am drawer one, with one line\n"); 714 | }, 715 | 716 | 'DRAWER_ONE exists and there is no DRAWER TWO on node1':function(nodes, u){ 717 | var n1=nodes[0]; 718 | assert.isTrue(n1.drawer['DRAWER_TWO']===undefined); 719 | }, 720 | 'DRAWER_TWO exists':function(nodes, u){ 721 | var n2=nodes[1]; 722 | //console.dir(n2); 723 | assert.isTrue(n2.drawer.DRAWER_TWO!==null); 724 | }, 725 | 'double drawers works':function(nodes,u){ 726 | var q=new orgParser.OrgQuery(nodes); 727 | var n=q.selectTag('indentedDrawerTest').first(); 728 | //console.dir(n); 729 | assert.isTrue(n.drawer.Drawer1 !==undefined); 730 | assert.isTrue(n.drawer.Drawer2 !==undefined); 731 | 732 | }, 733 | 'drawers indentation is stripped away':function(nodes,u){ 734 | var q=new orgParser.OrgQuery(nodes); 735 | var n=q.selectTag('indentedDrawerTest').first(); 736 | //console.dir(n); 737 | var drawer1Content=n.drawer.Drawer1; 738 | assert.equal(drawer1Content,"Nice to meet you, unindented I hope\n"); 739 | }, 740 | 'complex drawer mix':function(nodes,u){ 741 | var q=new orgParser.OrgQuery(nodes); 742 | var n=q.selectTag('complexDrawerMix').first(); 743 | assert.equal(n.body,'In this header, drawer are mixed in a fancy way\nTest below drawer1\nText above drawer2\nText below drawer2\n'); 744 | assert.isTrue(n.drawer.Drawer1 !==undefined); 745 | assert.isTrue(n.drawer.Drawer2 !==undefined); 746 | assert.equal(n.drawer.Drawer2, "Drawer2 content\n"); 747 | } 748 | 749 | }, 750 | 'Archive tag is supported':{ 751 | topic: function (){ 752 | orgParser.makelist("./test/drawerAndArchiveTag.org",this.callback); 753 | }, 754 | 'last two nodes are archived':function (nodes,u){ 755 | assert.isTrue(nodes[nodes.length-2].isArchived()); 756 | assert.isTrue(nodes[nodes.length-1].isArchived()); 757 | }, 758 | 'first node not archived':function (nodes,u){ 759 | assert.isFalse(nodes[0].isArchived()); 760 | }, 761 | 'query on archive tag':function(nodes,u){ 762 | var nodeSize=nodes[0].properties.expectedNodes; 763 | var expectedArchiveNodes=nodes[0].properties.archivedNodes; 764 | var q=new orgParser.OrgQuery(nodes); 765 | var notArchived=q.rejectTag('ARCHIVE').length; 766 | assert.equal(notArchived,nodeSize-expectedArchiveNodes); 767 | }, 768 | 'rejectArchived works':function(nodes,u){ 769 | var q=new orgParser.OrgQuery(nodes); 770 | var nodeSize=nodes[0].properties.expectedNodes; 771 | var expectedArchiveNodes=nodes[0].properties.archivedNodes; 772 | var expected=nodeSize-expectedArchiveNodes; 773 | assert.equal(q.rejectArchived().length, expected); 774 | 775 | } 776 | }, 777 | 'drawer without an :END: must fail':function(){ 778 | var fx=function(){ 779 | orgParser.parseBigString( 780 | "* Test Tree :test:testRoot:\n"+ 781 | "# Comment\n"+ 782 | ":WRONG_DRAWER:\n"+ 783 | ":simple:yes\n"+ 784 | /*":END:\n"+ INTENTIONALLY REMOVED*/ 785 | "First line of Data\n"+ 786 | "Second line of data\n" 787 | );}; 788 | assert.throws(fx,orgParser.ParseError); 789 | } 790 | 791 | ,'Tests for Error: ParseError: DRAWER :50: Found but :END: missed':{ 792 | topic: function(){ 793 | orgParser.makelist("./test/bug3DrawerConfusion.org",this.callback); 794 | }, 795 | 'parsing works':function(nodes,u){ 796 | // Expected one master node 797 | //console.dir(nodes); 798 | assert.isNotNull(nodes); 799 | //assert.equal(1,nodes.length); 800 | } 801 | } 802 | 803 | 804 | }).export(module); 805 | 806 | vows.describe('OrgMode API Bugs').addBatch({ 807 | '2017':{ 808 | 'Issue11': { 809 | topic: function (){ 810 | //orgParser.enableDebug(); 811 | orgParser.makelist("./test/issue11-empty-head.org",this.callback); 812 | }, 813 | 'empty header is here':function(n,unused){ 814 | //console.dir(n[1]); 815 | assert.equal(n.length,3 /*expected*/); 816 | }, 817 | 'emptyheader is empty':function(n,unused){ 818 | //console.dir(n[1]); 819 | assert.equal(n[1].headline," "); 820 | }, 821 | // 'body is not': function(n,unused){ 822 | // assert.equal(n[1].body, "Empty header above"); 823 | // } 824 | 825 | } 826 | /** GG Eval if it is needed: 827 | ,'No header body trouble': { 828 | topic: function (){ 829 | //orgParser.enableDebug(); 830 | orgParser.makelist("./test/no-header.org",this.callback); 831 | }, 832 | 'empty header is here':function(n,unused){ 833 | console.dir(n); 834 | assert.equal(n[0].headline, " "); 835 | assert.equal(n.length,2); 836 | } 837 | }*/ 838 | 839 | } 840 | }).export(module); 841 | 842 | vows.describe('Bug Fix 2018').addBatch({ 843 | '2018':{ 844 | 'colon mistake works': { 845 | topic: function (){ 846 | orgParser.enableDebug(); 847 | orgParser.makelist("./test/colon_mistake.org",this.callback); 848 | //orgParser.disableDebug(); 849 | }, 850 | 'Tag detection':function(n,unused){ 851 | (n[0]); 852 | assert.equal(n[0].tags["toblog"],true); 853 | //assert.equal(n.length,1 /*expected*/); 854 | }, 855 | 856 | } 857 | } 858 | }).export(module); 859 | 860 | 861 | // vows.describe('OrgMode Reika').addBatch({ 862 | // '2018':{ 863 | // 'INCLUDE works': { 864 | // topic: function (){ 865 | // //orgParser.enableDebug(); 866 | // orgParser.makelist("./test/includeTest.org",this.callback); 867 | // }, 868 | // 'IncludeWorks_basic':function(n,unused){ 869 | // //console.dir(n[1]); 870 | // assert.equal(n.length,3 /*expected*/); 871 | // }, 872 | 873 | // } 874 | // } 875 | // }).export(module); 876 | -------------------------------------------------------------------------------- /test/performanceTests.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'), 2 | assert = require('assert'), 3 | _=require("underscore"); 4 | 5 | 6 | 7 | var orgParser=require("../lib/org-mode-parser"); 8 | // To understand how the parser works, uncomment the following: 9 | // orgParser.enableDebug(); 10 | 11 | // The temp file used for some performance tests... 12 | var tempFileName="./test/bigTestFile.org.tmp"; 13 | vows.describe('OrgMode API Stability').addBatch({ 14 | 'Simple Performance Testing':{ 15 | topic: function(){ 16 | 17 | var fs=require('fs'); 18 | var stuffToDuplicate=fs.readFileSync("./test/treeLevel.org",'utf-8'); 19 | 20 | // Known emacs trouble near 4234k+ lines... 21 | // Our test is near this level 22 | for(var i=1; i<=8; i++){ 23 | //console.log(stuffToDuplicate); 24 | stuffToDuplicate +=stuffToDuplicate; 25 | } 26 | var sizeOfStuff=stuffToDuplicate.split(/\n/).length-1; 27 | 28 | // var fd=fs.openSync("./emacsCrasher.org", "w+"); 29 | // fs.writeSync(fd,stuffToDuplicate, 0, 'utf-8'); 30 | // fs.closeSync(fd); 31 | 32 | orgParser.makelistFromStringWithPerformance(stuffToDuplicate,this.callback,true); 33 | }, 34 | 35 | 'Huge string Loading performance':function(nodelist,performance){ 36 | 37 | /** Examples with max i= 15 we got 38 | msPerNode: 0.09316... 39 | 8000 on poor centrino 40 | */ 41 | var success=performance.nodesPerSeconds>8150; 42 | if(!success ){ 43 | console.dir(performance); 44 | } //else console.dir(performance); 45 | assert.isTrue(success); 46 | }, 47 | 'Regen Time performance':function(orgNodesList,performance){ 48 | var s=""; 49 | var startTime=Date.now(); 50 | _.each(orgNodesList, function (nx){ 51 | s+=nx.toOrgString(); 52 | }); 53 | var timeTaken=Date.now()-startTime; 54 | var nodesPerSeconds= 1000* (orgNodesList.length/timeTaken); 55 | console.log("toOrgString() per seconds:"+nodesPerSeconds); 56 | }, 57 | 'OrgQuery building time':function(orgNodesList,performance){ 58 | var startTime=Date.now(); 59 | // Trick: we will make a big father here 60 | _.each(orgNodesList, function (n){ 61 | n.level=n.level+2; 62 | }); 63 | orgNodesList[0].level=1; 64 | var q= new orgParser.OrgQuery(orgNodesList); 65 | console.log("Query Parser build time of "+orgNodesList.length+" Nodes:"+(Date.now()-startTime)+"ms"); 66 | var bigSubtree=q.selectSubtree(orgNodesList[0]); 67 | console.log("Big Subtree size="+bigSubtree.length); 68 | } 69 | 70 | 71 | } 72 | }).export(module); 73 | -------------------------------------------------------------------------------- /test/tableTest.org: -------------------------------------------------------------------------------- 1 | * Example of Org mode table 2 | | Simple table without heading | Col 2 | Col3 | 3 | | Row2 | | | 4 | | Row3 | | | 5 | * Table with heading 6 | | Simple table with heading | Col 2 | Col3 | 7 | |---------------------------+-------+------| 8 | | Row2 | | | 9 | | Row3 | | | 10 | 11 | * Table with narrow limit 12 | Use C-c C-c to update table structure after changing the 13 | A special startup option may be used to align tables 14 | #+STARTUP: align 15 | | Simple table with heading | Col 2 | Col3 | 16 | |------------+-------+------| 17 | | Row2 | | | 18 | | Row3 | | | 19 | | <10> | | | 20 | * Table groups 21 | Taken from 3.3 info manual of org 7 22 | | | N | N^2 | N^3 | N^4 | sqrt(n) | sqrt[4](N) | 23 | |---+----+-----+-----+-----+---------+------------| 24 | | / | <> | < | | > | < | > | 25 | | # | 1 | 1 | 1 | 1 | 1 | 1 | 26 | | # | 2 | 4 | 8 | 16 | 1.4142 | 1.1892 | 27 | | # | 3 | 9 | 27 | 81 | 1.7321 | 1.3161 | 28 | |---+----+-----+-----+-----+---------+------------| 29 | #+TBLFM: $3=$2^2::$4=$2^3::$5=$2^4::$6=sqrt($2)::$7=sqrt(sqrt(($2))) 30 | 31 | 32 | -------------------------------------------------------------------------------- /test/to-html-test.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'), 2 | assert = require('assert'), 3 | _=require("underscore"), 4 | util=require('util'); 5 | 6 | var orgParser=require("../lib/org-mode-parser"); 7 | 8 | // To understand how the parser works, uncomment the following: 9 | // orgParser.enableDebug(); 10 | 11 | // TODO: Include in afair way the html parser... 12 | 13 | // Create a Test Suite 14 | vows.describe('OrgMode Html plugin').addBatch({ 15 | 'basicLibraries':{ 16 | 'html':{ 17 | 'vanilla':function() { 18 | var n=new orgParser.OrgQuery(orgParser.parseBigString("* Very Stupid\nData Line1\n")); 19 | assert.equal('

    Very Stupid

    Data Line1

    ',n.toHtml()); 20 | } 21 | }, 22 | 23 | 'toHtml testing': { 24 | topic: function () { 25 | var that = this; 26 | // Callback issue see 27 | // https://github.com/vowsjs/vows/issues/187 28 | orgParser.makelistNode("./test/htmlTest.org", that.callback ); 29 | 30 | }, 31 | 'begin_src test': function (err,n) { 32 | //console.log("JUST HERE"); 33 | //console.dir(n); 34 | var q = new orgParser.OrgQuery(n); 35 | var sc=q.selectTag("sourceCode"); 36 | //console.dir(sc); 37 | //console.dir(sc.toHtml()); 38 | // assert.isNotNull(sc.toHtml().match(/[<]code class/)); 39 | assert.equal('

    source code

    \n\n (let ((greeting "hello, world")\n (name :spacecat)\n (food :candybar))\n (insert (format "%s! I am %s and I eat %s" greeting name food)))

    ' 40 | ,sc.toHtml()); 41 | 42 | }, 43 | 'css tag':function(err,n){ 44 | var q = new orgParser.OrgQuery(n); 45 | assert.equal('

    Tag test

    Simple tag test\n

    ', 46 | q.selectTag("tagTest1").toHtml()); 47 | }, 48 | 49 | 'css todo and tag':function(err,n){ 50 | var q = new orgParser.OrgQuery(n); 51 | assert.equal('

    Tag master and TODO Test

    This node test the TODO and tag css class.\n

    ', 52 | q.selectTag("tagTest2").toHtml()); 53 | }, 54 | 'full rendering':function(err, n){ 55 | var fs=require("fs"); 56 | fs.writeFileSync( "./renderTest.html", (new orgParser.OrgQuery(n)).toHtml({ 57 | fullHtml:true, 58 | pugTemplatePath:"./test/fullHtml.jade" 59 | })); 60 | 61 | } 62 | } 63 | 64 | } 65 | }).export(module); 66 | -------------------------------------------------------------------------------- /test/treeLevel.org: -------------------------------------------------------------------------------- 1 | * Level one :data:1: 2 | This file is used for some tree-nesting test and ALSO for performance testing. 3 | For this reason, the performance testing part has a very complex node on its hown 4 | First Level 5 | with data 6 | ** Level two :nodata: 7 | *** Level three :data:last: 8 | This level with some text 9 | * TODO This is a huge node with a lot of data and tags :complex:1: 10 | DEADLINE: <2111-10-04 Tue> SCHEDULED: <2011-11-30 Wed> 11 | CLOCK: [2011-10-04 Tue 16:08]--[2011-10-04 Tue 16:09] => 0:01 12 | :PROPERTIES: 13 | :property1: value1 14 | :END: 15 | This node is used to stress the parsing algorithm with a very complex node 16 | to deal with. This node has plenty of stuff inside and will likely stress the entire 17 | parsing procedure as well. 18 | It has: 19 | + some properties 20 | + a CLOCK entry used for measuting time taken dealing with this entry 21 | + a bullet list (you are reading it) 22 | ** Complex subtree for performance testing :last:t1:t2:t3:t4:t5 23 | :PROPERTIES: 24 | :p1: property1 25 | :p2: property1 26 | :p3: property1 27 | :Effort: Example of Effort Property 28 | :CATEGORY: test 29 | :END: 30 | 31 | :d1: 32 | A free content drawer 33 | :END: 34 | :d2: 35 | A second drawer with free contente 36 | :END: 37 | I am a complex subtree with a lot of stuff inside 38 | I have a table 39 | | Nice 3 Column | table | is here | 40 | |---------------+-------+---------| 41 | | | | | 42 | I have a bunch of tags and a long list of properties. 43 | For children pleasure, I spot also a long list of #+ special org commands... 44 | #+HERE TO GIVE TROUBLES 45 | #+DRAWERS: d1 d2 46 | #+ME TOO HERE TO GIVE TROUBLES 47 | 48 | -------------------------------------------------------------------------------- /test/treeLevelComplex.org: -------------------------------------------------------------------------------- 1 | * Subtree brothers test :has:sublevel:property: 2 | :PROPERTIES: 3 | :expected-sub-levels:4 4 | :headersToTest:3 5 | :END: 6 | ** I am at level2 7 | ** I am too at level2 :has:sublevel:property: 8 | :PROPERTIES: 9 | :expected-sub-levels:1 10 | :END: 11 | *** I am at level3 12 | ** I am again at level 2 13 | * I am a new level 1 :has:sublevel:property: 14 | :PROPERTIES: 15 | :expected-sub-levels: 3 16 | :END: 17 | Be careful because you do not need to have correct sequences in subheadings. 18 | *** Head 3 Right now 19 | **** Head 4 20 | ** Crazy Head 2 21 | --------------------------------------------------------------------------------