From 8f754ac11f34ad3a2c78dbc961c69280eea3cef7 Mon Sep 17 00:00:00 2001 From: Mark Griffiths Date: Sat, 1 Oct 2016 15:55:18 +0100 Subject: [PATCH] Initial commit --- .codeclimate.yml | 20 ++ .editorconfig | 54 +++ .sublime-gulp.cache | 1 + .travis.yml | 14 + bin/palette2oco.js | 350 ++++++++++++++++++++ gulpfile.js | 46 +++ inch.json | 7 + index-es.js | 206 ++++++++++++ index.js | 214 ++++++++++++ license | 24 ++ package.json | 68 ++++ readme.md | 37 +++ src/classes/reader.js | 233 +++++++++++++ src/cli.js | 157 +++++++++ src/index.js | 20 ++ src/writer.js | 17 + test/fixtures/config/.eslintignore | 1 + test/fixtures/config/.eslintrc | 213 ++++++++++++ test/fixtures/in/ase/ps-test.ase | Bin 0 -> 6804 bytes test/fixtures/in/ase/test.ase | Bin 0 -> 312 bytes test/fixtures/in/json/test.json | 47 +++ test/fixtures/in/oco/test.oco | 7 + test/fixtures/in/sippalette/test.sippalette | 47 +++ test/fixtures/out/ase.oco | 11 + test/fixtures/out/json.oco | 11 + test/fixtures/out/oco.oco | 11 + test/fixtures/out/ps-test.oco | 126 +++++++ test/fixtures/out/sippalette.oco | 11 + test/palettes.js | 48 +++ 29 files changed, 2001 insertions(+) create mode 100644 .codeclimate.yml create mode 100644 .editorconfig create mode 100644 .sublime-gulp.cache create mode 100644 .travis.yml create mode 100755 bin/palette2oco.js create mode 100644 gulpfile.js create mode 100644 inch.json create mode 100644 index-es.js create mode 100644 index.js create mode 100644 license create mode 100644 package.json create mode 100644 readme.md create mode 100644 src/classes/reader.js create mode 100644 src/cli.js create mode 100644 src/index.js create mode 100644 src/writer.js create mode 100644 test/fixtures/config/.eslintignore create mode 100644 test/fixtures/config/.eslintrc create mode 100644 test/fixtures/in/ase/ps-test.ase create mode 100644 test/fixtures/in/ase/test.ase create mode 100644 test/fixtures/in/json/test.json create mode 100644 test/fixtures/in/oco/test.oco create mode 100644 test/fixtures/in/sippalette/test.sippalette create mode 100644 test/fixtures/out/ase.oco create mode 100644 test/fixtures/out/json.oco create mode 100644 test/fixtures/out/oco.oco create mode 100644 test/fixtures/out/ps-test.oco create mode 100644 test/fixtures/out/sippalette.oco create mode 100644 test/palettes.js diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..63a5014 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,20 @@ +--- +engines: + duplication: + enabled: true + config: + languages: + - javascript + eslint: + enabled: true + config: + config: test/fixtures/config/.eslintrc + fixme: + enabled: true +ratings: + paths: + - "src/**.js" +exclude_paths: +- test/ +- lib/ +- index*.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d74551e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,54 @@ +root = true + +# Unix-style newlines with a newline ending every file +# with no excess whitespace ending lines, no indentation preference. +[*] +charset = utf8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +# 2 spaces for list indenting, 2 'tabs' for code blocks if not fenced. +# Don't trim line whitespace as it's significant in markdown. +[*.md] +trim_trailing_whitespace = false +indent_style = space +indent_size = 2 + +[*.{yaml,yml}] +indent_style = space +indent_size = 2 + +# A personal preference for hybrid indentation in sources I generate: +# Tabs for indent, spaces for alignment. It's the typeographer in me. +# Tab width of 3 is my 'Goldilocks' zone of indentation and until I can grep +# indention spaces explicitly, Tabs it will be. Opinionated? Absolutely! +[*.{coffee,js,JavaScript,json,es6}] +indent_style = tab +indent_size = 3 + +[*.{py,go}] +indent_style = tab +indent_size = 3 + +[*.{c,h,ino,cpp}] +indent_style = tab +indent_size = 3 + +[*.{fish,sh,pl}] +indent_style = tab +indent_size = 4 + +# 2 space indents when required. rc's should really be in package.json +[package.json] +insert_final_newline = false +indent_style = space +indent_size = 2 + +[.eslintrc.{js,json}] +indent_style = space +indent_size = 2 + +[{.esformatter,.jshintrc}] +indent_style = space +indent_size = 2 diff --git a/.sublime-gulp.cache b/.sublime-gulp.cache new file mode 100644 index 0000000..fbcde15 --- /dev/null +++ b/.sublime-gulp.cache @@ -0,0 +1 @@ +{"/Users/mark/Projects/TheBespokePixel/Node/palette2oco/gulpfile.js":{"sha1":"b65bfec768682873a78d31527cd6d4d9dbb75185","tasks":{"reset":{"name":"reset","dependencies":""},"bump":{"name":"bump","dependencies":""},"commit":{"name":"commit","dependencies":""},"push":{"name":"push","dependencies":" "},"push-force":{"name":"push-force","dependencies":" "},"push-tags":{"name":"push-tags","dependencies":" "},"backup":{"name":"backup","dependencies":"push "},"short-circuit":{"name":"short-circuit","dependencies":""},"test":{"name":"test","dependencies":"xo ava"},"publish":{"name":"publish","dependencies":""},"version-release":{"name":"version-release","dependencies":""},"start-release":{"name":"start-release","dependencies":"reset master"},"test-release":{"name":"test-release","dependencies":"test"},"finish-release":{"name":"finish-release","dependencies":"push-force push-tags"},"post-flow-release-start":{"name":"post-flow-release-start","dependencies":"start-release version-release"},"post-flow-release-finish":{"name":"post-flow-release-finish","dependencies":"test-release publish finish-release"},"filter-flow-release-start-version":{"name":"filter-flow-release-start-version","dependencies":""},"filter-flow-release-finish-tag-message":{"name":"filter-flow-release-finish-tag-message","dependencies":""},"bundle":{"name":"bundle","dependencies":" "},"master":{"name":"master","dependencies":" "},"clean":{"name":"clean","dependencies":""},"ava":{"name":"ava","dependencies":""},"xo":{"name":"xo","dependencies":""},"default":{"name":"default","dependencies":"bump bundle"}}}} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c203e01 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +language: node_js +node_js: + - 4 + - 5 + - stable +sudo: false +before_install: npm i -g npm@latest +notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/d0418713e51ac049564a + on_success: always + on_failure: change + on_start: never diff --git a/bin/palette2oco.js b/bin/palette2oco.js new file mode 100755 index 0000000..a111606 --- /dev/null +++ b/bin/palette2oco.js @@ -0,0 +1,350 @@ +#! /usr/bin/env node +'use strict' + +function _interopDefault(ex) { + return (ex && (typeof ex === 'object') && 'default' in ex) ? ex.default : ex +} + +var _initial = _interopDefault(require('lodash/initial')) +var _tail = _interopDefault(require('lodash/tail')) +var path = require('path') +var truwrap = _interopDefault(require('truwrap')) +var commonTags = require('common-tags') +var _thebespokepixel_string = require('@thebespokepixel/string') +var yargs = _interopDefault(require('yargs')) +var globby = _interopDefault(require('globby')) +var readPkg = _interopDefault(require('read-pkg')) +var updateNotifier = _interopDefault(require('update-notifier')) +var trucolor = _interopDefault(require('trucolor')) +var verbosity = require('verbosity') +var _isEqual = _interopDefault(require('lodash/isEqual')) +var fs = _interopDefault(require('fs')) +var promisify = _interopDefault(require('es6-promisify')) +var _thebespokepixel_ocoColorvalueEx = require('@thebespokepixel/oco-colorvalue-ex') +var oco = _interopDefault(require('opencolor')) +var ase = _interopDefault(require('ase-util')) + +const loader = promisify(fs.readFile) + +const supportedTypes = ['oco', 'json', 'sippalette', 'ase'] + +const fileFilter = new RegExp(`.(${supportedTypes.join('|')})$`) +const fileMatch = new RegExp(`(.*/)(.+?).(${supportedTypes.join('|')})$`) + +function createIdentity(rootPath) { + return function (path) { + const address = path.replace(rootPath, '').match(fileMatch) + return { + source: path, + name: address[2], + path: address[1].replace(/^\//, '').replace(/\//g, '.'), + type: address[3] + } + } +} + +function isPaletteJSON(datum) { + const tests = { + palette: { + name: typeof datum.name === 'string' && datum.name, + colors: Array.isArray(datum.colors) && datum.colors + }, + rgba: { + name: typeof datum.name === 'string' && datum.name, + red: datum.red >= 0.0 && datum.red <= 1.0 && datum.red, + green: datum.green >= 0.0 && datum.green <= 1.0 && datum.green, + blue: datum.blue >= 0.0 && datum.blue <= 1.0 && datum.blue, + alpha: datum.alpha >= 0.0 && datum.alpha <= 1.0 && datum.alpha + }, + rgbaInteger: { + name: typeof datum.name === 'string' && datum.name, + red: datum.red >= 0 && datum.red <= 255 && datum.red, + green: datum.green >= 0 && datum.green <= 255 && datum.green, + blue: datum.blue >= 0 && datum.blue <= 255 && datum.blue, + alpha: datum.alpha >= 0 && datum.alpha <= 255 && datum.alpha + } + } + return { + isPalette: _isEqual(datum, tests.palette), + isRGBA: _isEqual(datum, tests.rgba), + isIntegerRGBA: _isEqual(datum, tests.rgbaInteger) + } +} + +function loadOCO(identity) { + return loader(identity.source, 'utf8').then(oco.parse) +} + +function loadJSON(identity) { + return loader(identity.source, 'utf8').then(JSON.parse).then(palette => { + if (isPaletteJSON(palette).isPalette) { + console.debug(`JSON Palette: ${palette.name}`) + return new oco.Entry(identity.name, [_thebespokepixel_ocoColorvalueEx.OCOValueEX.generateOCO(palette.name, palette.colors.map(color => { + const paletteColor = isPaletteJSON(color) + switch (true) { + case paletteColor.isRGBA: + console.debug(`JSON Color (RGBA): ${color.name}`) + return _thebespokepixel_ocoColorvalueEx.fromPrecise(color) + case paletteColor.isIntegerRGBA: + console.debug(`JSON Color (Integer RGBA): ${color.name}`) + return _thebespokepixel_ocoColorvalueEx.fromBytes(color) + default: + throw new Error(`${color.name}.json is not a valid JSON color object`) + } + }))]) + } + throw new Error(`${identity.name}.json is not a valid palette`) + }) +} + +function loadASE(identity) { + function scan(node) { + return node.map(datum => { + switch (datum.type) { + case 'color': + switch (datum.color.model) { + case 'RGB': + console.debug(`ASE Color (RGB): ${datum.name}`) + return new _thebespokepixel_ocoColorvalueEx.OCOValueEX(datum.color.hex, datum.name) + case 'CMYK': + console.debug(`ASE Color (CMYK): ${datum.name}`) + return _thebespokepixel_ocoColorvalueEx.fromCMYK({ + name: datum.name, + cyan: datum.color.c, + magenta: datum.color.m, + yellow: datum.color.y, + black: datum.color.k + }) + case 'LAB': + console.debug(`ASE Color (Lab): ${datum.name}`) + return _thebespokepixel_ocoColorvalueEx.fromLab({ + name: datum.name, + L: datum.color.lightness, + a: datum.color.a, + b: datum.color.b + }) + case 'Gray': + console.debug(`ASE Color (Gray): ${datum.name}`) + return new _thebespokepixel_ocoColorvalueEx.OCOValueEX(datum.color.hex, datum.name) + default: + throw new Error(`${datum.color.model} is not a valid ASE color model`) + } + + case 'group': + console.debug(`ASE Group: ${datum.name}`) + return _thebespokepixel_ocoColorvalueEx.OCOValueEX.generateOCO(datum.name, scan(datum.entries)) + + default: + throw new Error(`${datum.type} is not a valid ASE data type`) + } + }) + } + + return loader(identity.source).then(ase.read).then(palette => { + if (Array.isArray(palette)) { + return palette.length === 1 ? new oco.Entry(identity.name, scan(palette)) : _thebespokepixel_ocoColorvalueEx.OCOValueEX.generateOCO(identity.name, scan(palette)) + } + throw new Error(`${identity.name}.ase is not a valid palette`) + }) +} + +function selectLoaderByIndentity(type) { + switch (type) { + case 'sippalette': + return loadJSON + case 'json': + return loadJSON + case 'ase': + return loadASE + case 'oco': + return loadOCO + default: + throw new Error(`${type} is not recognised`) + } +} + +class Reader { + constructor(source_) { + this.sourcePath = source_ + this.tree = new oco.Entry() + } + + pick(key_) { + return key_ ? this.tree.get(key_) : this.tree.root() + } + + transformColors(formats) { + this.tree.traverseTree('Color', color_ => { + const original = color_.get(0).identifiedValue.getOriginalInput() + color_.children = [] + + formats.forEach((format, index_) => { + const newFormat = new _thebespokepixel_ocoColorvalueEx.OCOValueEX(original, color_.name) + newFormat._format = format + + color_.addChild(new oco.ColorValue(format, newFormat.toString(format), newFormat), true, index_) + }) + }) + return this + } + + load(pathArray) { + return Promise.all(pathArray.filter(file => file.match(fileFilter)).map(createIdentity(this.sourcePath)).map(identity => selectLoaderByIndentity(identity.type)(identity).then(entry => { + entry.addMetadata({ + 'import/file/source': path.relative(process.cwd(), identity.source), + 'import/file/type': identity.type + }) + return entry + }).then(entry => this.tree.set(`${identity.path}${identity.name}`, entry)))).then(() => this) + } + + render(path) { + return oco.render(this.pick(path)) + } +} + +const writeFile = promisify(fs.writeFile) + +function writer(destination, oco) { + console.debug(`Writing oco file to ${destination}`) + return writeFile(destination, oco) +} + +const console = verbosity.createConsole({ + outStream: process.stderr +}) + +function paletteReader(pathArray) { + return new Reader(pathArray) +} + +function paletteWriter(palette, destination) { + return writer(palette, destination) +} + +const clr = trucolor.simplePalette() + +const _package = readPkg.sync(path.resolve(__dirname, '..')) + +const renderer = truwrap({ + outStream: process.stderr +}) + +const colorReplacer = new commonTags.TemplateTag(commonTags.replaceSubstitutionTransformer(/([a-zA-Z]+?)[:/|](.+)/, (match, colorName, content) => `${clr[colorName]}${content}${clr[colorName].out}`)) + +const title = _thebespokepixel_string.box(colorReplacer`${'title|palette2oco'}${`dim| │ v${_package.version}`}`, { + borderColor: 'red', + margin: { + top: 1 + }, + padding: { + bottom: 0, + top: 0, + left: 2, + right: 2 + } +}) + +const usage = commonTags.stripIndent(colorReplacer)`${title} + + Convert palette data from a variety of sources into Open Color .oco format. + + Allows structured directories of pallette data to be converted into nested oco palette data. + + Formats supported: + Sip (http://sipapp.io): Supports .sippalette and .json exports. + + Abobe Swatch Exchange (ASE): Full support of RGB, CMYK and Lab colorspaces. + + Vanilla JSON: File signature must match the following... + + { + "name" : "Palette name", + "colors" : [ + { + name: "Color name", + red: (0.0 - 1.0 | 0 - 255) + green: (0.0 - 1.0 | 0 - 255) + blue: (0.0 - 1.0 | 0 - 255) + alpha: (0.0 - 1.0 | 0 - 255) + } + ... + ] + } + + Usage: + ${'command|palette2oco'} ${'option|[options]'} ${'argument|sourceGlob'} ${'argument|outputFile'}` + +const epilogue = colorReplacer`${'green|© 2016'} ${'brightGreen|The Bespoke Pixel.'} ${'grey|Released under the MIT License.'}` + +yargs.strict().options({ + h: { + alias: 'help', + describe: 'Display help.' + }, + v: { + alias: 'version', + count: true, + describe: 'Print version to stdout. -vv Print name & version.' + }, + V: { + alias: 'verbose', + count: true, + describe: 'Be verbose. -VV Be loquacious.' + }, + color: { + describe: 'Force color output. Disable with --no-color' + } +}).wrap(renderer.getWidth()) + +const argv = yargs.argv + +if (!(process.env.USER === 'root' && process.env.SUDO_USER !== process.env.USER)) { + updateNotifier({ + pkg: _package + }).notify() +} + +if (argv.help) { + renderer.write(usage) + renderer.break(2) + renderer.write(yargs.getUsageInstance().help()) + renderer.break() + renderer.write(epilogue) + renderer.break(1) + process.exit(0) +} + +if (argv.version) { + const version = _package.buildNumber > 0 ? `${_package.version}-Δ${_package.buildNumber}` : `${_package.version}` + process.stdout.write(argv.version > 1 ? `${_package.name} v${version}` : version) + process.exit(0) +} + +if (argv.verbose) { + switch (argv.verbose) { + case 1: + console.verbosity(4) + console.log(`${clr.title}Verbose mode${clr.title.out}:`) + break + case 2: + console.verbosity(5) + console.log(`${clr.title}Extra-Verbose mode${clr.title.out}:`) + console.yargs(argv) + break + default: + console.verbosity(3) + } +} + +if (argv._.length > 1) { + const root = path.resolve() + const dest = path.resolve(_tail(argv._)[0]) + globby(_initial(argv._)).then(pathArray => paletteReader(root).load(pathArray)).then(pal => pal.render()).then(contents => paletteWriter(dest, contents)).catch(err => { + console.error(err) + process.exit(1) + }) +} else { + console.error('palette2oco needs at least a source and a destination.') + process.exit(1) +} diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..ce0f782 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,46 @@ +/* + * Gulp User Tasks + */ + +const gulp = require('gulp') +const cordial = require('@thebespokepixel/cordial')() + +// transpilation/formatting +gulp.task('bundle', cordial.macro({ + source: 'src/index.js' +}).basic()) + +gulp.task('master', cordial.macro({ + master: true, + source: 'src/index.js' +}).basic()) + +gulp.task('cli', gulp.series( + cordial.format({ + source: 'src/cli.js' + }).rollup.babel({ + banner: '#! /usr/bin/env node', + dest: 'bin/palette2oco.js' + }), + + cordial.shell().permissions({ + mode: '755', + dest: 'bin/palette2oco.js' + }) +)) + +// Hooks +gulp.task('start-release', gulp.series('reset', 'master', 'cli')) + +// Clean +gulp.task('clean', cordial.shell({ + source: ['npm-debug.log', './nyc_output', './test/coverage'] +}).trash()) + +// Tests +gulp.task('ava', cordial.test().ava(['test/*.js'])) +gulp.task('xo', cordial.test().xo(['src/**.js'])) +gulp.task('test', gulp.parallel('xo', 'ava')) + +// Default +gulp.task('default', gulp.series('bump', 'bundle', 'cli')) diff --git a/inch.json b/inch.json new file mode 100644 index 0000000..bca18ed --- /dev/null +++ b/inch.json @@ -0,0 +1,7 @@ +{ + "files": { + "included": [ + "src/**/*.js" + ] + } +} diff --git a/index-es.js b/index-es.js new file mode 100644 index 0000000..97f23d5 --- /dev/null +++ b/index-es.js @@ -0,0 +1,206 @@ +import { createConsole } from 'verbosity'; +import _isEqual from 'lodash/isEqual'; +import { relative } from 'path'; +import fs from 'fs'; +import promisify from 'es6-promisify'; +import { fromBytes, fromPrecise, OCOValueEX, fromLab, fromCMYK } from '@thebespokepixel/oco-colorvalue-ex'; +import oco from 'opencolor'; +import ase from 'ase-util'; + +const loader = promisify(fs.readFile); + +const supportedTypes = ['oco', 'json', 'sippalette', 'ase']; + +const fileFilter = new RegExp(`\.(${ supportedTypes.join('|') })$`); +const fileMatch = new RegExp(`(.*\/)(.+?).(${ supportedTypes.join('|') })$`); + +function createIdentity(rootPath) { + return function (path) { + const address = path.replace(rootPath, '').match(fileMatch); + return { + source: path, + name: address[2], + path: address[1].replace(/^\//, '').replace(/\//g, '.'), + type: address[3] + }; + }; +} + +function isPaletteJSON(datum) { + const tests = { + palette: { + name: typeof datum.name === 'string' && datum.name, + colors: Array.isArray(datum.colors) && datum.colors + }, + rgba: { + name: typeof datum.name === 'string' && datum.name, + red: datum.red >= 0.0 && datum.red <= 1.0 && datum.red, + green: datum.green >= 0.0 && datum.green <= 1.0 && datum.green, + blue: datum.blue >= 0.0 && datum.blue <= 1.0 && datum.blue, + alpha: datum.alpha >= 0.0 && datum.alpha <= 1.0 && datum.alpha + }, + rgbaInteger: { + name: typeof datum.name === 'string' && datum.name, + red: datum.red >= 0 && datum.red <= 255 && datum.red, + green: datum.green >= 0 && datum.green <= 255 && datum.green, + blue: datum.blue >= 0 && datum.blue <= 255 && datum.blue, + alpha: datum.alpha >= 0 && datum.alpha <= 255 && datum.alpha + } + }; + return { + isPalette: _isEqual(datum, tests.palette), + isRGBA: _isEqual(datum, tests.rgba), + isIntegerRGBA: _isEqual(datum, tests.rgbaInteger) + }; +} + +function loadOCO(identity) { + return loader(identity.source, 'utf8').then(oco.parse); +} + +function loadJSON(identity) { + return loader(identity.source, 'utf8').then(JSON.parse).then(palette => { + if (isPaletteJSON(palette).isPalette) { + console.debug(`JSON Palette: ${ palette.name }`); + return new oco.Entry(identity.name, [OCOValueEX.generateOCO(palette.name, palette.colors.map(color => { + const paletteColor = isPaletteJSON(color); + switch (true) { + case paletteColor.isRGBA: + console.debug(`JSON Color (RGBA): ${ color.name }`); + return fromPrecise(color); + case paletteColor.isIntegerRGBA: + console.debug(`JSON Color (Integer RGBA): ${ color.name }`); + return fromBytes(color); + default: + throw new Error(`${ color.name }.json is not a valid JSON color object`); + } + }))]); + } + throw new Error(`${ identity.name }.json is not a valid palette`); + }); +} + +function loadASE(identity) { + function scan(node) { + return node.map(datum => { + switch (datum.type) { + case 'color': + switch (datum.color.model) { + case 'RGB': + console.debug(`ASE Color (RGB): ${ datum.name }`); + return new OCOValueEX(datum.color.hex, datum.name); + case 'CMYK': + console.debug(`ASE Color (CMYK): ${ datum.name }`); + return fromCMYK({ + name: datum.name, + cyan: datum.color.c, + magenta: datum.color.m, + yellow: datum.color.y, + black: datum.color.k + }); + case 'LAB': + console.debug(`ASE Color (Lab): ${ datum.name }`); + return fromLab({ + name: datum.name, + L: datum.color.lightness, + a: datum.color.a, + b: datum.color.b + }); + case 'Gray': + console.debug(`ASE Color (Gray): ${ datum.name }`); + return new OCOValueEX(datum.color.hex, datum.name); + default: + throw new Error(`${ datum.color.model } is not a valid ASE color model`); + } + + case 'group': + console.debug(`ASE Group: ${ datum.name }`); + return OCOValueEX.generateOCO(datum.name, scan(datum.entries)); + + default: + throw new Error(`${ datum.type } is not a valid ASE data type`); + } + }); + } + + return loader(identity.source).then(ase.read).then(palette => { + if (Array.isArray(palette)) { + return palette.length === 1 ? new oco.Entry(identity.name, scan(palette)) : OCOValueEX.generateOCO(identity.name, scan(palette)); + } + throw new Error(`${ identity.name }.ase is not a valid palette`); + }); +} + +function selectLoaderByIndentity(type) { + switch (type) { + case 'sippalette': + return loadJSON; + case 'json': + return loadJSON; + case 'ase': + return loadASE; + case 'oco': + return loadOCO; + default: + throw new Error(`${ type } is not recognised`); + } +} + +class Reader { + constructor(source_) { + this.sourcePath = source_; + this.tree = new oco.Entry(); + } + + pick(key_) { + return key_ ? this.tree.get(key_) : this.tree.root(); + } + + transformColors(formats) { + this.tree.traverseTree('Color', color_ => { + const original = color_.get(0).identifiedValue.getOriginalInput(); + color_.children = []; + + formats.forEach((format, index_) => { + const newFormat = new OCOValueEX(original, color_.name); + newFormat._format = format; + + color_.addChild(new oco.ColorValue(format, newFormat.toString(format), newFormat), true, index_); + }); + }); + return this; + } + + load(pathArray) { + return Promise.all(pathArray.filter(file => file.match(fileFilter)).map(createIdentity(this.sourcePath)).map(identity => selectLoaderByIndentity(identity.type)(identity).then(entry => { + entry.addMetadata({ + 'import/file/source': relative(process.cwd(), identity.source), + 'import/file/type': identity.type + }); + return entry; + }).then(entry => this.tree.set(`${ identity.path }${ identity.name }`, entry)))).then(() => this); + } + + render(path) { + return oco.render(this.pick(path)); + } +} + +const writeFile = promisify(fs.writeFile); + +function writer(destination, oco) { + console.debug(`Writing oco file to ${ destination }`); + return writeFile(destination, oco); +} + +const console = createConsole({ outStream: process.stderr }); + +function paletteReader(pathArray) { + return new Reader(pathArray); +} + +function paletteWriter(palette, destination) { + return writer(palette, destination); +} + +export { console, paletteReader, paletteWriter }; \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..d5a54d6 --- /dev/null +++ b/index.js @@ -0,0 +1,214 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } + +var verbosity = require('verbosity'); +var _isEqual = _interopDefault(require('lodash/isEqual')); +var path = require('path'); +var fs = _interopDefault(require('fs')); +var promisify = _interopDefault(require('es6-promisify')); +var _thebespokepixel_ocoColorvalueEx = require('@thebespokepixel/oco-colorvalue-ex'); +var oco = _interopDefault(require('opencolor')); +var ase = _interopDefault(require('ase-util')); + +const loader = promisify(fs.readFile); + +const supportedTypes = ['oco', 'json', 'sippalette', 'ase']; + +const fileFilter = new RegExp(`\.(${ supportedTypes.join('|') })$`); +const fileMatch = new RegExp(`(.*\/)(.+?).(${ supportedTypes.join('|') })$`); + +function createIdentity(rootPath) { + return function (path) { + const address = path.replace(rootPath, '').match(fileMatch); + return { + source: path, + name: address[2], + path: address[1].replace(/^\//, '').replace(/\//g, '.'), + type: address[3] + }; + }; +} + +function isPaletteJSON(datum) { + const tests = { + palette: { + name: typeof datum.name === 'string' && datum.name, + colors: Array.isArray(datum.colors) && datum.colors + }, + rgba: { + name: typeof datum.name === 'string' && datum.name, + red: datum.red >= 0.0 && datum.red <= 1.0 && datum.red, + green: datum.green >= 0.0 && datum.green <= 1.0 && datum.green, + blue: datum.blue >= 0.0 && datum.blue <= 1.0 && datum.blue, + alpha: datum.alpha >= 0.0 && datum.alpha <= 1.0 && datum.alpha + }, + rgbaInteger: { + name: typeof datum.name === 'string' && datum.name, + red: datum.red >= 0 && datum.red <= 255 && datum.red, + green: datum.green >= 0 && datum.green <= 255 && datum.green, + blue: datum.blue >= 0 && datum.blue <= 255 && datum.blue, + alpha: datum.alpha >= 0 && datum.alpha <= 255 && datum.alpha + } + }; + return { + isPalette: _isEqual(datum, tests.palette), + isRGBA: _isEqual(datum, tests.rgba), + isIntegerRGBA: _isEqual(datum, tests.rgbaInteger) + }; +} + +function loadOCO(identity) { + return loader(identity.source, 'utf8').then(oco.parse); +} + +function loadJSON(identity) { + return loader(identity.source, 'utf8').then(JSON.parse).then(palette => { + if (isPaletteJSON(palette).isPalette) { + console.debug(`JSON Palette: ${ palette.name }`); + return new oco.Entry(identity.name, [_thebespokepixel_ocoColorvalueEx.OCOValueEX.generateOCO(palette.name, palette.colors.map(color => { + const paletteColor = isPaletteJSON(color); + switch (true) { + case paletteColor.isRGBA: + console.debug(`JSON Color (RGBA): ${ color.name }`); + return _thebespokepixel_ocoColorvalueEx.fromPrecise(color); + case paletteColor.isIntegerRGBA: + console.debug(`JSON Color (Integer RGBA): ${ color.name }`); + return _thebespokepixel_ocoColorvalueEx.fromBytes(color); + default: + throw new Error(`${ color.name }.json is not a valid JSON color object`); + } + }))]); + } + throw new Error(`${ identity.name }.json is not a valid palette`); + }); +} + +function loadASE(identity) { + function scan(node) { + return node.map(datum => { + switch (datum.type) { + case 'color': + switch (datum.color.model) { + case 'RGB': + console.debug(`ASE Color (RGB): ${ datum.name }`); + return new _thebespokepixel_ocoColorvalueEx.OCOValueEX(datum.color.hex, datum.name); + case 'CMYK': + console.debug(`ASE Color (CMYK): ${ datum.name }`); + return _thebespokepixel_ocoColorvalueEx.fromCMYK({ + name: datum.name, + cyan: datum.color.c, + magenta: datum.color.m, + yellow: datum.color.y, + black: datum.color.k + }); + case 'LAB': + console.debug(`ASE Color (Lab): ${ datum.name }`); + return _thebespokepixel_ocoColorvalueEx.fromLab({ + name: datum.name, + L: datum.color.lightness, + a: datum.color.a, + b: datum.color.b + }); + case 'Gray': + console.debug(`ASE Color (Gray): ${ datum.name }`); + return new _thebespokepixel_ocoColorvalueEx.OCOValueEX(datum.color.hex, datum.name); + default: + throw new Error(`${ datum.color.model } is not a valid ASE color model`); + } + + case 'group': + console.debug(`ASE Group: ${ datum.name }`); + return _thebespokepixel_ocoColorvalueEx.OCOValueEX.generateOCO(datum.name, scan(datum.entries)); + + default: + throw new Error(`${ datum.type } is not a valid ASE data type`); + } + }); + } + + return loader(identity.source).then(ase.read).then(palette => { + if (Array.isArray(palette)) { + return palette.length === 1 ? new oco.Entry(identity.name, scan(palette)) : _thebespokepixel_ocoColorvalueEx.OCOValueEX.generateOCO(identity.name, scan(palette)); + } + throw new Error(`${ identity.name }.ase is not a valid palette`); + }); +} + +function selectLoaderByIndentity(type) { + switch (type) { + case 'sippalette': + return loadJSON; + case 'json': + return loadJSON; + case 'ase': + return loadASE; + case 'oco': + return loadOCO; + default: + throw new Error(`${ type } is not recognised`); + } +} + +class Reader { + constructor(source_) { + this.sourcePath = source_; + this.tree = new oco.Entry(); + } + + pick(key_) { + return key_ ? this.tree.get(key_) : this.tree.root(); + } + + transformColors(formats) { + this.tree.traverseTree('Color', color_ => { + const original = color_.get(0).identifiedValue.getOriginalInput(); + color_.children = []; + + formats.forEach((format, index_) => { + const newFormat = new _thebespokepixel_ocoColorvalueEx.OCOValueEX(original, color_.name); + newFormat._format = format; + + color_.addChild(new oco.ColorValue(format, newFormat.toString(format), newFormat), true, index_); + }); + }); + return this; + } + + load(pathArray) { + return Promise.all(pathArray.filter(file => file.match(fileFilter)).map(createIdentity(this.sourcePath)).map(identity => selectLoaderByIndentity(identity.type)(identity).then(entry => { + entry.addMetadata({ + 'import/file/source': path.relative(process.cwd(), identity.source), + 'import/file/type': identity.type + }); + return entry; + }).then(entry => this.tree.set(`${ identity.path }${ identity.name }`, entry)))).then(() => this); + } + + render(path) { + return oco.render(this.pick(path)); + } +} + +const writeFile = promisify(fs.writeFile); + +function writer(destination, oco) { + console.debug(`Writing oco file to ${ destination }`); + return writeFile(destination, oco); +} + +const console = verbosity.createConsole({ outStream: process.stderr }); + +function paletteReader(pathArray) { + return new Reader(pathArray); +} + +function paletteWriter(palette, destination) { + return writer(palette, destination); +} + +exports.console = console; +exports.paletteReader = paletteReader; +exports.paletteWriter = paletteWriter; \ No newline at end of file diff --git a/license b/license new file mode 100644 index 0000000..51d9ecb --- /dev/null +++ b/license @@ -0,0 +1,24 @@ +The MIT License (MIT) + +Copyright (c) 2016 Mark Griffiths + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/package.json b/package.json new file mode 100644 index 0000000..0b6fe2d --- /dev/null +++ b/package.json @@ -0,0 +1,68 @@ +{ + "name": "@thebespokepixel/palette2oco", + "version": "0.0.1", + "description": "Convert directories of Sip, JSON and ASE palette files to Open Color", + "main": "index.js", + "bin": { + "palette2oco": "./bin/palette2oco.js" + }, + "scripts": { + "test": "xo & ava" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/MarkGriffiths/palette2oco.git" + }, + "keywords": [ + "Open", + "Color", + "oco", + "sip", + "ase", + "json", + "color", + "colour", + "converter" + ], + "author": "Mark Griffiths (http://thebespokepixel.com/)", + "license": "MIT", + "bugs": { + "url": "https://github.com/MarkGriffiths/palette2oco/issues" + }, + "homepage": "https://github.com/MarkGriffiths/palette2oco#readme", + "dependencies": { + "@thebespokepixel/oco-colorvalue-ex": "^0.1.0", + "@thebespokepixel/string": "^0.4.0", + "ase-util": "^1.0.1", + "common-tags": "^1.3.1", + "es6-promisify": "^4.1.0", + "globby": "^6.0.0", + "lodash": "^4.16.2", + "opencolor": "^0.2.0", + "read-pkg": "^2.0.0", + "shelljs": "^0.7.4", + "trucolor": "^0.5.5", + "truwrap": "^0.6.3", + "update-notifier": "^1.0.2", + "verbosity": "^0.7.1", + "yargs": "^6.0.0" + }, + "devDependencies": { + "@thebespokepixel/cordial": "^0.16.2", + "ava": "^0.16.0", + "codeclimate-test-reporter": "^0.3.3", + "gulp": "github:gulpjs/gulp#4.0", + "nyc": "^8.3.0", + "snyk": "^1.19.1", + "xo": "^0.16.0" + }, + "xo": { + "semicolon": false, + "esnext": true + }, + "buildNumber": 182, + "engines": { + "node": ">=4.0 <7.0" + }, + "jsnext:main": "index-es.js" +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..35c6bce --- /dev/null +++ b/readme.md @@ -0,0 +1,37 @@ +# palette2oco converter +> Load or convert files or directories of various palette formats to structured Open Color data or .oco files. +> +>[![Build Status][build-badge]][travis] +[![Dependency Status][david-badge]][david] +[![devDependency Status][david-dev-badge]][david-dev] +![Project status][project-badge] +[![npm Status][npm-badge]][npm] +[![XO code style][xo-badge]][xo] +[![Chat on Gitter][gitter-badge]][gitter] + +## Load palette data from various tools into Open Color format. + + + +## Convert a range of palette formats to Open Color palettes from the command line. + +- Converting JSON based color palettes, such as those exported from Sip's .sippallette. +- Converting binary Adobe ASE files, including basic CMYK -> RGB conversion. +- Add supports for L*ab, CMYK, Lch, HWB, Color temperatures. +- Generate aesthetic palettes, or describe dynamic scaling along curves from oco files with Chroma support. + + +[project-badge]: http://img.shields.io/badge/status-experimental-red.svg?style=flat +[build-badge]: http://img.shields.io/travis/MarkGriffiths/palette2oco.svg?branch=master&style=flat +[david-badge]: http://img.shields.io/david/MarkGriffiths/palette2oco.svg?style=flat +[david-dev-badge]: http://img.shields.io/david/dev/MarkGriffiths/palette2oco.svg?style=flat +[npm-badge]: https://img.shields.io/npm/v/@thebespokepixel/palette2oco.svg?style=flat +[xo-badge]: https://img.shields.io/badge/code_style-XO-5ed9c7.svg +[gitter-badge]: https://badges.gitter.im/MarkGriffiths/help.svg + +[travis]: https://travis-ci.org/MarkGriffiths/palette2oco +[david]: https://david-dm.org/MarkGriffiths/palette2oco +[david-dev]: https://david-dm.org/MarkGriffiths/palette2oco#info=devDependencies +[npm]: https://www.npmjs.com/package/@thebespokepixel/palette2oco +[xo]: https://github.com/sindresorhus/xo +[gitter]: https://gitter.im/MarkGriffiths/help?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge diff --git a/src/classes/reader.js b/src/classes/reader.js new file mode 100644 index 0000000..0b1cb04 --- /dev/null +++ b/src/classes/reader.js @@ -0,0 +1,233 @@ +/* + * Open Color Converter for Sip, JSON and ASE palettes + * ────────────────────────────────────────────────────────────── + * ©2016 Mark Griffiths @ The Bespoke Pixel (MIT licensed) + */ + +import {relative as relativePath} from 'path' +import fs from 'fs' + +import _ from 'lodash' +import promisify from 'es6-promisify' +import {OCOValueEX, fromPrecise, fromBytes, fromCMYK, fromLab} from '@thebespokepixel/oco-colorvalue-ex' +import oco from 'opencolor' +import ase from 'ase-util' +import {console} from '../index' + +const loader = promisify(fs.readFile) + +const supportedTypes = [ + 'oco', + 'json', + 'sippalette', + 'ase' +] + +const fileFilter = new RegExp(`\.(${supportedTypes.join('|')})$`) +const fileMatch = new RegExp(`(.*\/)(.+?).(${supportedTypes.join('|')})$`) + +function createIdentity(rootPath) { + return function (path) { + const address = path + .replace(rootPath, '') + .match(fileMatch) + return { + source: path, + name: address[2], + path: address[1].replace(/^\//, '').replace(/\//g, '.'), + type: address[3] + } + } +} + +function isPaletteJSON(datum) { + const tests = { + palette: { + name: (typeof datum.name === 'string') && datum.name, + colors: (Array.isArray(datum.colors)) && datum.colors + }, + rgba: { + name: typeof datum.name === 'string' && datum.name, + red: (datum.red >= 0.0 && datum.red <= 1.0) && datum.red, + green: (datum.green >= 0.0 && datum.green <= 1.0) && datum.green, + blue: (datum.blue >= 0.0 && datum.blue <= 1.0) && datum.blue, + alpha: (datum.alpha >= 0.0 && datum.alpha <= 1.0) && datum.alpha + }, + rgbaInteger: { + name: typeof datum.name === 'string' && datum.name, + red: (datum.red >= 0 && datum.red <= 255) && datum.red, + green: (datum.green >= 0 && datum.green <= 255) && datum.green, + blue: (datum.blue >= 0 && datum.blue <= 255) && datum.blue, + alpha: (datum.alpha >= 0 && datum.alpha <= 255) && datum.alpha + } + } + return { + isPalette: _.isEqual(datum, tests.palette), + isRGBA: _.isEqual(datum, tests.rgba), + isIntegerRGBA: _.isEqual(datum, tests.rgbaInteger) + } +} + +function loadOCO(identity) { + return loader(identity.source, 'utf8') + .then(oco.parse) +} + +function loadJSON(identity) { + return loader(identity.source, 'utf8') + .then(JSON.parse) + .then(palette => { + if (isPaletteJSON(palette).isPalette) { + console.debug(`JSON Palette: ${palette.name}`) + return new oco.Entry( + identity.name, + [OCOValueEX.generateOCO( + palette.name, + palette.colors.map(color => { + const paletteColor = isPaletteJSON(color) + switch (true) { + case paletteColor.isRGBA: + console.debug(`JSON Color (RGBA): ${color.name}`) + return fromPrecise(color) + case paletteColor.isIntegerRGBA: + console.debug(`JSON Color (Integer RGBA): ${color.name}`) + return fromBytes(color) + default: + throw new Error(`${color.name}.json is not a valid JSON color object`) + } + }) + )] + ) + } + throw new Error(`${identity.name}.json is not a valid palette`) + } + ) +} + +function loadASE(identity) { + function scan(node) { + return node.map(datum => { + switch (datum.type) { + case 'color': + switch (datum.color.model) { + case 'RGB': + console.debug(`ASE Color (RGB): ${datum.name}`) + return new OCOValueEX(datum.color.hex, datum.name) + case 'CMYK': + console.debug(`ASE Color (CMYK): ${datum.name}`) + return fromCMYK({ + name: datum.name, + cyan: datum.color.c, + magenta: datum.color.m, + yellow: datum.color.y, + black: datum.color.k + }) + case 'LAB': + console.debug(`ASE Color (Lab): ${datum.name}`) + return fromLab({ + name: datum.name, + L: datum.color.lightness, + a: datum.color.a, + b: datum.color.b + }) + case 'Gray': + console.debug(`ASE Color (Gray): ${datum.name}`) + return new OCOValueEX(datum.color.hex, datum.name) + default: + throw new Error(`${datum.color.model} is not a valid ASE color model`) + } + + case 'group': + console.debug(`ASE Group: ${datum.name}`) + return OCOValueEX.generateOCO( + datum.name, + scan(datum.entries) + ) + + default: + throw new Error(`${datum.type} is not a valid ASE data type`) + } + }) + } + + return loader(identity.source) + .then(ase.read) + .then(palette => { + if (Array.isArray(palette)) { + return palette.length === 1 ? new oco.Entry( + identity.name, + scan(palette) + ) : OCOValueEX.generateOCO( + identity.name, + scan(palette) + ) + } + throw new Error(`${identity.name}.ase is not a valid palette`) + }) +} + +function selectLoaderByIndentity(type) { + switch (type) { + case 'sippalette': + return loadJSON + case 'json': + return loadJSON + case 'ase': + return loadASE + case 'oco': + return loadOCO + default: + throw new Error(`${type} is not recognised`) + } +} + +export default class Reader { + constructor(source_) { + this.sourcePath = source_ + this.tree = new oco.Entry() + } + + pick(key_) { + return key_ ? this.tree.get(key_) : this.tree.root() + } + + transformColors(formats) { + this.tree.traverseTree('Color', color_ => { + const original = color_.get(0).identifiedValue.getOriginalInput() + color_.children = [] + + formats.forEach((format, index_) => { + const newFormat = new OCOValueEX(original, color_.name) + newFormat._format = format + + color_.addChild(new oco.ColorValue( + format, + newFormat.toString(format), + newFormat + ), true, index_) + }) + }) + return this + } + + load(pathArray) { + return Promise.all(pathArray + .filter(file => file.match(fileFilter)) + .map(createIdentity(this.sourcePath)) + .map(identity => selectLoaderByIndentity(identity.type)(identity) + .then(entry => { + entry.addMetadata({ + 'import/file/source': relativePath(process.cwd(), identity.source), + 'import/file/type': identity.type + }) + return entry + }) + .then(entry => this.tree.set(`${identity.path}${identity.name}`, entry))) + ) + .then(() => this) + } + + render(path) { + return oco.render(this.pick(path)) + } +} diff --git a/src/cli.js b/src/cli.js new file mode 100644 index 0000000..748679f --- /dev/null +++ b/src/cli.js @@ -0,0 +1,157 @@ +/* ─────────────────────────╮ + │ @thebespokepixel/xo-tidy │ CLI Utility + ╰──────────────────────────┴───────────────────────────────────────────────────*/ +/** + * Allow xo-tidy to run as a command line tool. + * @module xo-tidy/cli + * @see module:xo-tidy + */ +/* eslint xo/no-process-exit:0, no-process-exit:0 */ + +import {resolve} from 'path' +import _ from 'lodash' +import truwrap from 'truwrap' +import {stripIndent, TemplateTag, replaceSubstitutionTransformer} from 'common-tags' +import {box} from '@thebespokepixel/string' +import yargs from 'yargs' +import globby from 'globby' +import readPkg from 'read-pkg' +import updateNotifier from 'update-notifier' +import trucolor from 'trucolor' +import {console, paletteReader, paletteWriter} from './index' + +const clr = trucolor.simplePalette() + +const _package = readPkg.sync(resolve(__dirname, '..')) + +const renderer = truwrap({ + outStream: process.stderr +}) + +const colorReplacer = new TemplateTag( + replaceSubstitutionTransformer( + /([a-zA-Z]+?)[:/|](.+)/, + (match, colorName, content) => `${clr[colorName]}${content}${clr[colorName].out}` + ) +) + +const title = box(colorReplacer`${'title|palette2oco'}${`dim| │ v${_package.version}`}`, { + borderColor: 'red', + margin: { + top: 1 + }, + padding: { + bottom: 0, + top: 0, + left: 2, + right: 2 + } +}) + +const usage = stripIndent(colorReplacer)`${title} + + Convert palette data from a variety of sources into Open Color .oco format. + + Allows structured directories of pallette data to be converted into nested oco palette data. + + Formats supported: + Sip (http://sipapp.io): Supports .sippalette and .json exports. + + Abobe Swatch Exchange (ASE): Full support of RGB, CMYK and Lab colorspaces. + + Vanilla JSON: File signature must match the following... + + { + "name" : "Palette name", + "colors" : [ + { + name: "Color name", + red: (0.0 - 1.0 | 0 - 255) + green: (0.0 - 1.0 | 0 - 255) + blue: (0.0 - 1.0 | 0 - 255) + alpha: (0.0 - 1.0 | 0 - 255) + } + ... + ] + } + + Usage: + ${'command|palette2oco'} ${'option|[options]'} ${'argument|sourceGlob'} ${'argument|outputFile'}` + +const epilogue = colorReplacer`${'green|© 2016'} ${'brightGreen|The Bespoke Pixel.'} ${'grey|Released under the MIT License.'}` + +yargs.strict().options({ + h: { + alias: 'help', + describe: 'Display help.' + }, + v: { + alias: 'version', + count: true, + describe: 'Print version to stdout. -vv Print name & version.' + }, + V: { + alias: 'verbose', + count: true, + describe: 'Be verbose. -VV Be loquacious.' + }, + color: { + describe: 'Force color output. Disable with --no-color' + } +}).wrap(renderer.getWidth()) + +const argv = yargs.argv + +if (!(process.env.USER === 'root' && process.env.SUDO_USER !== process.env.USER)) { + updateNotifier({ + pkg: _package + }).notify() +} + +if (argv.help) { + renderer.write(usage) + renderer.break(2) + renderer.write(yargs.getUsageInstance().help()) + renderer.break() + renderer.write(epilogue) + renderer.break(1) + process.exit(0) +} + +if (argv.version) { + const version = _package.buildNumber > 0 ? `${_package.version}-Δ${_package.buildNumber}` : `${_package.version}` + process.stdout.write(argv.version > 1 ? `${_package.name} v${version}` : version) + process.exit(0) +} + +if (argv.verbose) { + switch (argv.verbose) { + case 1: + console.verbosity(4) + console.log(`${clr.title}Verbose mode${clr.title.out}:`) + break + case 2: + console.verbosity(5) + console.log(`${clr.title}Extra-Verbose mode${clr.title.out}:`) + console.yargs(argv) + break + default: + console.verbosity(3) + } +} + +if (argv._.length > 1) { + const root = resolve() + const dest = resolve(_.tail(argv._)[0]) + globby(_.initial(argv._)) + .then(pathArray => paletteReader(root).load(pathArray)) + .then(pal => pal.render()) + .then(contents => paletteWriter(dest, contents)) + .catch(err => { + console.error(err) + process.exit(1) + }) +} else { + console.error('palette2oco needs at least a source and a destination.') + process.exit(1) +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..7608c81 --- /dev/null +++ b/src/index.js @@ -0,0 +1,20 @@ +/* + * Open Color Converter for Sip, JSON and ASE palettes + * ────────────────────────────────────────────────────────────── + * ©2016 Mark Griffiths @ The Bespoke Pixel (MIT licensed) + */ + +import {createConsole} from 'verbosity' + +import Reader from './classes/reader' +import writer from './writer' + +export const console = createConsole({outStream: process.stderr}) + +export function paletteReader(pathArray) { + return new Reader(pathArray) +} + +export function paletteWriter(palette, destination) { + return writer(palette, destination) +} diff --git a/src/writer.js b/src/writer.js new file mode 100644 index 0000000..afa8032 --- /dev/null +++ b/src/writer.js @@ -0,0 +1,17 @@ +/* + * Open Color Converter for Sip, JSON and ASE palettes + * ────────────────────────────────────────────────────────────── + * ©2016 Mark Griffiths @ The Bespoke Pixel (MIT licensed) + */ + +// import path from 'path' +import fs from 'fs' +import promisify from 'es6-promisify' +import {console} from './index' + +const writeFile = promisify(fs.writeFile) + +export default function writer(destination, oco) { + console.debug(`Writing oco file to ${destination}`) + return writeFile(destination, oco) +} diff --git a/test/fixtures/config/.eslintignore b/test/fixtures/config/.eslintignore new file mode 100644 index 0000000..96212a3 --- /dev/null +++ b/test/fixtures/config/.eslintignore @@ -0,0 +1 @@ +**/*{.,-}min.js diff --git a/test/fixtures/config/.eslintrc b/test/fixtures/config/.eslintrc new file mode 100644 index 0000000..77799e8 --- /dev/null +++ b/test/fixtures/config/.eslintrc @@ -0,0 +1,213 @@ +ecmaFeatures: + modules: true + jsx: true + +env: + amd: true + browser: true + es6: true + jquery: true + node: true + +# http://eslint.org/docs/rules/ +rules: + # Possible Errors + comma-dangle: [2, never] + no-cond-assign: 2 + no-console: 0 + no-constant-condition: 2 + no-control-regex: 2 + no-debugger: 2 + no-dupe-args: 2 + no-dupe-keys: 2 + no-duplicate-case: 2 + no-empty: 2 + no-empty-character-class: 2 + no-ex-assign: 2 + no-extra-boolean-cast: 2 + no-extra-parens: 0 + no-extra-semi: 2 + no-func-assign: 2 + no-inner-declarations: [2, functions] + no-invalid-regexp: 2 + no-irregular-whitespace: 2 + no-negated-in-lhs: 2 + no-obj-calls: 2 + no-regex-spaces: 2 + no-sparse-arrays: 2 + no-unexpected-multiline: 2 + no-unreachable: 2 + use-isnan: 2 + valid-jsdoc: 0 + valid-typeof: 2 + + # Best Practices + accessor-pairs: 2 + block-scoped-var: 0 + complexity: [2, 6] + consistent-return: 0 + curly: 0 + default-case: 0 + dot-location: 0 + dot-notation: 0 + eqeqeq: 2 + guard-for-in: 2 + no-alert: 2 + no-caller: 2 + no-case-declarations: 2 + no-div-regex: 2 + no-else-return: 0 + no-empty-label: 2 + no-empty-pattern: 2 + no-eq-null: 2 + no-eval: 2 + no-extend-native: 2 + no-extra-bind: 2 + no-fallthrough: 2 + no-floating-decimal: 0 + no-implicit-coercion: 0 + no-implied-eval: 2 + no-invalid-this: 0 + no-iterator: 2 + no-labels: 0 + no-lone-blocks: 2 + no-loop-func: 2 + no-magic-number: 0 + no-multi-spaces: 0 + no-multi-str: 0 + no-native-reassign: 2 + no-new-func: 2 + no-new-wrappers: 2 + no-new: 2 + no-octal-escape: 2 + no-octal: 2 + no-proto: 2 + no-redeclare: 2 + no-return-assign: 2 + no-script-url: 2 + no-self-compare: 2 + no-sequences: 0 + no-throw-literal: 0 + no-unused-expressions: 2 + no-useless-call: 2 + no-useless-concat: 2 + no-void: 2 + no-warning-comments: 0 + no-with: 2 + radix: 2 + vars-on-top: 0 + wrap-iife: [2, inside] + yoda: 0 + + # Strict + strict: 0 + + # Variables + init-declarations: 0 + no-catch-shadow: 2 + no-delete-var: 2 + no-label-var: 2 + no-shadow-restricted-names: 2 + no-shadow: 0 + no-undef-init: 2 + no-undef: 0 + no-undefined: 0 + no-unused-vars: 0 + no-use-before-define: 0 + + # Node.js and CommonJS + callback-return: 2 + global-require: 2 + handle-callback-err: 2 + no-mixed-requires: 0 + no-new-require: 0 + no-path-concat: 2 + no-process-exit: 2 + no-restricted-modules: 0 + no-sync: 0 + + # Stylistic Issues + array-bracket-spacing: 0 + block-spacing: 0 + brace-style: 0 + camelcase: 0 + comma-spacing: 0 + comma-style: 0 + computed-property-spacing: 0 + consistent-this: 0 + eol-last: 0 + func-names: 0 + func-style: 0 + id-length: 0 + id-match: 0 + indent: 0 + jsx-quotes: 0 + key-spacing: 0 + linebreak-style: 0 + lines-around-comment: 0 + max-depth: 0 + max-len: 0 + max-nested-callbacks: 0 + max-params: 0 + max-statements: [2, 30] + new-cap: 0 + new-parens: 0 + newline-after-var: 0 + no-array-constructor: 0 + no-bitwise: 0 + no-continue: 0 + no-inline-comments: 0 + no-lonely-if: 0 + no-mixed-spaces-and-tabs: 0 + no-multiple-empty-lines: 0 + no-negated-condition: 0 + no-nested-ternary: 0 + no-new-object: 0 + no-plusplus: 0 + no-restricted-syntax: 0 + no-spaced-func: 0 + no-ternary: 0 + no-trailing-spaces: 0 + no-underscore-dangle: 0 + no-unneeded-ternary: 0 + object-curly-spacing: 0 + one-var: 0 + operator-assignment: 0 + operator-linebreak: 0 + padded-blocks: 0 + quote-props: 0 + quotes: 0 + require-jsdoc: 0 + semi-spacing: 0 + semi: 0 + sort-vars: 0 + space-after-keywords: 0 + space-before-blocks: 0 + space-before-function-paren: 0 + space-before-keywords: 0 + space-in-parens: 0 + space-infix-ops: 0 + space-return-throw-case: 0 + space-unary-ops: 0 + spaced-comment: 0 + wrap-regex: 0 + + # ECMAScript 6 + arrow-body-style: 0 + arrow-parens: 0 + arrow-spacing: 0 + constructor-super: 0 + generator-star-spacing: 0 + no-arrow-condition: 0 + no-class-assign: 0 + no-const-assign: 0 + no-dupe-class-members: 0 + no-this-before-super: 0 + no-var: 0 + object-shorthand: 0 + prefer-arrow-callback: 0 + prefer-const: 0 + prefer-reflect: 0 + prefer-spread: 0 + prefer-template: 0 + require-yield: 0 diff --git a/test/fixtures/in/ase/ps-test.ase b/test/fixtures/in/ase/ps-test.ase new file mode 100644 index 0000000000000000000000000000000000000000..a3ce8ccf3789de90ce0437aba6d594c3a2d4b83c GIT binary patch literal 6804 zcma)AJ#QUV5FHu>!9e(mk_NVb2t^SJdA8#qy!)^XE@BJINJOr}6R`s##L~kNGS$`; zhzb!4Aqq-58blRQAPOVFA3&+75fVLf?%mltb9Z*1*YbObJm;LbGjs0V-8bHO^DTOa zaD5iOU!+H9o7QQC3jW@un?&2|D@9d@82JF=0zKA7?r`L7{@JHbQ@p|;{$)DV#;?0!D~AJB(%0N(ZKd6(y$$$6eUo;j2Ag3A+94)wXfHC&}l+M#P)#b)YO zH+QZf1hsDJjsK#am$-*1-prn$5SpQ0;dxCcHG#lLtb8|07kOqArkz8LfKqcIH;ZVe zQ4oqBARsQ&<4Pl&Nm-AB!&EJJ)i3`xU0n34UmlwdExJ5Enu)pM^4v4iaK+`RvDDTH zOD>O{@+FtYPTp0IXW6dns>idOwBT*@XhQGPN71(-*O)CPW{dy!NpSvcXEr&wY5>g& zU#F*COhIpl-QiByaY*`F{_&@qo*?D7Vmr$ zUVrAAl2sl2n4Zn>b_F>L*T7+cFKqQQoMbKE%mF`jJCJY%JFU5w1V;v4B3Vbnfc?cP zyQl_?FX(owCa5CJShy>Rs!|kuSJmzw9EWL97gS4nCd*Y&1-;AntbLyDU7p|2Z&XTE z3sm6fPD%%v&8Z;Q=uD=rkp=JR<_?S1W93c`i}Ym1Zyv#P3*sOEE_eHoSQL<$afAFZr$zMt(( z3J>-a&0`%IGC-)4dA@dwbe*ZHj*8ez$Xr0!3JD_jZOxT6P)AK1*s4`y1A$sR0Z2sz;d!<; zy#x{Pb_%rf+CHcr2r8boL{+0)zh=`XEb+7@EY@>D9f6LzU9-jc-UCs*ux5C5jZX(V6gk(`Gg~B8z)Cs(*u<&Za z+N_g@+8J@w;nl>DV>nX>V=J97171}*!>dVAoELL!^q{E_wnBm!@T$UEHE~|6CSFwt z%x%g~5Hq~0@OBEc>)JkuTNDajO{y9-)2oUFUQI04b3t9YnN`@X+2VZf*$(L`+GWT3 zn3`w*9A0Y$zv|i5m>Hf`3^lim&EI;>wq7Sipk^4%I(tnXVUQTp+u zHVJPJFeUrpO#(VM4R87R&Aq(CTK?~Tc$#jWsBxD@@Ac`w7hjJ5pz@2asl3nM-+hCH z0FRm7k1AM;Mr&j93K5BLQ4d0=T6?A@yuU7h}lsv!Y9aQ*rj_-t2==Hs3l%J)B161wbhpsVEk?vHBs@MImB{0$I_E{XsE literal 0 HcmV?d00001 diff --git a/test/fixtures/in/json/test.json b/test/fixtures/in/json/test.json new file mode 100644 index 0000000..886d0a7 --- /dev/null +++ b/test/fixtures/in/json/test.json @@ -0,0 +1,47 @@ +{ + "name" : "Test", + "colors" : [ + { + "red" : 0.9410084, + "alpha" : 1, + "name" : "Ku Crimson", + "blue" : 0, + "green" : 0 + }, + { + "red" : 0.9410084, + "alpha" : 1, + "name" : "Titanium Yellow", + "blue" : 0, + "green" : 0.9420211 + }, + { + "red" : 0, + "alpha" : 1, + "name" : "Lime", + "blue" : 0, + "green" : 0.9420211 + }, + { + "red" : 0, + "alpha" : 1, + "name" : "Turquoise Blue", + "blue" : 0.9420091, + "green" : 0.9420211 + }, + { + "red" : 0, + "alpha" : 1, + "name" : "Blue", + "blue" : 0.9420091, + "green" : 0 + }, + { + "red" : 0.9428651, + "alpha" : 1, + "name" : "Fuchsia", + "blue" : 0.9420091, + "green" : 0 + } + ] +} \ No newline at end of file diff --git a/test/fixtures/in/oco/test.oco b/test/fixtures/in/oco/test.oco new file mode 100644 index 0000000..2fc9c20 --- /dev/null +++ b/test/fixtures/in/oco/test.oco @@ -0,0 +1,7 @@ +Test: + Ku Crimson: rgb(240, 0, 0) + Titanium Yellow: rgb(240, 240, 0) + Lime: rgb(0, 240, 0) + Turquoise Blue: rgb(0, 240, 240) + Blue: rgb(0, 0, 240) + Fuchsia: rgb(240, 0, 240) diff --git a/test/fixtures/in/sippalette/test.sippalette b/test/fixtures/in/sippalette/test.sippalette new file mode 100644 index 0000000..886d0a7 --- /dev/null +++ b/test/fixtures/in/sippalette/test.sippalette @@ -0,0 +1,47 @@ +{ + "name" : "Test", + "colors" : [ + { + "red" : 0.9410084, + "alpha" : 1, + "name" : "Ku Crimson", + "blue" : 0, + "green" : 0 + }, + { + "red" : 0.9410084, + "alpha" : 1, + "name" : "Titanium Yellow", + "blue" : 0, + "green" : 0.9420211 + }, + { + "red" : 0, + "alpha" : 1, + "name" : "Lime", + "blue" : 0, + "green" : 0.9420211 + }, + { + "red" : 0, + "alpha" : 1, + "name" : "Turquoise Blue", + "blue" : 0.9420091, + "green" : 0.9420211 + }, + { + "red" : 0, + "alpha" : 1, + "name" : "Blue", + "blue" : 0.9420091, + "green" : 0 + }, + { + "red" : 0.9428651, + "alpha" : 1, + "name" : "Fuchsia", + "blue" : 0.9420091, + "green" : 0 + } + ] +} \ No newline at end of file diff --git a/test/fixtures/out/ase.oco b/test/fixtures/out/ase.oco new file mode 100644 index 0000000..a10f78c --- /dev/null +++ b/test/fixtures/out/ase.oco @@ -0,0 +1,11 @@ +test: + import/ + file/source: test/fixtures/in/ase/test.ase + file/type: ase + Test: + Ku Crimson: rgb(239, 0, 0) + Titanium Yellow: rgb(239, 240, 0) + Lime: rgb(0, 240, 0) + Turquoise Blue: rgb(0, 240, 240) + Blue: rgb(0, 0, 240) + Fuchsia: rgb(240, 0, 240) diff --git a/test/fixtures/out/json.oco b/test/fixtures/out/json.oco new file mode 100644 index 0000000..e9c6dcd --- /dev/null +++ b/test/fixtures/out/json.oco @@ -0,0 +1,11 @@ +test: + import/ + file/source: test/fixtures/in/json/test.json + file/type: json + Test: + Ku Crimson: rgb(240, 0, 0) + Titanium Yellow: rgb(240, 240, 0) + Lime: rgb(0, 240, 0) + Turquoise Blue: rgb(0, 240, 240) + Blue: rgb(0, 0, 240) + Fuchsia: rgb(240, 0, 240) diff --git a/test/fixtures/out/oco.oco b/test/fixtures/out/oco.oco new file mode 100644 index 0000000..04f097b --- /dev/null +++ b/test/fixtures/out/oco.oco @@ -0,0 +1,11 @@ +test: + import/ + file/source: test/fixtures/in/oco/test.oco + file/type: oco + Test: + Ku Crimson: rgb(240, 0, 0) + Titanium Yellow: rgb(240, 240, 0) + Lime: rgb(0, 240, 0) + Turquoise Blue: rgb(0, 240, 240) + Blue: rgb(0, 0, 240) + Fuchsia: rgb(240, 0, 240) diff --git a/test/fixtures/out/ps-test.oco b/test/fixtures/out/ps-test.oco new file mode 100644 index 0000000..2907550 --- /dev/null +++ b/test/fixtures/out/ps-test.oco @@ -0,0 +1,126 @@ +ps-test: + import/ + file/source: test/fixtures/in/ase/ps-test.ase + file/type: ase + RGB Red: rgb(255, 0, 0) + RGB Yellow: rgb(255, 255, 0) + RGB Green: rgb(0, 255, 0) + RGB Cyan: rgb(0, 255, 255) + RGB Blue: rgb(0, 0, 255) + RGB Magenta: rgb(255, 0, 255) + White: rgb(255, 255, 255) + 10% Gray: rgb(229, 229, 229) + 15% Gray: rgb(216, 216, 216) + 20% Gray: rgb(204, 204, 204) + 25% Gray: rgb(191, 191, 191) + 30% Gray: rgb(178, 178, 178) + 35% Gray: rgb(165, 165, 165) + 40% Gray: rgb(153, 153, 153) + 45% Gray: rgb(140, 140, 140) + 50% Gray: rgb(127, 127, 127) + CMYK Red: rgb(255, 0, 0) + CMYK Yellow: rgb(255, 255, 0) + CMYK Green: rgb(0, 255, 0) + CMYK Cyan: rgb(0, 255, 255) + CMYK Blue: rgb(0, 0, 255) + CMYK Magenta: rgb(255, 0, 255) + 55% Gray: rgb(114, 114, 114) + 60% Gray: rgb(102, 102, 102) + 65% Gray: rgb(89, 89, 89) + 70% Gray: rgb(76, 76, 76) + 75% Gray: rgb(63, 63, 63) + 80% Gray: rgb(51, 51, 51) + 85% Gray: rgb(38, 38, 38) + 90% Gray: rgb(25, 25, 25) + 95% Gray: rgb(12, 12, 12) + Black: rgb(0, 0, 0) + Pastel Red: rgb(255, 128, 128) + Pastel Red Orange: rgb(255, 158, 128) + Pastel Yellow Orange: rgb(255, 191, 128) + Pastel Yellow: rgb(255, 255, 128) + Pastel Pea Green: rgb(191, 255, 128) + Pastel Yellow Green: rgb(158, 255, 128) + Pastel Green: rgb(128, 255, 128) + Pastel Green Cyan: rgb(128, 255, 191) + Pastel Cyan: rgb(128, 255, 255) + Pastel Cyan Blue: rgb(128, 191, 255) + Pastel Blue: rgb(128, 158, 255) + Pastel Blue Violet: rgb(128, 128, 255) + Pastel Violet: rgb(158, 128, 255) + Pastel Violet Magenta: rgb(191, 128, 255) + Pastel Magenta: rgb(255, 128, 255) + Pastel Magenta Red: rgb(255, 128, 191) + Light Red: rgb(255, 71, 71) + Light Red Orange: rgb(255, 117, 71) + Light Yellow Orange: rgb(255, 163, 71) + Light Yellow: rgb(255, 255, 71) + Light Pea Green: rgb(163, 255, 71) + Light Yellow Green: rgb(117, 255, 71) + Light Green: rgb(71, 255, 71) + Light Green Cyan: rgb(71, 255, 163) + Light Cyan: rgb(71, 255, 255) + Light Cyan Blue: rgb(71, 163, 255) + Light Blue: rgb(71, 117, 255) + Light Blue Violet: rgb(71, 71, 255) + Light Violet: rgb(117, 71, 255) + Light Violet Magenta: rgb(163, 71, 255) + Light Magenta: rgb(255, 71, 255) + Light Magenta Red: rgb(255, 71, 163) + Pure Red: rgb(255, 0, 0) + Pure Red Orange: rgb(255, 64, 0) + Pure Yellow Orange: rgb(255, 128, 0) + Pure Yellow: rgb(255, 255, 0) + Pure Pea Green: rgb(128, 255, 0) + Pure Yellow Green: rgb(64, 255, 0) + Pure Green: rgb(0, 255, 0) + Pure Green Cyan: rgb(0, 255, 128) + Pure Cyan: rgb(0, 255, 255) + Pure Cyan Blue: rgb(0, 128, 255) + Pure Blue: rgb(0, 64, 255) + Pure Blue Violet: rgb(0, 0, 255) + Pure Violet: rgb(64, 0, 255) + Pure Violet Magenta: rgb(128, 0, 255) + Pure Magenta: rgb(255, 0, 255) + Pure Magenta Red: rgb(255, 0, 128) + Dark Red: rgb(153, 0, 0) + Dark Red Orange: rgb(153, 38, 0) + Dark Yellow Orange: rgb(153, 77, 0) + Dark Yellow: rgb(153, 153, 0) + Dark Pea Green: rgb(77, 153, 0) + Dark Yellow Green: rgb(38, 153, 0) + Dark Green: rgb(0, 153, 0) + Dark Green Cyan: rgb(0, 153, 77) + Dark Cyan: rgb(0, 153, 153) + Dark Cyan Blue: rgb(0, 77, 153) + Dark Blue: rgb(0, 38, 153) + Dark Blue Violet: rgb(0, 0, 153) + Dark Violet: rgb(38, 0, 153) + Dark Violet Magenta: rgb(77, 0, 153) + Dark Magenta: rgb(153, 0, 153) + Dark Magenta Red: rgb(153, 0, 77) + Darker Red: rgb(102, 0, 0) + Darker Red Orange: rgb(102, 25, 0) + Darker Yellow Orange: rgb(102, 51, 0) + Darker Yellow: rgb(102, 102, 0) + Darker Pea Green: rgb(51, 102, 0) + Darker Yellow Green: rgb(25, 102, 0) + Darker Green: rgb(0, 102, 0) + Darker Green Cyan: rgb(0, 102, 51) + Darker Cyan: rgb(0, 102, 102) + Darker Cyan Blue: rgb(0, 51, 102) + Darker Blue: rgb(0, 25, 102) + Darker Blue Violet: rgb(0, 0, 102) + Darker Violet: rgb(25, 0, 102) + Darker Violet Magenta: rgb(51, 0, 102) + Darker Magenta: rgb(102, 0, 102) + Darker Magenta Red: rgb(102, 0, 51) + Pale Cool Brown: rgb(191, 168, 143) + Light Cool Brown: rgb(142, 126, 109) + Medium Cool Brown: rgb(99, 90, 79) + Dark Cool Brown: rgb(63, 58, 54) + Darker Cool Brown: rgb(32, 32, 32) + Pale Warm Brown: rgb(191, 143, 96) + Light Warm Brown: rgb(157, 111, 66) + Medium Warm Brown: rgb(127, 84, 40) + Dark Warm Brown: rgb(100, 59, 18) + Darker Warm Brown: rgb(77, 38, 0) diff --git a/test/fixtures/out/sippalette.oco b/test/fixtures/out/sippalette.oco new file mode 100644 index 0000000..2d1542b --- /dev/null +++ b/test/fixtures/out/sippalette.oco @@ -0,0 +1,11 @@ +test: + import/ + file/source: test/fixtures/in/sippalette/test.sippalette + file/type: sippalette + Test: + Ku Crimson: rgb(240, 0, 0) + Titanium Yellow: rgb(240, 240, 0) + Lime: rgb(0, 240, 0) + Turquoise Blue: rgb(0, 240, 240) + Blue: rgb(0, 0, 240) + Fuchsia: rgb(240, 0, 240) diff --git a/test/palettes.js b/test/palettes.js new file mode 100644 index 0000000..1a37edf --- /dev/null +++ b/test/palettes.js @@ -0,0 +1,48 @@ +import test from 'ava' +import {cat} from 'shelljs' +import {paletteReader, paletteWriter, console} from '..' + +test('Named palette (JSON)', t => { + const fixture = cat('fixtures/out/json.oco').toString() + return paletteReader('fixtures/in/json') + .load(['fixtures/in/json/test.json']) + .then(palette => { + t.is(palette.render(), fixture) + }) +}) + +test('Named palette (Sip pallette)', t => { + const fixture = cat('fixtures/out/sippalette.oco').toString() + return paletteReader('fixtures/in/sippalette') + .load(['fixtures/in/sippalette/test.sippalette']) + .then(palette => { + t.is(palette.render(), fixture) + }) +}) + +test('Named palette (OCO)', t => { + const fixture = cat('fixtures/out/oco.oco').toString() + return paletteReader('fixtures/in/oco') + .load(['fixtures/in/oco/test.oco']) + .then(palette => { + t.is(palette.render(), fixture) + }) +}) + +test('Named palette (ASE)', t => { + const fixture = cat('fixtures/out/ase.oco').toString() + return paletteReader('fixtures/in/ase') + .load(['fixtures/in/ase/test.ase']) + .then(palette => { + t.is(palette.render(), fixture) + }) +}) + +test('Photoshop palette (ASE)', t => { + const fixture = cat('fixtures/out/ps-test.oco').toString() + return paletteReader('fixtures/in/ase') + .load(['fixtures/in/ase/ps-test.ase']) + .then(palette => { + t.is(palette.render(), fixture) + }) +})