├── .gitignore ├── .travis.yml ├── index.js ├── package.json ├── readme.md └── test ├── fixtures └── foo.graphml └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | node_modules 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "4.0.0" 5 | - "5.0.0" 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require('assert'); 4 | 5 | function parse (src) { 6 | // check if the runtime is node or browser 7 | if (typeof Buffer === 'function' && 8 | Buffer.isBuffer(src)) { 9 | src += ''; 10 | } 11 | var pos = 0; // parsing cursor 12 | var token = ''; // record token 13 | var obj = newObj(); // the object to be returned 14 | var ctx = obj; // context object 15 | var isParsingArgs = false; // flag if the program is parsing arguments 16 | var savedToken = null; // when parsing arguments, we need to record last token here 17 | var savedArgs = null; // when having arguments parsing done, we need to save parsed 18 | // arguments and use it in next `{` 19 | 20 | do { 21 | var ch = src[pos]; 22 | 23 | // check if the program is parsing arguments () 24 | if (isParsingArgs === true) { 25 | // enter parsing arguments mode on `(` 26 | if (ch === ')') { 27 | // parse the arguments string 28 | savedArgs = token.split(',').reduce(function (val, item) { 29 | var obj = item.split('='); 30 | // convert the string to number 31 | var oval = Number(obj[1]); 32 | if (isNaN(oval)) { 33 | oval = obj[1]; 34 | } 35 | if (oval === 'true') { 36 | oval = true; 37 | } else if (oval === 'false') { 38 | oval = false; 39 | } 40 | val[obj[0].trim()] = oval; 41 | return val; 42 | }, {}); 43 | isParsingArgs = false; 44 | token = savedToken; 45 | } else { 46 | token += ch; 47 | } 48 | continue; 49 | } 50 | 51 | assert.equal(isParsingArgs, false); 52 | if (!/(\s|{|}|,|\()/.test(ch)) { 53 | token += ch; 54 | } else { 55 | if (!obj.type) { 56 | obj.type = token; 57 | token = ''; 58 | } else if (ch === ',') { 59 | if (token) { 60 | ctx.fields.push(token); 61 | token = ''; 62 | } 63 | } else if (ch === '{') { 64 | var o = newObj(); 65 | o.parent = ctx; 66 | if (savedArgs) { 67 | o.args = savedArgs; 68 | savedArgs = null; 69 | } 70 | if (!token) { 71 | ctx = ctx.root = o; 72 | } else { 73 | ctx = ctx.methods[token] = o; 74 | token = ''; 75 | } 76 | } else if (ch === '}') { 77 | if (token) { 78 | ctx.fields.push(token); 79 | } 80 | ctx = ctx.parent; 81 | token = ''; 82 | } else if (ch === '(') { 83 | assert.equal(isParsingArgs, false); 84 | isParsingArgs = true; 85 | savedToken = token; 86 | token = ''; 87 | } 88 | } 89 | } while (src[pos++]); 90 | // return objects 91 | return obj; 92 | } 93 | 94 | function newObj () { 95 | return { 96 | type: false, 97 | fields: [], 98 | methods: {} 99 | }; 100 | } 101 | 102 | exports.parse = parse; 103 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphml-parser", 3 | "version": "1.4.0", 4 | "description": "Graph ML is for Graph Marked Lanuage, which is similar with Facebook's GraphQL, But mostly as a Domain-Specific expression in WeFlex team.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/weflex/graphml-parser.git" 12 | }, 13 | "keywords": [ 14 | "graphql", 15 | "graphml", 16 | "parser", 17 | "ast" 18 | ], 19 | "author": "Yorkie Liu ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/weflex/graphml-parser/issues" 23 | }, 24 | "homepage": "https://github.com/weflex/graphml-parser#readme", 25 | "devDependencies": { 26 | "tape": "^4.4.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # GraphML Parser 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![Build status][travis-image]][travis-url] 5 | [![Dependency Status][david-image]][david-url] 6 | [![Downloads][downloads-image]][downloads-url] 7 | 8 | **What's the Graph ML?** 9 | 10 | Graph ML is for Graph Marked Lanuage, which is similar with Facebook's GraphQL, 11 | But mostly as a Domain-Specific expression in WeFlex team. 12 | 13 | This libaray is for generating abstract syntax tree from given Graph ML source file. 14 | 15 | ## Why create this project 16 | 17 | WeFlex team internally takes StrongLoop's loopback framework be working with most other 18 | libraries, and we are focusing on writing more graceful codebase. Loopback's JSON model 19 | definition looks good to define SQL-like tables, but we have a document-based MongoDB to 20 | be generated for views, so we found the GraphQL is close to our needs. 21 | 22 | This project doesn't be compatible with GraphQL, if you expect a library to parse the complete 23 | GraphQL, you should visit [ooflorent/graphql-parser](https://github.com/ooflorent/graphql-parser). 24 | 25 | ## Installation 26 | 27 | ```sh 28 | $ npm install graphml-parser --save 29 | ``` 30 | 31 | ## Usage 32 | 33 | ```js 34 | var graphql = require('graphml-parser'); 35 | var source = fs.readFileSync('../foo.graphml').toString('utf8'); 36 | var ast = graphql.parse(source); 37 | ``` 38 | 39 | The GraphML file should look like the following: 40 | 41 | ``` 42 | Basis { 43 | foo, 44 | bar, 45 | items(limit=10, status=checkin) { 46 | bar 47 | } 48 | } 49 | ``` 50 | 51 | Then you will get the following tree: 52 | 53 | ```js 54 | { 55 | "type": "Basis", 56 | "fields": [], 57 | "root": { 58 | "fields": [ 59 | "foo", 60 | "bar" 61 | ], 62 | "methods": { 63 | "items": { 64 | "type": false, 65 | "args": { 66 | "limit": 10, 67 | "status": "checkin" 68 | }, 69 | "fields": [ 70 | "bar" 71 | ] 72 | } 73 | } 74 | } 75 | } 76 | ``` 77 | 78 | ## License 79 | 80 | MIT @ WeFlex, Inc. 81 | 82 | [npm-image]: https://img.shields.io/npm/v/graphml-parser.svg?style=flat-square 83 | [npm-url]: https://npmjs.org/package/graphml-parser 84 | [travis-image]: https://img.shields.io/travis/weflex/graphml-parser.svg?style=flat-square 85 | [travis-url]: https://travis-ci.org/weflex/graphml-parser 86 | [david-image]: http://img.shields.io/david/weflex/graphml-parser.svg?style=flat-square 87 | [david-url]: https://david-dm.org/weflex/graphml-parser 88 | [downloads-image]: http://img.shields.io/npm/dm/graphml-parser.svg?style=flat-square 89 | [downloads-url]: https://npmjs.org/package/graphml-parser 90 | -------------------------------------------------------------------------------- /test/fixtures/foo.graphml: -------------------------------------------------------------------------------- 1 | Basis { 2 | foo, 3 | bar, 4 | items(limit=10, status=checkin) { 5 | bar 6 | } 7 | } -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const test = require('tape'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const parse = require('../index').parse; 7 | 8 | test('foo', function (t) { 9 | var source = fs.readFileSync( 10 | path.join(__dirname, './fixtures/foo.graphml')); 11 | var ast = parse(source); 12 | t.equal(ast.type, 'Basis'); 13 | t.deepEqual(ast.root.fields, ['foo', 'bar']); 14 | t.deepEqual(ast.root.methods.items.fields, ['bar']); 15 | t.deepEqual(ast.root.methods.items.args, { 16 | limit: 10, 17 | status: 'checkin' 18 | }); 19 | t.end(); 20 | }); 21 | --------------------------------------------------------------------------------