From 349836fe2c1ef4d1131fa5d982324569a18f5024 Mon Sep 17 00:00:00 2001 From: Sven Slootweg Date: Sat, 16 Apr 2016 15:08:10 +0200 Subject: [PATCH] Initial commit --- .gitignore | 3 +- README.md | 29 +++++++++++++ gulpfile.js | 38 +++++++++++++++++ index.js | 3 ++ package.json | 33 +++++++++++++++ src/flatten-query.js | 33 +++++++++++++++ src/format-flattened-query.js | 14 ++++++ src/index.js | 80 +++++++++++++++++++++++++++++++++++ src/query.pegjs | 55 ++++++++++++++++++++++++ src/tag-queries.js | 23 ++++++++++ test.js | 8 ++++ 11 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 README.md create mode 100644 gulpfile.js create mode 100644 index.js create mode 100644 package.json create mode 100644 src/flatten-query.js create mode 100644 src/format-flattened-query.js create mode 100644 src/index.js create mode 100644 src/query.pegjs create mode 100644 src/tag-queries.js create mode 100644 test.js diff --git a/.gitignore b/.gitignore index d3f11de..bdaa3f7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ # https://help.github.com/articles/ignoring-files # Example .gitignore files: https://github.com/github/gitignore /bower_components/ -/node_modules/ \ No newline at end of file +/node_modules/ +/lib/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..4e82cf4 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# packagename + +TODO + +## License + +[WTFPL](http://www.wtfpl.net/txt/copying/) or [CC0](https://creativecommons.org/publicdomain/zero/1.0/), whichever you prefer. A donation and/or attribution are appreciated, but not required. + +## Donate + +My income consists largely of donations for my projects. If this module is useful to you, consider [making a donation](http://cryto.net/~joepie91/donate.html)! + +You can donate using Bitcoin, PayPal, Flattr, cash-in-mail, SEPA transfers, and pretty much anything else. + +## Contributing + +Pull requests welcome. Please make sure your modifications are in line with the overall code style, and ensure that you're editing the files in `src/`, not those in `lib/`. + +Build tool of choice is `gulp`; simply run `gulp` while developing, and it will watch for changes. + +Be aware that by making a pull request, you agree to release your modifications under the licenses stated above. + +## Usage + +TODO + +## API + +TODO \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..a81ecd1 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,38 @@ +var gulp = require('gulp'); + +var gutil = require('gulp-util'); +var babel = require('gulp-babel'); +var pegjs = require('gulp-peg'); +var cache = require('gulp-cached'); +var remember = require('gulp-remember'); +var plumber = require('gulp-plumber'); + +var sources = { + babel: ["src/**/*.js"], + pegjs: ["src/**/*.pegjs"] +} + +gulp.task('babel', function() { + return gulp.src(sources.babel) + .pipe(plumber()) + .pipe(cache("babel")) + .pipe(babel({presets: ["es2015"]}).on('error', gutil.log)).on('data', gutil.log) + .pipe(remember("babel")) + .pipe(gulp.dest("lib/")); +}); + +gulp.task('pegjs', function() { + return gulp.src(sources.pegjs) + .pipe(plumber()) + .pipe(cache("pegjs")) + .pipe(pegjs().on('error', gutil.log)).on('data', gutil.log) + .pipe(remember("pegjs")) + .pipe(gulp.dest("lib/")); +}) + +gulp.task('watch', function () { + gulp.watch(sources.babel, ['babel']); + gulp.watch(sources.pegjs, ['pegjs']); +}); + +gulp.task('default', ['pegjs', 'babel', 'watch']); \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..507257b --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require("./lib"); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..0bad884 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "riot-query", + "version": "0.0.1", + "description": "Lets you traverse through a tree of Riot tags and underlying DOM elements", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "http://git.cryto.net/joepie91/riot-query.git" + }, + "keywords": [ + "riot", + "query", + "jquery" + ], + "author": "Sven Slootweg", + "license": "WTFPL", + "dependencies": { + "dedupe": "^2.0.1" + }, + "devDependencies": { + "babel-preset-es2015": "^6.6.0", + "gulp": "^3.9.1", + "gulp-babel": "^6.1.2", + "gulp-cached": "^1.1.0", + "gulp-peg": "^0.2.0", + "gulp-plumber": "^1.1.0", + "gulp-remember": "^0.3.0", + "gulp-util": "^3.0.7" + } +} diff --git a/src/flatten-query.js b/src/flatten-query.js new file mode 100644 index 0000000..8438836 --- /dev/null +++ b/src/flatten-query.js @@ -0,0 +1,33 @@ +'use strict'; + +function flattenSegmentTree(stacks, segments) { + segments.forEach((segment) => { + stacks = flattenSegment(stacks, segment); + }); + + return stacks; +} + +function flattenSegment(stacks, segment) { + if (segment.type === "identifier" || segment.type === "wildcard") { + return stacks.map((stack) => { + return stack.concat([segment]); + }); + } else if (segment.type === "group") { + let newStacks = []; + + segment.items.forEach((groupItem) => { + newStacks = newStacks.concat(flattenSegmentTree(stacks, groupItem)); + }); + + return newStacks; + } else if (segment.type === "empty") { + return stacks; + } else { + throw new Error(`Unknown segment type '${segment.type}'`) + } +} + +module.exports = function (queryTree) { + return flattenSegmentTree([[]], queryTree); +} \ No newline at end of file diff --git a/src/format-flattened-query.js b/src/format-flattened-query.js new file mode 100644 index 0000000..8e54ca5 --- /dev/null +++ b/src/format-flattened-query.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = function(query) { + return query.map((segment) => { + if (segment.type === "identifier") { + return segment.value; + } else if (segment.type === "wildcard") { + return "**"; + } else { + return "?"; + } + }).join("/"); +} + diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..03afec0 --- /dev/null +++ b/src/index.js @@ -0,0 +1,80 @@ +'use strict'; + +const dedupe = require("dedupe"); +const queryParser = require("./query"); +const flatten = require("./flatten-query"); +const tagQueries = require("./tag-queries"); + +function ensureArray(item) { + if (Array.isArray(item)) { + return item; + } else if (item == null) { + return []; + } else { + return [item]; + } +} + +function recurseTags(tag) { + let allTags = [tag]; + + Object.keys(tag.tags).forEach((tagType) => { + ensureArray(tag.tags[tagType]).forEach((subTag) => { + allTags = allTags.concat(recurseTags(subTag)); + }); + }); + + return allTags; +} + +function createQuery(query) { + let {riotQuery, domQuery} = queryParser.parse(query); + let flattenedQueries = flatten(riotQuery); + tagQueries(flattenedQueries); + + return function traverse(tag) { + let results = []; + + flattenedQueries.forEach((subQuery) => { + let lastFound = [tag]; + + subQuery.forEach((segment) => { + let nextLastFound = []; + + lastFound.forEach((subTag) => { + //console.log("subtag", subQuery, segment, subTag); + let found; + + if (segment.type === "identifier") { + found = ensureArray(subTag.tags[segment.value]); + } else if (segment.type === "wildcard") { + found = recurseTags(subTag); + } + + if (found.length > 0) { + nextLastFound = nextLastFound.concat(found) + } + }); + + lastFound = nextLastFound; + }); + + results = results.concat(lastFound); + }); + + return dedupe(results, result => result._riot_id); + } +} + +let queryCache = {}; + +module.exports = function(tag, query) { + if (queryCache[query] == null) { + queryCache[query] = createQuery(query); + } + + // TODO: DOM traversal + + return queryCache[query](tag); +} + diff --git a/src/query.pegjs b/src/query.pegjs new file mode 100644 index 0000000..b66d30d --- /dev/null +++ b/src/query.pegjs @@ -0,0 +1,55 @@ +{ + function concatRepeat(first, rest, restIndex) { + return [first].concat(rest.map(function(item) { + return item[restIndex]; + })); + } + + function combineFinal(riotQuery, domQuery) { + var resultObject = { + riotQuery: riotQuery + }; + + if (domQuery != null) { + resultObject.domQuery = domQuery[1]; + } + + return resultObject; + } +} + +start + = riotQuery:riotQuery domQuery:domQuerySuffix? { return combineFinal(riotQuery, domQuery) } + +riotQuery + = segment:riotQuerySegment subSegments:("/" riotQuerySegment)* { return concatRepeat(segment, subSegments, 1); } + +riotQuerySegment + = riotQuerySegmentSelector + / riotQuerySegmentGroup + +riotQuerySegmentGroup + = '{' items:riotQuerySegmentGroupItems '}' { return {type: "group", items: items}; } + +riotQuerySegmentGroupItems + = item:riotQuerySegmentGroupItemsItem subItems:("," riotQuerySegmentGroupItemsItem)* { return concatRepeat(item, subItems, 1); } + +riotQuerySegmentGroupItemsItem + = riotQuery + / '' { return [{type: "empty"}]; } + +riotQuerySegmentSelector + = riotQueryIdentifier + / riotQueryWildcard + +riotQueryIdentifier + = identifier:[a-z-]+ { return {type: "identifier", value: identifier.join("")}; } + +riotQueryWildcard + = "**" { return {type: "wildcard"}; } + +domQuery + = query:.+ { return query.join(""); } + +domQuerySuffix + = '//' domQuery \ No newline at end of file diff --git a/src/tag-queries.js b/src/tag-queries.js new file mode 100644 index 0000000..116ae01 --- /dev/null +++ b/src/tag-queries.js @@ -0,0 +1,23 @@ +'use strict'; + +const format = require("./format-flattened-query"); + +module.exports = function tagQuerySegments(queries) { + let tagMap = {}; + let currentTag = 0; + + queries.forEach((query) => { + let stack = []; + + query.forEach((segment) => { + stack.push(segment); + let formatted = format(stack); + + if (tagMap[formatted] == null) { + tagMap[formatted] = currentTag++; + } + + segment.tag = tagMap[formatted]; + }); + }); +} \ No newline at end of file diff --git a/test.js b/test.js new file mode 100644 index 0000000..60d4bd1 --- /dev/null +++ b/test.js @@ -0,0 +1,8 @@ +'use strict'; + +const util = require("util"); +const parse = require("./lib"); + +let testQuery = "foo/bar/**/{baz,qux/quz,something/{hai,bai},}/{duck,swan}//.domFoo .domBar"; + +// TODO \ No newline at end of file