package/package.json000644 000765 000024 0000001372 13107012526013015 0ustar00000000 000000 { "name": "sass-graph", "version": "2.2.4", "description": "Parse sass files and extract a graph of imports", "license": "MIT", "repository": "xzyfer/sass-graph", "author": "xzyfer", "main": "sass-graph.js", "directories": { "bin": "./bin" }, "scripts": { "test": "nyc mocha", "coverage": "nyc report --reporter=text-lcov | coveralls" }, "keywords": [ "sass", "graph" ], "dependencies": { "glob": "^7.0.0", "lodash": "^4.0.0", "scss-tokenizer": "^0.2.3", "yargs": "^7.0.0" }, "devDependencies": { "assert": "^1.3.0", "chai": "^3.5.0", "coveralls": "^2.13.0", "mocha": "^3.2.0", "nyc": "^10.2.0" }, "files": [ "bin", "parse-imports.js", "sass-graph.js" ] } package/sass-graph.js000644 000765 000024 0000011354 13101043466013137 0ustar00000000 000000 'use strict'; var fs = require('fs'); var path = require('path'); var _ = require('lodash'); var glob = require('glob'); var parseImports = require('./parse-imports'); // resolve a sass module to a path function resolveSassPath(sassPath, loadPaths, extensions) { // trim sass file extensions var re = new RegExp('(\.('+extensions.join('|')+'))$', 'i'); var sassPathName = sassPath.replace(re, ''); // check all load paths var i, j, length = loadPaths.length, scssPath, partialPath; for (i = 0; i < length; i++) { for (j = 0; j < extensions.length; j++) { scssPath = path.normalize(loadPaths[i] + '/' + sassPathName + '.' + extensions[j]); try { if (fs.lstatSync(scssPath).isFile()) { return scssPath; } } catch (e) {} } // special case for _partials for (j = 0; j < extensions.length; j++) { scssPath = path.normalize(loadPaths[i] + '/' + sassPathName + '.' + extensions[j]); partialPath = path.join(path.dirname(scssPath), '_' + path.basename(scssPath)); try { if (fs.lstatSync(partialPath).isFile()) { return partialPath; } } catch (e) {} } } // File to import not found or unreadable so we assume this is a custom import return false; } function Graph(options, dir) { this.dir = dir; this.extensions = options.extensions || []; this.index = {}; this.follow = options.follow || false; this.loadPaths = _(options.loadPaths).map(function(p) { return path.resolve(p); }).value(); if (dir) { var graph = this; _.each(glob.sync(dir+'/**/*.@('+this.extensions.join('|')+')', { dot: true, nodir: true, follow: this.follow }), function(file) { graph.addFile(path.resolve(file)); }); } } // add a sass file to the graph Graph.prototype.addFile = function(filepath, parent) { var entry = this.index[filepath] = this.index[filepath] || { imports: [], importedBy: [], modified: fs.statSync(filepath).mtime }; var resolvedParent; var isIndentedSyntax = path.extname(filepath) === '.sass'; var imports = parseImports(fs.readFileSync(filepath, 'utf-8'), isIndentedSyntax); var cwd = path.dirname(filepath); var i, length = imports.length, loadPaths, resolved; for (i = 0; i < length; i++) { loadPaths = _([cwd, this.dir]).concat(this.loadPaths).filter().uniq().value(); resolved = resolveSassPath(imports[i], loadPaths, this.extensions); if (!resolved) continue; // recurse into dependencies if not already enumerated if (!_.includes(entry.imports, resolved)) { entry.imports.push(resolved); this.addFile(fs.realpathSync(resolved), filepath); } } // add link back to parent if (parent) { resolvedParent = _(parent).intersection(this.loadPaths).value(); if (resolvedParent) { resolvedParent = parent.substr(parent.indexOf(resolvedParent)); } else { resolvedParent = parent; } entry.importedBy.push(resolvedParent); } }; // visits all files that are ancestors of the provided file Graph.prototype.visitAncestors = function(filepath, callback) { this.visit(filepath, callback, function(err, node) { if (err || !node) return []; return node.importedBy; }); }; // visits all files that are descendents of the provided file Graph.prototype.visitDescendents = function(filepath, callback) { this.visit(filepath, callback, function(err, node) { if (err || !node) return []; return node.imports; }); }; // a generic visitor that uses an edgeCallback to find the edges to traverse for a node Graph.prototype.visit = function(filepath, callback, edgeCallback, visited) { filepath = fs.realpathSync(filepath); var visited = visited || []; if (!this.index.hasOwnProperty(filepath)) { edgeCallback('Graph doesn\'t contain ' + filepath, null); } var edges = edgeCallback(null, this.index[filepath]); var i, length = edges.length; for (i = 0; i < length; i++) { if (!_.includes(visited, edges[i])) { visited.push(edges[i]); callback(edges[i], this.index[edges[i]]); this.visit(edges[i], callback, edgeCallback, visited); } } }; function processOptions(options) { return _.assign({ loadPaths: [process.cwd()], extensions: ['scss', 'css', 'sass'], }, options); } module.exports.parseFile = function(filepath, options) { if (fs.lstatSync(filepath).isFile()) { filepath = path.resolve(filepath); options = processOptions(options); var graph = new Graph(options); graph.addFile(filepath); return graph; } // throws }; module.exports.parseDir = function(dirpath, options) { if (fs.lstatSync(dirpath).isDirectory()) { dirpath = path.resolve(dirpath); options = processOptions(options); var graph = new Graph(options, dirpath); return graph; } // throws }; package/parse-imports.js000644 000765 000024 0000002627 13101043466013677 0ustar00000000 000000 var tokenizer = require('scss-tokenizer'); function parseImports(content, isIndentedSyntax) { var tokens = tokenizer.tokenize(content); var results = []; var tmp = ''; var inImport = false; var inParen = false; var prevToken = tokens[0]; var i, token; for (i = 1; i < tokens.length; i++) { token = tokens[i]; if (inImport && !inParen && token[0] === 'string') { results.push(token[1]); } else if (token[1] === 'import' && prevToken[1] === '@') { if (inImport && !isIndentedSyntax) { throw new Error('Encountered invalid @import syntax.'); } inImport = true; } else if (inImport && !inParen && (token[0] === 'ident' || token[0] === '/')) { tmp += token[1]; } else if (inImport && !inParen && (token[0] === 'space' || token[0] === 'newline')) { if (tmp !== '') { results.push(tmp); tmp = ''; if (isIndentedSyntax) { inImport = false; } } } else if (inImport && token[0] === ';') { inImport = false; if (tmp !== '') { results.push(tmp); tmp = ''; } } else if (inImport && token[0] === '(') { inParen = true; tmp = ''; } else if (inParen && token[0] === ')') { inParen = false; } prevToken = token; } if (tmp !== '') { results.push(tmp); } return results; } module.exports = parseImports; package/bin/sassgraph000755 000765 000024 0000005215 13101043466013221 0ustar00000000 000000 #!/usr/bin/env node var fs = require('fs'); var path = require('path'); var command, directory, file; var yargs = require('yargs') .usage('Usage: $0 [options] [file]') // .demand(1) .command('ancestors', 'Output the ancestors') .command('descendents', 'Output the descendents') .example('$0 ancestors -I src src/ src/_footer.scss', 'outputs the ancestors of src/_footer.scss') .option('I', { alias: 'load-path', default: [process.cwd()], describe: 'Add directories to the sass load path', type: 'array', }) .option('e', { alias: 'extensions', default: ['scss', 'css', 'sass'], describe: 'File extensions to include in the graph', type: 'array', }) .option('f', { alias: 'follow', default: false, describe: 'Follow symbolic links', type: 'bool', }) .option('j', { alias: 'json', default: false, describe: 'Output the index in json', type: 'bool', }) .version(function() { return require('../package').version; }) .alias('v', 'version') .help('h') .alias('h', 'help'); var argv = yargs.argv; if (argv._.length === 0) { yargs.showHelp(); process.exit(1); } if (['ancestors', 'descendents'].indexOf(argv._[0]) !== -1) { command = argv._.shift(); } if (argv._ && path.extname(argv._[0]) === '') { directory = argv._.shift(); } if (argv._ && path.extname(argv._[0])) { file = argv._.shift(); } try { if (!directory) { throw new Error('Missing directory'); } if (!command && !argv.json) { throw new Error('Missing command'); } if (!file && (command === 'ancestors' || command === 'descendents')) { throw new Error(command + ' command requires a file'); } var loadPaths = argv.loadPath; if(process.env.SASS_PATH) { loadPaths = loadPaths.concat(process.env.SASS_PATH.split(/:/).map(function(f) { return path.resolve(f); })); } var graph = require('../').parseDir(directory, { extensions: argv.extensions, loadPaths: loadPaths, follow: argv.follow, }); if(argv.json) { console.log(JSON.stringify(graph.index, null, 4)); process.exit(0); } if (command === 'ancestors') { graph.visitAncestors(path.resolve(file), function(f) { console.log(f); }); } if (command === 'descendents') { graph.visitDescendents(path.resolve(file), function(f) { console.log(f); }); } } catch(e) { if (e.code === 'ENOENT') { console.error('Error: no such file or directory "' + e.path + '"'); } else { console.log('Error: ' + e.message); } // console.log(e.stack); process.exit(1); } package/CHANGELOG.md000644 000765 000024 0000005305 13107012505012335 0ustar00000000 000000 # Change Log All notable changes to this project will be documented in this file. ## [next] ### Features ### Fixes ### Tests ## [2.2.4] ### Dependencies - yargs@^7.0.0 (@alan-agius4, #84) ## [2.2.3] ### Dependencies - scss-tokenizer@^0.2.3 (@xzyfer) ## [2.2.2] ### Fixes - Babel runtime error messages (@xzyfer, #76 #77) ### Dependencies - scss-tokenizer@^0.2.1 (@xzyfer) ## [2.2.1] ### Fixes - Babel runtime error messages (@STRML, #76 #77) ### Dependencies - scss-tokenizer@^0.2.0 ## [2.2.0] ### Features - Replace `@import` regexes with [scss-tokenizer](https://www.npmjs.com/package/scss-tokenizer) (@xzyfer, #68) - Add support for the old indented (`.sass`) syntax (@xzyfer, #31) - Add an option to follow symbolic links (@ludwiktrammer, #74) ### Fixes - Replaces deprecated `fs.existsSync` (@martinheidegger, #29) ### Tests - Significantly clean up test suite (@xzyfer, #69) ### Dependencies - yargs@^6.6.0 - mocha@^3.2.0 ## [2.1.2] ### Fixes - Remove non-essential files from npm package (@jorrit, #48) - Update yargs to version 4.7.1 (@greenkeeperio-bot, #46) - Update glob to version 7.0.0 (@greenkeeperio-bot, #36) ## [2.1.1] ### Fixes - Don't add directory `@import`s to graph - [@niksy](https://github.com/niksy) ## [2.1.0] ### Features - Update to lodash 4 - [@nightwolfz](https://github.com/nightwolfz) ### Fixes - Fixed directories with extensions being treated as files - [@niksy](https://github.com/niksy) ## [2.0.1] ### Fixes - Fixed tests for Windows - [@pleunv](https://github.com/pleunv) ## [2.0.0] ### BREAKING CHANGES - `.sass` files are not included in the graph by default. Use the `-e .sass` flag. ### Features - Configurable file extensions - [@dannymidnight](https://github.com/dannymidnight), [@xzyfer](https://github.com/xzyfer) ### Fixes - Prioritize cwd when resolving load paths - [@schnerd](https://github.com/schnerd) ### Tests - Added test for prioritizing cwd when resolving load paths - [@xzyfer](https://github.com/xzyfer) ## [1.3.0] ### Features - Add support for indented syntax - [@vegetableman](https://github.com/vegetableman) ## [1.2.0] ### Features - Add support for custom imports - [@kevin-smets](https://github.com/kevin-smets) ## [1.1.0] - 2015-03-18 ### Fixes - Only strip extension for css, scss, sass files - [@nervo](https://github.com/nervo) ## [1.0.4] - 2015-03-03 ### Tests - Added a test for nested imports - [@kevin-smets](https://github.com/kevin-smets) ## [1.0.3] - 2015-02-02 ### Fixes - Replace incorrect usage of `for..in` loops with simple `for` loops ## [1.0.2] - 2015-02-02 ### Fixes - Don't iterate over inherited object properties ## [1.0.1] - 2015-01-05 ### Fixes - Handle errors in the visitor ## [1.0.0] - 2015-01-05 Initial stable release package/readme.md000644 000765 000024 0000005424 13101043466012311 0ustar00000000 000000 # Sass Graph Parses Sass files in a directory and exposes a graph of dependencies [![Build Status](https://travis-ci.org/xzyfer/sass-graph.svg?branch=master)](https://travis-ci.org/xzyfer/sass-graph) [![Coverage Status](https://coveralls.io/repos/github/xzyfer/sass-graph/badge.svg?branch=master)](https://coveralls.io/github/xzyfer/sass-graph?branch=master) [![npm version](https://badge.fury.io/js/sass-graph.svg)](http://badge.fury.io/js/sass-graph) [![Dependency Status](https://david-dm.org/xzyfer/sass-graph.svg?theme=shields.io)](https://david-dm.org/xzyfer/sass-graph) [![devDependency Status](https://david-dm.org/xzyfer/sass-graph/dev-status.svg?theme=shields.io)](https://david-dm.org/xzyfer/sass-graph#info=devDependencies) ## Install Install with [npm](https://npmjs.org/package/sass-graph) ``` npm install --save-dev sass-graph ``` ## Usage Usage as a Node library: ```js var sassGraph = require('./sass-graph'); ``` Usage as a command line tool: The command line tool will parse a graph and then either display ancestors, descendents or both. ``` $ ./bin/sassgraph --help Usage: bin/sassgraph [options] [file] Commands: ancestors Output the ancestors descendents Output the descendents Options: -I, --load-path Add directories to the sass load path -e, --extensions File extensions to include in the graph -j, --json Output the index in json -h, --help Show help -v, --version Show version number Examples: ./bin/sassgraph descendents test/fixtures test/fixtures/a.scss /path/to/test/fixtures/b.scss /path/to/test/fixtures/_c.scss ``` ## API #### parseDir Parses a directory and builds a dependency graph of all requested file extensions. #### parseFile Parses a file and builds its dependency graph. ## Options #### loadPaths Type: `Array` Default: `[process.cwd]` Directories to use when resolved `@import` directives. #### extensions Type: `Array` Default: `['scss', 'css', 'sass']` File types to be parsed. #### follow Type: `Boolean` Default: `false` Follow symbolic links. ## Example ```js var sassGraph = require('./sass-graph'); console.log(sassGraph.parseDir('test/fixtures')); //{ index: {, // '/path/to/test/fixtures/a.scss': { // imports: ['b.scss'], // importedBy: [], // }, // '/path/to/test/fixtures/b.scss': { // imports: ['_c.scss'], // importedBy: ['a.scss'], // }, // '/path/to/test/fixtures/_c.scss': { // imports: [], // importedBy: ['b/scss'], // }, //}} ``` ## Running Mocha tests You can run the tests by executing the following commands: ``` npm install npm test ``` ## Authors Sass graph was originally written by [Lachlan Donald](http://lachlan.me). It is now maintained by [Michael Mifsud](http://twitter.com/xzyfer). ## License MIT