├── .gitignore ├── .travis.yml ├── index.js ├── as-object-key.js ├── generate-from-dir.js ├── cmdline.js ├── tacks.js ├── entry.js ├── symlink.js ├── package.json ├── CHANGES.md ├── dir.js ├── tap.js ├── as-literal.js ├── load-from-dir.js ├── file.js ├── test └── functional.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .#* 3 | node_modules 4 | .nyc_output 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "10" 5 | - "8" 6 | - "6" 7 | - "4" 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = require('./tacks.js') 3 | module.exports.generateFromDir = require('./generate-from-dir.js') 4 | -------------------------------------------------------------------------------- /as-object-key.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = asObjectKey 3 | 4 | var asLiteral = require('./as-literal.js') 5 | 6 | function asObjectKey (key) { 7 | var isIdent = /^[a-zA-Z$_][a-zA-Z$_0-9]+$/.test(key) 8 | return isIdent ? key : asLiteral(key) 9 | } 10 | -------------------------------------------------------------------------------- /generate-from-dir.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var loadFromDir = require('./load-from-dir.js') 3 | 4 | module.exports = function generateFromDir (dir) { 5 | return "var Tacks = require('tacks')\n" + 6 | 'var File = Tacks.File\n' + 7 | 'var Symlink = Tacks.Symlink\n' + 8 | 'var Dir = Tacks.Dir\n' + 9 | 'module.exports = ' + loadFromDir(dir).toSource() + '\n' 10 | } 11 | -------------------------------------------------------------------------------- /cmdline.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | require('@iarna/cli')(main) 4 | 5 | const path = require('path') 6 | const generateFromDir = require('./generate-from-dir.js') 7 | 8 | function main (opts, fixturedir) { 9 | return new Promise((resolve, reject) => { 10 | if (!fixturedir) { 11 | console.error(`Usage: ${path.basename(process.argv[1])} fixturedir`) 12 | return reject(1) 13 | } 14 | var fixturedata = generateFromDir(fixturedir) 15 | console.log(fixturedata) 16 | return resolve() 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /tacks.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var fs = require('fs') 3 | 4 | module.exports = Tacks 5 | 6 | Tacks.File = require('./file.js') 7 | Tacks.Dir = require('./dir.js') 8 | Tacks.Symlink = require('./symlink.js') 9 | 10 | function Tacks (fixture) { 11 | this.fixture = fixture 12 | fixture.computePath('/') 13 | } 14 | Tacks.prototype = {} 15 | 16 | Tacks.prototype.create = function (location) { 17 | this.fixture.create(location) 18 | } 19 | 20 | Tacks.prototype.remove = function (location) { 21 | this.fixture.remove(location) 22 | } 23 | 24 | Tacks.prototype.toSource = function () { 25 | return 'new Tacks(\n' + 26 | this.fixture.toSource().replace(/(^|\n)/g, '$1 ') + '\n)' 27 | } 28 | -------------------------------------------------------------------------------- /entry.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var path = require('path') 3 | var rimraf = require('rimraf') 4 | module.exports = Entry 5 | 6 | function Entry (type, contents) { 7 | this.type = type 8 | this.contents = contents 9 | this.path = null 10 | } 11 | Entry.prototype = {} 12 | 13 | Entry.prototype.forContents = function (cb) { 14 | cb.call(this, this.contents) 15 | } 16 | 17 | Entry.prototype.computePath = function (entitypath) { 18 | this.path = path.relative('/', entitypath) 19 | } 20 | 21 | Entry.prototype.create = function (where) { 22 | throw new Error("Don't know how to create " + this.constructor.name + " at " + where) 23 | } 24 | 25 | Entry.prototype.remove = function (where) { 26 | rimraf.sync(where) 27 | } 28 | -------------------------------------------------------------------------------- /symlink.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var path = require('path') 3 | var fs = require('fs') 4 | var inherits = require('util').inherits 5 | var Entry = require('./entry') 6 | var asLiteral = require('./as-literal.js') 7 | 8 | module.exports = Symlink 9 | 10 | function Symlink (dest) { 11 | if (this == null) return new Symlink(dest) 12 | if (dest == null || dest === '') throw new Error('Symlinks must have a destination') 13 | Entry.call(this, 'symlink', dest) 14 | } 15 | inherits(Symlink, Entry) 16 | 17 | Symlink.prototype.create = function (where) { 18 | var filepath = path.resolve(where, this.path) 19 | var dest = this.contents 20 | if (dest[0] === '/') { 21 | dest = path.resolve(where, dest.slice(1)) 22 | } else if (/^\w:[\\/]/.test(dest)) { 23 | dest = path.resolve(where, dest.slice(3)) 24 | } 25 | try { 26 | fs.symlinkSync(dest, filepath, 'directory') 27 | } catch (_) { 28 | fs.symlinkSync(dest, filepath, 'junction') 29 | } 30 | } 31 | 32 | Symlink.prototype.toSource = function () { 33 | return 'Symlink(' + asLiteral(this.contents) + ')' 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tacks", 3 | "version": "1.3.0", 4 | "description": "Generate fixture modules from folders", 5 | "main": "index.js", 6 | "bin": { 7 | "tacks": "cmdline.js" 8 | }, 9 | "scripts": { 10 | "test": "tap test/*.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/iarna/tacks" 15 | }, 16 | "keywords": [], 17 | "author": "Rebecca Turner (http://re-becca.org/)", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/iarna/tacks/issues" 21 | }, 22 | "homepage": "https://github.com/iarna/tacks", 23 | "dependencies": { 24 | "@iarna/cli": "^2.0.0", 25 | "mkdirp": "^0.5.1", 26 | "rimraf": "^2.6.2" 27 | }, 28 | "bundleDependencies": [ 29 | "@iarna/cli", 30 | "mkdirp", 31 | "rimraf" 32 | ], 33 | "devDependencies": { 34 | "tap": "^12.0.1" 35 | }, 36 | "files": [ 37 | "as-literal.js", 38 | "as-object-key.js", 39 | "cmdline.js", 40 | "dir.js", 41 | "entry.js", 42 | "file.js", 43 | "generate-from-dir.js", 44 | "index.js", 45 | "load-from-dir.js", 46 | "symlink.js", 47 | "tacks.js", 48 | "tap.js" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # tacks 2 | 3 | ## v1.3.0 2019-01-25 4 | 5 | * Include undocumented, cut-rate tap integration. 6 | 7 | ## v1.2.7 2018-06-08 8 | 9 | * Dep updates and faster to install cli 10 | 11 | ## v1.2.6 2017-02-20 12 | 13 | * Sometimes links are created as symlinks, sometimes as junctions. Make the result on read 14 | be consistent regardless of source. 15 | 16 | ## v1.2.5 2017-02-20 17 | 18 | * MORE consistently use unix-style path separators 19 | * Create symlinks if possible, use junctions as fallback only 20 | 21 | ## v1.2.4 2017-02-17 22 | 23 | * Don't consider the final slash meaningful when comparing path names for equality. 24 | 25 | ## v1.2.3 2017-02-17 26 | 27 | * Strip off Windows drive letter absolutes when creating symlinks just as we do 28 | with `/`. 29 | * Consistently use unix-style path separators on Windows. 30 | 31 | ## v1.2.2 2016-09-19 32 | 33 | * Added repo info to package.json. Thank you @watilde! 34 | 35 | ## v1.2.1 2016-04-22 36 | 37 | * Lots of internal-only refactoring/rewriting to simplify future additions. 38 | 39 | ## v1.2.0 2016-04-20 40 | 41 | * Add tap comparer for tacks objects 42 | 43 | ## v1.1.0 2016-04-19 44 | 45 | * Add new Symlink entry type 46 | 47 | ## v1.0.0 2016-01-26 48 | 49 | * Initial release 50 | -------------------------------------------------------------------------------- /dir.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var path = require('path') 3 | var mkdirp = require('mkdirp') 4 | var inherits = require('util').inherits 5 | var Entry = require('./entry') 6 | var asObjectKey = require('./as-object-key.js') 7 | 8 | module.exports = Dir 9 | 10 | function Dir (contents) { 11 | if (this == null) return new Dir(contents) 12 | Entry.call(this, 'dir', contents || {}) 13 | } 14 | inherits(Dir, Entry) 15 | 16 | Dir.prototype.forContents = function (cb) { 17 | var contentNames = Object.keys(this.contents) 18 | for (var ii in contentNames) { 19 | var name = contentNames[ii] 20 | cb.call(this, this.contents[name], name, ii, contentNames) 21 | } 22 | } 23 | 24 | Dir.prototype.computePath = function (entitypath) { 25 | Entry.prototype.computePath.call(this, entitypath) 26 | 27 | this.forContents(function (content, name) { 28 | content.computePath(path.join(entitypath, name)) 29 | }) 30 | } 31 | 32 | Dir.prototype.create = function (where) { 33 | var subdirpath = path.resolve(where, this.path) 34 | mkdirp.sync(subdirpath) 35 | 36 | this.forContents(function (content) { 37 | content.create(where) 38 | }) 39 | } 40 | 41 | Dir.prototype.remove = function (where) { 42 | this.forContents(function (content) { 43 | content.remove(where) 44 | }) 45 | 46 | Entry.prototype.remove.call(this, where) 47 | } 48 | 49 | Dir.prototype.toSource = function () { 50 | var output = 'Dir({\n' 51 | this.forContents(function (content, filename, ii, keys) { 52 | var key = asObjectKey(filename) 53 | var value = content.toSource() 54 | output += ' ' + key + ': ' + value.replace(/(\n)(.)/mg, '$1 $2') 55 | if (ii < keys.length - 1) output += ',' 56 | output += '\n' 57 | }) 58 | return output + '})' 59 | } 60 | -------------------------------------------------------------------------------- /tap.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | exports.areTheSame = function (tap, actual, expected, msg) { 3 | return tap.test(msg, function (t) { 4 | compare(t, '/', actual.fixture, expected.fixture) 5 | t.done() 6 | }) 7 | } 8 | 9 | function join (p1, p2) { 10 | if (p1 === '/') return p1 + p2 11 | return p1 + '/' + p2 12 | } 13 | 14 | function compare (t, path, actual, expected) { 15 | t.is(actual.type, expected.type, path + ': type') 16 | if (actual.type !== expected.type) return 17 | if (expected.type === 'dir') { 18 | return compareDir(t, path, actual, expected) 19 | } else if (expected.type === 'file') { 20 | return compareFile(t, path, actual, expected) 21 | } else if (expected.type === 'symlink') { 22 | return compareSymlink(t, path, actual, expected) 23 | } else { 24 | throw new Error('what? ' + expected.type) 25 | } 26 | } 27 | 28 | function compareDir (t, path, actual, expected) { 29 | expected.forContents(function (expectedContent, filename) { 30 | if (!actual.contents[filename]) { 31 | t.fail(join(path, filename) + ' missing file') 32 | return 33 | } 34 | compare(t, join(path, filename), actual.contents[filename], expectedContent) 35 | }) 36 | actual.forContents(function (_, filename) { 37 | if (!expected.contents[filename]) { 38 | t.fail(join(path, filename) + ' extraneous file') 39 | } 40 | }) 41 | } 42 | 43 | function compareFile (t, path, actual, expected) { 44 | if (Buffer.isBuffer(expected.contents)) { 45 | t.same(Buffer(actual.contents), expected.contents, path + ': file buffer content') 46 | } else { 47 | t.is(actual.contents.toString(), expected.contents, path + ': file string content') 48 | } 49 | } 50 | 51 | function compareSymlink(t, path, actual, expected) { 52 | t.is(actual.contents, expected.contents, path + ': symlink destination') 53 | } 54 | -------------------------------------------------------------------------------- /as-literal.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = asLiteral 3 | 4 | var asObjectKey = require('./as-object-key.js') 5 | 6 | function asLiteral (thing) { 7 | if (thing === null) return 'null' 8 | if (thing == null) return 'undefined' 9 | if (typeof thing === 'boolean' || typeof thing === 'number') { 10 | return thing.toString() 11 | } else if (typeof thing === 'string') { 12 | return asStringLiteral(thing) 13 | } else if (thing instanceof Array) { 14 | return asArrayLiteral(thing) 15 | } else { 16 | return asObjectLiteral(thing) 17 | } 18 | } 19 | 20 | function asStringLiteral (thing) { 21 | var str = thing.toString() 22 | .replace(/\\/g, '\\\\') 23 | .replace(/[\0]/g, '\\0') 24 | .replace(/[\b]/g, '\\b') 25 | .replace(/[\f]/g, '\\f') 26 | .replace(/[\n]/g, '\\n') 27 | .replace(/[\r]/g, '\\r') 28 | .replace(/[\t]/g, '\\t') 29 | .replace(/[\v]/g, '\\v') 30 | if (/'/.test(str) && !/"/.test(str)) { 31 | return '"' + str + '"' 32 | } else { 33 | return "'" + str.replace(/'/g, "\\'") + "'" 34 | } 35 | } 36 | 37 | function asArrayLiteral (thing) { 38 | if (!thing.length) return '[ ]' 39 | var arr = '[\n' 40 | function arrayItem (item) { 41 | return asLiteral(item).replace(/\n(.*)(?=\n)/g, '\n $1') 42 | } 43 | arr += arrayItem(thing.shift()) 44 | thing.forEach(function (item) { 45 | arr += ',\n' + arrayItem(item) 46 | }) 47 | arr += '\n]' 48 | return arr 49 | } 50 | 51 | 52 | function asObjectLiteral (thing) { 53 | var keys = Object.keys(thing) 54 | if (!keys.length) return '{ }' 55 | var obj = '{\n' 56 | function objectValue (key) { 57 | return asObjectKey(key) + ': ' + asLiteral(thing[key]).replace(/\n(.*)(?=\n)/g, '\n $1') 58 | } 59 | obj += objectValue(keys.shift()) 60 | keys.forEach(function (key) { 61 | obj += ',\n' + objectValue(key) 62 | }) 63 | obj += '\n}' 64 | return obj 65 | } 66 | -------------------------------------------------------------------------------- /load-from-dir.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var path = require('path') 3 | var fs = require('fs') 4 | var Tacks = require('./tacks.js') 5 | var File = Tacks.File 6 | var Dir = Tacks.Dir 7 | var Symlink = Tacks.Symlink 8 | 9 | module.exports = function (dir) { 10 | return new Tacks(loadFromDir(dir)) 11 | } 12 | 13 | function fromJSON (str) { 14 | try { 15 | return JSON.parse(str) 16 | } catch (ex) { 17 | return 18 | } 19 | } 20 | 21 | function loadFromDir (dir, top) { 22 | if (!top) top = dir 23 | var dirInfo = {} 24 | fs.readdirSync(dir).forEach(function (filename) { 25 | if (filename === '.git') return 26 | var filepath = path.join(dir, filename) 27 | var fileinfo = fs.lstatSync(filepath) 28 | if (fileinfo.isSymbolicLink()) { 29 | var dest = fs.readlinkSync(filepath).replace(/[\\/]$/, '') 30 | var absDest = path.resolve(path.dirname(filepath), dest) 31 | var relativeDest = path.relative(path.dirname(filepath), absDest) 32 | // if we're two or more levels up, plot relative to the top level instead of 33 | // the link point 34 | if (/^\.\.[/\\]\.\.[/\\]/.test(relativeDest.slice(0,6))) { 35 | dest = '/' + path.relative(top, absDest) 36 | } else { 37 | dest = relativeDest 38 | } 39 | dirInfo[filename] = Symlink(dest.replace(/\\/g, '/')) 40 | } else if (fileinfo.isDirectory()) { 41 | dirInfo[filename] = loadFromDir(filepath, top) 42 | } else { 43 | var content = fs.readFileSync(filepath) 44 | var contentStr = content.toString('utf8') 45 | var contentJSON = fromJSON(contentStr) 46 | if (contentJSON !== undefined) { 47 | dirInfo[filename] = File(contentJSON) 48 | } else if (/[^\-\w\s~`!@#$%^&*()_=+[\]{}|\\;:'",./<>?]/.test(contentStr)) { 49 | dirInfo[filename] = File(content) 50 | } else { 51 | dirInfo[filename] = File(contentStr) 52 | } 53 | } 54 | }) 55 | return Dir(dirInfo) 56 | } 57 | -------------------------------------------------------------------------------- /file.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var path = require('path') 3 | var fs = require('fs') 4 | var inherits = require('util').inherits 5 | var Entry = require('./entry') 6 | var asLiteral = require('./as-literal.js') 7 | 8 | module.exports = File 9 | 10 | function File (contents) { 11 | if (this == null) return new File(contents) 12 | this.type = 'file' 13 | if (typeof contents === 'object' && !Buffer.isBuffer(contents)) { 14 | contents = JSON.stringify(contents) 15 | } 16 | Entry.call(this, 'file', contents) 17 | } 18 | inherits(File, Entry) 19 | 20 | File.prototype.create = function (where) { 21 | fs.writeFileSync(path.resolve(where, this.path), this.contents) 22 | } 23 | 24 | function tryJSON (str) { 25 | try { 26 | return JSON.parse(str) 27 | } catch (ex) { 28 | return 29 | } 30 | } 31 | 32 | File.prototype.toSource = function () { 33 | if (this.contents.length === 0) return "File('')" 34 | var output = 'File(' 35 | var fromJson = tryJSON(this.contents) 36 | if (fromJson != null) { 37 | var jsonStr = asLiteral(fromJson) 38 | if (/^[\[{]/.test(jsonStr)) { 39 | output += jsonStr.replace(/\n/g, '\n ') 40 | .replace(/[ ]{2}([}\]])$/, '$1)') 41 | } else { 42 | output += jsonStr + '\n )' 43 | } 44 | } else if (/[^\-\w\s~`!@#$%^&*()_=+[\]{}|\\;:'",./<>?]/.test(this.contents.toString())) { 45 | output += outputAsBuffer(this.contents) 46 | .replace(/[)]$/, '\n))') 47 | } else { 48 | output += outputAsText(this.contents) + 49 | '\n)' 50 | } 51 | return output 52 | } 53 | 54 | function outputAsText (content) { 55 | content = content.toString('utf8') 56 | var endsInNewLine = /\n$/.test(content) 57 | var lines = content.split(/\n/).map(function (line) { return line + '\n' }) 58 | if (endsInNewLine) lines.pop() 59 | var output = '\n ' + asLiteral(lines.shift()) 60 | lines.forEach(function (line) { 61 | output += ' +\n ' + asLiteral(line) 62 | }) 63 | return output 64 | } 65 | 66 | function outputAsBuffer (content) { 67 | var chunks = content.toString('hex').match(/.{1,60}/g) 68 | var output = 'Buffer.from(\n' 69 | output += " '" + chunks.shift() + "'" 70 | chunks.forEach(function (chunk) { 71 | output += ' +\n ' + "'" + chunk + "'" 72 | }) 73 | output += ",\n 'hex')" 74 | return output 75 | } 76 | -------------------------------------------------------------------------------- /test/functional.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var tap = require('tap') 4 | var test = require('tap').test 5 | var rimraf = require('rimraf') 6 | var loadFromDir = require('../load-from-dir.js') 7 | var generateFromDir = require('../generate-from-dir.js') 8 | var tacksAreTheSame = require('../tap.js').areTheSame 9 | var Tacks = require('../index.js') 10 | var File = Tacks.File 11 | var Dir = Tacks.Dir 12 | var Symlink = Tacks.Symlink 13 | 14 | var testroot = path.join(__dirname, path.basename(__filename, '.js')) 15 | var testdir = path.join(testroot, 'example') 16 | var testmodule = path.join(testroot, 'example.js') 17 | 18 | var fixture = new Tacks( 19 | Dir({ 20 | 'a': Dir({ 21 | 'b': Dir({ 22 | 'c': Dir({ 23 | 'foo.txt': File(''), 24 | 'bar.txt': Symlink('foo.txt'), 25 | 'ascii.txt': Symlink('/ascii.txt') 26 | }) 27 | }) 28 | }), 29 | 'ascii.txt': File( 30 | 'abc\n' 31 | ), 32 | 'foo': Dir({ 33 | 'foo.txt': Symlink('../a/b/c/foo.txt') 34 | }), 35 | 'binary.gz': File(Buffer.from( 36 | '1f8b0800d063115700034b4c4ae602004e81884704000000', 37 | 'hex' 38 | )), 39 | 'empty.txt': File(''), 40 | 'example.json': File({ 41 | 'a': true, 42 | 'b': 23, 43 | 'c': 'xyzzy', 44 | 'd': [], 45 | 'e': {}, 46 | 'f': null, 47 | 'complex': { 48 | 'xyz': [1, 2, 3, { abc: 'def' }], 49 | '123': false 50 | } 51 | }), 52 | 'x': Dir({ 53 | 'y': Dir({ 54 | 'z': Dir({ 55 | }) 56 | }) 57 | }) 58 | }) 59 | ) 60 | 61 | function setup () { 62 | fixture.create(testdir) 63 | } 64 | 65 | function cleanup () { 66 | fixture.remove(testdir) 67 | } 68 | 69 | test('setup', function (t) { 70 | rimraf.sync(testroot) 71 | setup() 72 | t.done() 73 | }) 74 | 75 | test('loadFromDir', function (t) { 76 | var model = loadFromDir(testdir) 77 | return tacksAreTheSame(t, model, fixture, 'loadFromDir') 78 | }) 79 | 80 | test('generateFromDir', function (t) { 81 | var js = generateFromDir(testdir) 82 | fs.writeFileSync(testmodule, js.replace(/'tacks'/g, "'../../index.js'")) 83 | var modelFromModule = require(testmodule) 84 | return tacksAreTheSame(t, modelFromModule, fixture, 'generateFromDir') 85 | }).catch(test.throws) 86 | 87 | test('cleanup', function (t) { 88 | cleanup() 89 | try { 90 | fs.statSync(testdir) 91 | t.fail(testdir + ' should not exist') 92 | } catch (ex) { 93 | t.pass(testdir + ' should not exist') 94 | } 95 | rimraf.sync(testroot) 96 | t.done() 97 | }) 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## tacks 2 | 3 | Generate fixture modules from folders 4 | 5 | ### USAGE 6 | 7 | Generate a fixture from a folder on disk: 8 | 9 | ```console 10 | tacks /path/to/fixture/example > example.js 11 | ``` 12 | 13 | Create and destroy the fixture from your tests: 14 | 15 | ```js 16 | var Tacks = require('tacks') 17 | var Dir = Tacks.Dir 18 | var File = Tacks.File 19 | var Symlink = Tacks.Symlink 20 | 21 | // I like my fixture paths to match my test filename: 22 | var fixturepath = path.join(__dirname, path.basename(__filename, '.js')) 23 | 24 | var example = require('./example.js') 25 | example.create(fixturepath) 26 | … 27 | example.remove(fixturepath) 28 | ``` 29 | 30 | Or create your own fixture inline: 31 | ```js 32 | var example = new Tacks(Dir({ 33 | 'package.json': File({ 34 | name: 'example', 35 | version: '1.0.0' 36 | }) 37 | })) 38 | example.create(fixturepath) 39 | … 40 | example.remove(fixturepath) 41 | ``` 42 | 43 | ### STATUS 44 | 45 | This is very much a "release early" type release. Still very much in 46 | progress, but being used. 47 | 48 | ### CLASSES 49 | 50 | These are used in the generated code. It's totally legit to write them directly though. 51 | 52 | #### Consturctor 53 | 54 | ```js 55 | var fixture = new Tacks(Dir({ 56 | 'package.json': File({ 57 | name: 'example', 58 | version: '1.0.0' 59 | }) 60 | })) 61 | ``` 62 | 63 | Create a new fixture object based on a `Dir` object, see below. 64 | 65 | #### Create Fixture On Disk 66 | 67 | ```js 68 | fixture.create('/path/to/fixture') 69 | ``` 70 | 71 | Take the directory and files described by the fixture and create it in `/path/to/fixture` 72 | 73 | #### Remove Fixture From Disk 74 | 75 | ```js 76 | fixture.remove('/path/to/fixture') 77 | ``` 78 | 79 | Cleanup a fixture we installed in `/path/to/fixture`. 80 | 81 | #### Add Directory 82 | 83 | ```js 84 | var Dir = Tacks.Dir 85 | var mydir = Tacks.Dir(dirspec) 86 | ``` 87 | 88 | Creates a new `Dir` object for consumption by `new Tacks`. `dirspec` is a 89 | object whose properties are the names of files in a directory and whose 90 | values are either `File` objects, `Dir` objects or `Symlink` objects. 91 | 92 | #### Add File 93 | 94 | ```js 95 | var File = Tacks.File 96 | var myfile = Tacks.File(filespec) 97 | ``` 98 | 99 | Creates a new `File` object for use in `Dir` objects. `filespec` can be 100 | either a `String`, a `Buffer` or an `Object`. In the last case, it 101 | will be stringified with `JSON.stringify` before writing it to disk 102 | 103 | #### Add Symlink 104 | 105 | ```js 106 | var Symlink = Tacks.Symlink 107 | var mysymlink = Tacks.Symlink(destination) 108 | ``` 109 | 110 | Creates a new `Symlink` object for use in `Dir` objects. `destination` should 111 | either be relative to where the symlink is being created, or absolute relative 112 | to the root of the fixture. That is, `Tacks.Symlink('/')` will create a symlink 113 | pointing at the fixture root. 114 | 115 | #### Generate Fixture Object From Directory 116 | 117 | ```js 118 | var loadFromDir = require('tacks/load-from-dir') 119 | var onDisk = loadFromDir('tests/example') 120 | ``` 121 | The value returned is a `Tacks` object that you can call `create` or 122 | `remove` on. It's also handy for using in tests use compare an in 123 | memory tacks fixture to whatever ended up on disk. 124 | 125 | #### Assert Two Fixtures The Same With node-tap 126 | 127 | ```js 128 | var test = require('tap').test 129 | var tacksAreTheSame = require('tacks/tap').areTheSame 130 | test('example', function (t) { 131 | return tacksAreTheSame(t, actual, expected, 'got the expected results') 132 | }) 133 | ``` 134 | The `tacks/tap` submodule is the start of tap assertions for comparing fixtures. 135 | 136 | `areTheSame` creates a subtest, and inside that subtest runs a bunch of 137 | assertions comparing the contents of the two models. It's smart enough to 138 | consider `tacks` equivalent things equal, eg strings & buffers with the same 139 | content. 140 | 141 | Because it creates a subtest, it's async, it returns the subtest (which is 142 | also a promise) so you can either return it yourself and your test will 143 | complete when it does, or do something like: 144 | 145 | ```js 146 | tacksAreTheSame(t, actual, expected, 'got the expected results').then(t.done) 147 | ``` 148 | 149 | or 150 | 151 | ```js 152 | tacksAreTheSame(t, actual, expected, 'got the expected results').then(function () { 153 | … more tests … 154 | t.done() 155 | }) 156 | ``` 157 | 158 | #### Geneate JavaScript From Directory 159 | 160 | ```js 161 | var generateFromDir = require('tacks/generate-from-dir') 162 | var fixturestr = Tacks.generateFromDir(dir) 163 | ``` 164 | 165 | This is what's used by the commandline– it generates javascript as a string 166 | from a directory on disk. It works hard to produce something that looks 167 | like it might have been typed by a human– It translates JSON on disk into 168 | object literals. And it doesn't quote property names in object literals 169 | unless it has to. It uses single quotes when it can. It double quotes when 170 | it has to, and escapes when it has no other choice. It includs plain text 171 | as strings concatenated one per line. For everything else it makes Buffer 172 | objects using hex encoded strings as input. 173 | 174 | ### WANT TO HAVES 175 | 176 | These are things I'll do sooner or late myself. 177 | 178 | * Include adding a `.mockFs('/tmp/fixture/path/')` function which returns a 179 | patched version of `fs` that, for attempts to read from `/tmp/fixture/path` 180 | returns data from the in memory fixture instead of looking at the 181 | filesystem. For injection into tested modules with something like 182 | `require-inject`. 183 | 184 | ### NICE TO HAVES 185 | 186 | I'd love to see these, but I may never get time to do them myself. If 187 | someone else did them though… 188 | 189 | * Having some way to control the formatting of the generated output would be 190 | nice for folks who don't use `standard`… eg, semicolons, indentation, 191 | default quoting. The right answer might be to generate AST objects for 192 | use by an existing formatter. Relatedly, it'd be nice to have some 193 | standard extension method for the generated sourcecode. Right now I make 194 | use of it just by concattenating source code. 195 | 196 | --------------------------------------------------------------------------------