From c436ade4f6b6b343d7cc045026451259231bdbb3 Mon Sep 17 00:00:00 2001 From: Cameron McEfee Date: Sat, 21 Jun 2014 08:58:32 -0700 Subject: [PATCH 01/16] move stuff over --- .gitignore | 2 + README.md | 12 + package.json | 21 + script/test | 6 + src/GridNotation.coffee | 768 +++++++++++++++++++++++++++++++++ test/test-commands.coffee | 200 +++++++++ test/test-grid-notation.coffee | 307 +++++++++++++ test/test-scenarios.coffee | 300 +++++++++++++ test/test-units.coffee | 105 +++++ 9 files changed, 1721 insertions(+) create mode 100644 .gitignore create mode 100644 package.json create mode 100755 script/test create mode 100644 src/GridNotation.coffee create mode 100644 test/test-commands.coffee create mode 100644 test/test-grid-notation.coffee create mode 100644 test/test-scenarios.coffee create mode 100644 test/test-units.coffee diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd4f2b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +.DS_Store diff --git a/README.md b/README.md index 0b9f15f..e015611 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,15 @@ **This repository is a work in progress. The contents likely will not work yet.** Grid notation is a way to “write” grids. For more information, see the [spec](SPEC.md). + +## Setup + +``` +npm install +``` + +## Development + +``` +script/test +``` diff --git a/package.json b/package.json new file mode 100644 index 0000000..2620e8d --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "grid-notation", + "version": "0.0.1", + "description": "A written language to describe grids", + "engines": { + "node": "0.10", + "npm": "1.2" + }, + "dependencies": { + "mocha": "^1.20.1", + "coffee-script": "^1.7.1" + }, + "main": "lib/GridNotation.js", + "scripts": { + "test": "script/test" + }, + "repository" : { + "type" : "git", + "url" : "http://github.com/guideguide/grid-notation.git" + } +} diff --git a/script/test b/script/test new file mode 100755 index 0000000..f43293f --- /dev/null +++ b/script/test @@ -0,0 +1,6 @@ +#!/bin/sh +# usage: script/test +# +# Run tests + +mocha --compilers coffee:coffee-script/register --watch diff --git a/src/GridNotation.coffee b/src/GridNotation.coffee new file mode 100644 index 0000000..c01adf8 --- /dev/null +++ b/src/GridNotation.coffee @@ -0,0 +1,768 @@ +class GridNotation + + constructor: (args = {}) -> + @unit = new Unit() + @cmd = new Command() + + # Convert a GridNotation string into an array of guides. + # + # string - GridNotation string to parse + # info - information about the document + # + # Returns an Array. + parse: (string = "", info = {}) -> + @unit.resolution = info.resolution if info.resolution + @cmd.unit = @unit + guides = [] + tested = @validate(@objectify(string)) + return null if !tested.isValid + + gn = tested.obj + for key, variable of gn.variables + gn.variables[key] = @expandCommands variable, variables: gn.variables + + for grid in gn.grids + guideOrientation = grid.params.orientation + wholePixels = grid.params.calculation is 'p' + fill = find(grid.commands, (el) -> el.isFill)[0] || null + originalWidth = if guideOrientation == 'h' then info.height else info.width + measuredWidth = if guideOrientation == 'h' then info.height else info.width + measuredWidth = grid.params.width.unit.base if grid.params.width?.unit?.base + offset = if guideOrientation == 'h' then info.offsetY else info.offsetX + stretchDivisions = 0 + adjustRemainder = 0 + wildcards = find grid.commands, (el) -> el.isWildcard + + # Measure arbitrary commands + arbitrary = find grid.commands, (el) -> el.isArbitrary and !el.isFill + arbitrarySum = 0 + arbitrarySum += command.unit.base for command in arbitrary + + # If a width was specified, position it. If it wasn't, subtract the offsets + # from the boundaries. + if grid.params.width?.unit?.base + adjustRemainder = originalWidth - grid.params.width?.unit.base + else + adjustRemainder = originalWidth - arbitrarySum if wildcards.length == 0 + measuredWidth -= grid.params.firstOffset?.unit?.base || 0 + measuredWidth -= grid.params.lastOffset?.unit?.base || 0 + if adjustRemainder > 0 + adjustRemainder -= grid.params.firstOffset?.unit?.base || 0 + adjustRemainder -= grid.params.lastOffset?.unit?.base || 0 + + # Calculate wild offsets + stretchDivisions++ if grid.params.firstOffset?.isWildcard + stretchDivisions++ if grid.params.lastOffset?.isWildcard + adjust = adjustRemainder/stretchDivisions + if grid.params.firstOffset?.isWildcard + adjust = Math.ceil(adjust) if wholePixels + grid.params.firstOffset = @cmd.parse("#{ adjust }px") + if grid.params.lastOffset?.isWildcard + adjust = Math.floor(adjust) if wholePixels + grid.params.lastOffset = @cmd.parse("#{ adjust }px") + + # Adjust the first offset. + offset += grid.params.firstOffset?.unit.base || 0 + + wildcardArea = measuredWidth - arbitrarySum + + # Calculate fills + if wildcardArea and fill + fillIterations = Math.floor wildcardArea/lengthOf(fill, gn.variables) + fillCollection = [] + fillWidth = 0 + + for i in [1..fillIterations] + if fill.isVariable + fillCollection = fillCollection.concat gn.variables[fill.id] + fillWidth += lengthOf(fill, gn.variables) + else + newCommand = @cmd.parse(@cmd.toSimpleString(fill)) + fillCollection.push newCommand + fillWidth += newCommand.unit.base + + wildcardArea -= fillWidth + + # Set the width of any wildcards + if wildcardArea and wildcards + wildcardWidth = wildcardArea/wildcards.length + + if wholePixels + wildcardWidth = Math.floor wildcardWidth + remainderPixels = wildcardArea % wildcards.length + + for command in wildcards + command.isWildcard = false + command.isArbitrary = true + command.isFill = true + command.multiplier = 1 + command.isPercent = false + command.unit = @unit.parse("#{ wildcardWidth }px") + + # Adjust for pixel specific grids + if remainderPixels + remainderOffset = 0 + if grid.params.remainder == 'c' + remainderOffset = Math.floor (wildcards.length - remainderPixels)/2 + if grid.params.remainder == 'l' + remainderOffset = wildcards.length - remainderPixels + + for command, i in wildcards + if i >= remainderOffset && i < remainderOffset + remainderPixels + command.unit = @unit.parse("#{ wildcardWidth+1 }px") + + # Figure out where the grid starts + insertMarker = grid.params.firstOffset?.unit.base + insertMarker ||= offset + + # Expand any fills or variables + expandOpts = + variables: gn.variables + fillCollection: fillCollection + grid.commands = @expandCommands grid.commands, expandOpts + + # Set value of percent commands + percents = find grid.commands, (el) -> el.isPercent + for command in percents + percentValue = measuredWidth*(command.unit.value/100) + percentValue = Math.floor(percentValue) if wholePixels + command.unit = @unit.parse("#{ percentValue }px") + + for command in grid.commands + if command.isGuide + guides.push + location: insertMarker + orientation: guideOrientation + else + insertMarker += command.unit.base + + guides + + # Format a GridNotation string according to spec. + # + # string - string to format + # + # Returns a String. + clean: (string = "") => + gn = @validate(@objectify(string)).obj + string = "" + + for key, variable of gn.variables + string += "#{ key } = #{ @stringifyCommands variable }\n" + + string += "\n" if gn.variables.length > 0 + + for grid in gn.grids + line = "" + line += @stringifyCommands grid.commands + line += " #{ @stringifyParams grid.params }" + string += "#{ trim line }\n" + + trim string.replace(/\n\n\n+/g, "\n") + + # Create an object of grid data from a Guide Notation String + # + # string - string to parse + # + # Returns an object + objectify: (string = "") => + lines = string.split /\n/g + string = "" + variables = {} + grids = [] + + for line in lines + if /^\$.*?\s?=.*$/i.test line + variable = @parseVariable line + variables[variable.id] = variable.commands + else if /^\s*#/i.test line + # ignored line + else + grid = @parseGrid line + grids.push grid if grid.commands.length > 0 + + variables: variables + grids: grids + + # Process a guide notation object looking for errors. If any exist, mark them + # and return the results. + # + # obj - guide notation object + # + # Returns an Object. + validate: (obj) => + isValid = if obj.grids.length > 0 then true else false + variablesWithWildcards = {} + + for key, commands of obj.variables + for command in commands + isValid = false if command.isValid is false + id = command.id + variable = obj.variables[id] if id + + # If an undefined variable is called, we can't do anything with it. + isValid = command.isValid = false if id and !variable + + # Fills are only meant to be used once, in one place. Including a fill + # in a variable likely means it will be used in multiple places. In + # theory this *could* be used once, but for now, let's just invalidate. + isValid = command.isValid = false if command.isFill + + variablesWithWildcards[key] = true if command.isWildcard + + for key, grid of obj.grids + fills = 0 + + # Determine if the adjustments are valid + first = grid.params.firstOffset + width = grid.params.width + last = grid.params.lastOffset + isValid = false if first and !first.isValid + isValid = false if width and !width.isValid + isValid = false if last and !last.isValid + + for command in grid.commands + isValid = false if command.isValid is false + id = command.id + variable = obj.variables[id] if id + + # If an undefined variable is called, we can't do anything with it. + isValid = command.isValid = false if id and !variable + + # Since wildcards don't have an inherent value, it's impossible to + # calculate a fill variable containing one. + varHasWildcard = find(variable, (el) -> el.isWildcard).length > 0 + + if command.isFill and varHasWildcard + isValid = command.isValid = false + + fills++ if command.isFill + varHasFill = find(variable, (el) -> el.isFill).length > 0 + + # count as a fill if it's a variable that contains a fill + if id and variable and varHasFill + fills++ + + # Fills can only be used once. + isValid = command.isValid = false if fills > 1 + if id and variable and varHasFill + isValid = command.isValid = false + + isValid: isValid + obj: obj + + # Convert a string of command and guide commands into an object. + # + # Returns a command object + parseCommands: (string = "") -> + string = @pipeCleaner string + commands = [] + return commands if string == "" + tokens = string.replace(/^\s+|\s+$/g, '').replace(/\s\s+/g, ' ').split(/\s/) + + commands.push(@cmd.parse(token)) for token in tokens + + commands + + # Take an array of commands and apply any multiples + # + # array - array of commands + # args - arguments to influence expansion + # variables - if present, variables will be expanded + # fillCollection - if present, fills will be expanded + # + # Returns an Array + expandCommands: (commands = [], args = {}) -> + commands = @parseCommands commands if typeof commands is "string" + + # Expand fills + newCommands = [] + for command, i in commands + if args.fillCollection and command.isFill + newCommands = newCommands.concat args.fillCollection + else + newCommands.push command + commands = [].concat newCommands + + # Apply any variables + newCommands = [] + for command, i in commands + if command.isVariable and args.variables and args.variables[command.id] + newCommands = newCommands.concat(args.variables[command.id]) + else + newCommands.push command + commands = [].concat newCommands + + # Expand any multipliers + newCommands = [] + for command in commands + loops = command.multiplier || 1 + for i in [0...loops] by 1 + newCommands.push @cmd.parse(@cmd.toSimpleString(command)) + commands = [].concat newCommands + + # Remove dupe guides + newCommands = [] + for command, i in commands + if !command.isGuide or (command.isGuide and !commands[i-1]?.isGuide) + newCommands.push command + + newCommands + + # Look into a string to see if it contains commands + # + # string - string to test + # + # Returns a Boolean + isCommands: (string = "") => + return false if string is "" + return true if string.indexOf("|") >= 0 # it has pipes + commands = @parseCommands string + return true if commands.length > 1 # it has multiple commands + return true if commands[0].isValid # it has a valid first command + false + + # Convert a grid string into an object. + # + # string - string to parse + # + # Returns an Object. + parseGrid: (string = "") => + regex = /\((.*?)\)/i + params = regex.exec(string) || [] + string = trim string.replace regex, '' + commands = @parseCommands string + commands: commands + wildcards: find commands, (el) -> el.isWildcard + params: @parseParams params[1] || '' + + # Deterimine a grid's paramaters + # + # string - string to be parsed + # + # Returns an Object. + parseParams: (string = "") => + bits = string.replace(/[\s\(\)]/g,'').split ',' + obj = + orientation: "h" + remainder: "l" + calculation: "" + + if bits.length > 1 + obj[k] = v for k,v of @parseOptions bits[0] + obj[k] = v for k,v of @parseAdjustments(bits[1] || "") + return obj + else if bits.length is 1 + if @isCommands bits[0] + obj[k] = v for k,v of @parseAdjustments(bits[0] || "") + else + obj[k] = v for k,v of @parseOptions bits[0] + obj + + # Determine a grid's options + # + # string - string to be parse + # + # Returns an Object. + parseOptions: (string = "") -> + options = string.split '' + obj = {} + for option in options + switch option.toLowerCase() + when "h", "v" + obj.orientation = option + when "f", "c", "l" + obj.remainder = option + when "p" + obj.calculation = option + obj + + # Determine a grid's position + # + # string - string to be parsed + # + # Returns an Object or null if invalid. + parseAdjustments: (string = "") -> + adj = + firstOffset: null + width: null + lastOffset: null + return adj if string is "" + + bits = @expandCommands(string.replace(/\s/,'')).splice(0,5) + + end = bits.length-1 + adj.lastOffset = bits[end] if bits.length > 1 and !bits[end].isGuide + adj.firstOffset = bits[0] if !bits[0].isGuide + + for el, i in bits + if bits[i-1]?.isGuide and bits[i+1]?.isGuide + adj.width = el if !el.isGuide + + adj + + # Determine a variable's id, and gaps + # + # string - variable string to be parsed + # + # Return an object + parseVariable: (string) => + bits = /^\$([^=\s]+)?\s?=\s?(.*)$/i.exec(string) + return null if !bits[2]? + id: if bits[1] then "$#{ bits[1] }" else "$" + commands: @parseCommands bits[2] + + # Clean up the formatting of pipes in a command string + # + # string - string to be cleaned + # + # Returns a String. + pipeCleaner: (string = "") -> + string + .replace(/[^\S\n]*\|[^\S\n]*/g, '|') # Normalize spaces + .replace(/\|+/g, ' | ') # Duplicate pipes + .replace(/^\s+|\s+$/g, '') # Leading and trailing whitespace + + # Convert a command array into a guide notation spec compliant string. + # + # commands - command array + # + # Returns a String. + stringifyCommands: (commands) => + string = "" + string += @cmd.toString(command) for command in commands + @pipeCleaner string + + # Convert a grid's params to a guiden notation spec compliant string. + # + # params - grid params object + # + # Returns a String. + stringifyParams: (params) => + string = "" + string += "#{ params.orientation || '' }" + string += "#{ params.remainder || '' }" + string += "#{ params.calculation || '' }" + + if params.firstOffset or params.width or params.lastOffset + string += ", " if string.length > 0 + + string += @cmd.toString(params.firstOffset) if params.firstOffset + string += "|#{ @cmd.toString(params.width) }|" if params.width + string += "|" if params.firstOffset and params.lastOffset and !params.width + string += @cmd.toString(params.lastOffset) if params.firstOffset + + if string then "( #{ @pipeCleaner(string) } )" else '' + +# +# A command tells the guide parser to move ahead by the specified distance, or +# to add a guide. +# +class Command + variableRegexp: /^\$([^\*]+)?(\*(\d+)?)?$/i + arbitraryRegexp: /^(([-0-9\.]+)?[a-z%]+)(\*(\d+)?)?$/i + wildcardRegexp: /^~(\*(\d*))?$/i + + constructor: (args = {}) -> + @unit = new Unit() + + # Test if a command is a guide + # + # command - command to test + # + # Returns a Boolean + isGuide: (command = "") -> + if typeof command is "string" + command.replace(/\s/g, '') == "|" + else + command.isGuide || false + + # Test if a string is a variable + # + # string - command string to test + # + # Returns a Boolean + isVariable: (command = "") => + if typeof command is "string" + @variableRegexp.test command.replace /\s/g, '' + else + command.isVariable || false + + + # Test if a command is an arbitray command (unit pair) + # + # string - command string to test + # + # Returns a Boolean + isArbitrary: (command = "") => + if typeof command is "string" + return false if !@arbitraryRegexp.test command.replace /\s/g, '' + return false if @unit.parse(command) == null + true + else + command.isArbitrary || false + + # Test if a command is a wildcard + # + # string - command string to test + # + # Returns a Boolean + isWildcard: (command = "") => + if typeof command is "string" + @wildcardRegexp.test command.replace /\s/g, '' + else + command.isWildcard || false + + # Test if a command is a percent + # + # string - command string to test + # + # Returns a Boolean + isPercent: (command = "") -> + if typeof command is "string" + unit = @unit.parse(command.replace /\s/g, '') + unit? and unit.type == '%' + else + command.isPercent || false + + + # Test if a command does not have a multiple defined, and therefor should be + # repeated to fill the given area + # + # string - command string to test + # + # Returns a Boolean + isFill: (string = "") -> + if @isVariable string + bits = @variableRegexp.exec string + return bits[2] && !bits[3] || false + else if @isArbitrary string + bits = @arbitraryRegexp.exec string + return bits[3] && !bits[4] || false + else if @isWildcard string + bits = @wildcardRegexp.exec string + return bits[1] && !bits[2] || false + else + false + + # Parse a command and return the number of multiples + # + # string - wildcard string to parse + # + # Returns an integer + count: (string = "") -> + string = string.replace /\s/g, '' + if @isVariable string + parseInt(@variableRegexp.exec(string)[3]) || 1 + else if @isArbitrary string + parseInt(@arbitraryRegexp.exec(string)[4]) || 1 + else if @isWildcard string + parseInt(@wildcardRegexp.exec(string)[2]) || 1 + else + null + + # Parse a command into its constituent parts + # + # string - command string to parse + # + # Returns an object + parse: (string = "") -> + string = string.replace /\s/g, '' + if @isGuide string + isValid: true + isGuide: true + else if @isVariable string + bits = @variableRegexp.exec string + isValid: true + isVariable: true + isFill: @isFill string + id: if bits[1] then "$#{ bits[1] }" else "$" + multiplier: @count string + else if @isArbitrary string + isValid: true + isArbitrary: true + isPercent: @isPercent string + isFill: @isFill string + unit: @unit.parse(string) + multiplier: @count string + else if @isWildcard string + isValid: if @isFill(string) then false else true + isWildcard: true + isFill: @isFill string + multiplier: @count string + else + isValid: false + string: string + + # Output a command as a string. If it is unrecognized, format it properly. + # + # command - command to be converted to a string + # + # Returns an Integer. + toString: (command = "") -> + return command if typeof command is "string" + string = "" + + if command.isGuide + string += "|" + else if command.isVariable + string += command.id + else if command.isArbitrary + string += @unit.toString(command.unit) + else if command.isWildcard + string += "~" + else + return "" if command.string is "" + string += command.string + + if command.isVariable or command.isArbitrary or command.isWildcard + string += '*' if command.isFill or command.multiplier > 1 + string += command.multiplier if command.multiplier > 1 + + if command.isValid then string else "{#{ string }}" + + # Create a command string without a multiplier + # + # command - command to stringify + # + # Returns a String. + toSimpleString: (command = "") -> + return command.replace(/\*.*/gi, "") if typeof command is "string" + @toString(command).replace(/[\{\}]|\*.*/gi, "") + +# +# Unit is a utility for parsing and validating unit strings +# +class Unit + + resolution: 72 + + constructor: (args = {}) -> + + # Parse a string and change it to a unit object + # + # string - unit string to be parsed + # + # Returns an object or null if invalid + parse: (string = "") => + string = string.replace /\s/g, '' + bits = string.match(/([-0-9\.]+)([a-z%]+)?/i) + return null if !string or string == "" or !bits? + return null if bits[2] and !@preferredName(bits[2]) + + # Integer + if bits[1] and !bits[2] + value = parseFloat bits[1] + return if value.toString() == bits[1] then value else null + + # Unit pair + string: string + value: parseFloat bits[1] + type: @preferredName bits[2] + base: @asBaseUnit value: parseFloat(bits[1]), type: @preferredName(bits[2]) + + # Parse a string and change it to a friendly unit + # + # string - string to be parsed + # + # Returns a string or null, if invalid + preferredName: (string) -> + switch string + when 'centimeter', 'centimeters', 'centimetre', 'centimetres', 'cm' + 'cm' + when 'inch', 'inches', 'in' + 'in' + when 'millimeter', 'millimeters', 'millimetre', 'millimetres', 'mm' + 'mm' + when 'pixel', 'pixels', 'px' + 'px' + when 'point', 'points', 'pts', 'pt' + 'points' + when 'pica', 'picas' + 'picas' + when 'percent', 'pct', '%' + '%' + else + null + + # Convert the given value of type to the base unit of the application. + # This accounts for reslution, but the resolution must be manually changed. + # The result is pixels/points. + # + # unit - unit object + # resolution - dots per inch + # + # Returns a number + asBaseUnit: (unit) -> + return null unless unit? and unit.value? and unit.type? + + # convert to inches + switch unit.type + when 'cm' then unit.value = unit.value / 2.54 + when 'in' then unit.value = unit.value / 1 + when 'mm' then unit.value = unit.value / 25.4 + when 'px' then unit.value = unit.value / @resolution + when 'points' then unit.value = unit.value / @resolution + when 'picas' then unit.value = unit.value / 6 + else + return null + + # convert to base units + unit.value * @resolution + + # Convert a unit object to a string or format a unit string to conform to the + # unit string standard + # + # unit = string or object + # + # Returns a string + toString: (unit = "") => + return null if unit == "" + return @toString(@parse(unit)) if typeof unit == "string" + + "#{ unit.value }#{ unit.type }" + +# Remove leading and trailing whitespace +# +# string - string to be trimmed +# +# Returns a String. +trim = (string) -> string.replace(/^\s+|\s+$/g, '') + +# Find all items in an array that match the iterator +# +# arr - array +# iterator - condition to match +# +# Returns a array. +find = (arr, iterator) -> + return [] unless arr and iterator + matches = [] + (matches.push el if iterator(el) is true) for el, i in arr + matches + +# Get the total length of the given command. +# +# command - command to be measured +# variables - variables from the guide notation +# +# Returns a Number. +lengthOf = (command, variables) -> + return command.unit.value * command.multiplier unless command.isVariable + return 0 if !variables[command.id] + + sum = 0 + for command in variables[command.id] + sum += command.unit.value + sum + + +if (typeof module != 'undefined' && typeof module.exports != 'undefined') + module.exports = + notation: new GridNotation() + unit: new Unit() + command: new Command() +else + window.GridNotation = new GridNotation() + window.Unit = new Unit() + window.Command = new Command() diff --git a/test/test-commands.coffee b/test/test-commands.coffee new file mode 100644 index 0000000..9c9369e --- /dev/null +++ b/test/test-commands.coffee @@ -0,0 +1,200 @@ +assert = require "assert" +GridNotation = require " + #{ process.cwd() }/src/GridNotation.coffee +" +GN = GridNotation.notation +Unit = GridNotation.unit +Command = GridNotation.command + +describe 'Commands', -> + + describe 'Evalutations', -> + + it 'should succeed for guides', -> + assert.equal Command.isGuide("|"), true + + it 'should fail for non-guides', -> + assert.equal Command.isGuide("foo"), false + + it 'should succeed for variables', -> + assert.strictEqual Command.isVariable("$"), true + assert.strictEqual Command.isVariable("$ = | 10px |"), true + assert.strictEqual Command.isVariable("$foo = | 10px |"), true + + it 'should fail for non-variables', -> + assert.strictEqual Command.isVariable(""), false + assert.strictEqual Command.isVariable("foo"), false + assert.strictEqual Command.isVariable("1"), false + assert.strictEqual Command.isVariable("1px"), false + + it 'should succeed for arbitrary commands', -> + assert.strictEqual Command.isArbitrary("1cm"), true + assert.strictEqual Command.isArbitrary("1in"), true + assert.strictEqual Command.isArbitrary("1mm"), true + assert.strictEqual Command.isArbitrary("1px"), true + assert.strictEqual Command.isArbitrary("1pt"), true + assert.strictEqual Command.isArbitrary("1pica"), true + assert.strictEqual Command.isArbitrary("1%"), true + + it 'should fail for non-arbitrary commands', -> + assert.strictEqual Command.isArbitrary(""), false + assert.strictEqual Command.isArbitrary("1"), false + assert.strictEqual Command.isArbitrary("foo"), false + assert.strictEqual Command.isArbitrary("$"), false + assert.strictEqual Command.isArbitrary("$A = | 10px |"), false + + it 'should succeed for wildcards', -> + assert.strictEqual Command.isWildcard("~"), true + + it 'should fail for non-wildcards', -> + assert.strictEqual Command.isWildcard("~10px"), false + assert.strictEqual Command.isWildcard(""), false + assert.strictEqual Command.isWildcard("1px"), false + assert.strictEqual Command.isWildcard("foo"), false + assert.strictEqual Command.isWildcard("$A"), false + + it 'should succeed for percents', -> + assert.strictEqual Command.isPercent("10%"), true + + it 'should fail for non-percents', -> + assert.strictEqual Command.isPercent("%"), false + assert.strictEqual Command.isPercent("~10px"), false + assert.strictEqual Command.isPercent(""), false + assert.strictEqual Command.isPercent("1px"), false + assert.strictEqual Command.isPercent("foo"), false + assert.strictEqual Command.isPercent("$"), false + + describe 'Parsing', -> + + it 'should parse guides', -> + assert.deepEqual Command.parse("|"), + isValid: true + isGuide: true + + it 'should parse variables', -> + assert.deepEqual Command.parse("$"), + isValid: true + isVariable: true + isFill: false + id: "$" + multiplier: 1 + assert.deepEqual Command.parse("$foo*2"), + isValid: true + isVariable: true + isFill: false + id: "$foo" + multiplier: 2 + + it 'should parse wildcards', -> + assert.deepEqual Command.parse("~"), + isValid: true + isWildcard: true + isFill: false + multiplier: 1 + assert.deepEqual Command.parse("~*2"), + isValid: true + isWildcard: true + isFill: false + multiplier: 2 + + it 'should parse arbitrary', -> + assert.deepEqual Command.parse("10px*2"), + isValid: true + isArbitrary: true + isFill: false + isPercent: false + unit: + string: "10px*2" + value: 10 + type: "px" + base: 10 + multiplier: 2 + + it 'should parse unknown', -> + assert.deepEqual Command.parse("foo"), + isValid: false + string: "foo" + + describe 'Multiples', -> + + it 'should succeed for fills', -> + assert.strictEqual Command.isFill("~*"), true + assert.strictEqual Command.isFill("$*"), true + assert.strictEqual Command.isFill("1px*"), true + + it 'should fail for non-fills', -> + assert.strictEqual Command.isFill("foo"), false + assert.strictEqual Command.isFill("10px*2"), false + + it 'should not count bad values', -> + assert.equal Command.count("foo"), null + + it 'should count wildcard multiples', -> + assert.equal Command.count("~"), 1 + assert.equal Command.count("~*"), 1 + assert.equal Command.count("~*2"), 2 + + it 'should count arbitrary multiples', -> + assert.equal Command.count("1px"), 1 + assert.equal Command.count("1px*"), 1 + assert.equal Command.count("1px*2"), 2 + + it 'should count variable multiples', -> + assert.equal Command.count("$"), 1 + assert.equal Command.count("$*"), 1 + assert.equal Command.count("$*2"), 2 + + describe 'Strings', -> + + it 'should convert guide commands to strings', -> + assert.equal "|", Command.toString + isValid: true + isGuide: true + + it 'should convert variable commands to strings', -> + assert.equal "$", Command.toString + isValid: true + isVariable: true + isFill: false + id: "$" + multiplier: 1 + assert.equal "$foo*2", Command.toString + isValid: true + isVariable: true + isFill: false + id: "$foo" + multiplier: 2 + + it 'should convert wildcard commands to strings', -> + assert.equal "~", Command.toString + isValid: true + isWildcard: true + isFill: false + multiplier: 1 + assert.equal "~*2", Command.toString + isValid: true + isWildcard: true + isFill: false + multiplier: 2 + + it 'should convert arbitrary commands to strings', -> + assert.equal "10px*2", Command.toString + isValid: true + isArbitrary: true + isFill: false + isPercent: false + unit: + string: "10px*2" + value: 10 + type: "px" + base: 10 + multiplier: 2 + + it 'should simplify strings', -> + assert.equal "~", Command.toSimpleString("~*2") + assert.equal "~", Command.toSimpleString + isValid: true + isWildcard: true + isFill: false + multiplier: 2 + assert.equal Command.toSimpleString(Command.parse("10px*")), "10px" diff --git a/test/test-grid-notation.coffee b/test/test-grid-notation.coffee new file mode 100644 index 0000000..f6d107b --- /dev/null +++ b/test/test-grid-notation.coffee @@ -0,0 +1,307 @@ +assert = require "assert" +GridNotation = require " + #{ process.cwd() }/src/GridNotation.coffee +" +GN = GridNotation.notation +Unit = GridNotation.unit +Command = GridNotation.command + + +describe 'Grid Notation', -> + + describe "Cleaning", -> + + it 'should clean successfully', -> + gn = """ + $=|10px| + |$|~|10px|(v) + """ + expected = """ + $ = | 10px | + | $ | ~ | 10px | ( vl ) + """ + assert.equal GN.clean(gn), expected + + it 'should clean empty grids', -> + assert.equal GN.clean("$=|10px|"), "$ = | 10px |" + + it 'should detect bad commands in grids', -> + gn = """ + $=|10px| + |$|~|foo|(v) + """ + expected = """ + $ = | 10px | + | $ | ~ | {foo} | ( vl ) + """ + assert.equal GN.clean(gn), expected + + it 'should detect bad commands in variables', -> + assert.equal GN.clean("$=|foo|"), "$ = | {foo} |" + + it 'should detect fills in variables', -> + assert.equal GN.clean("$=|10px*|"), "$ = | {10px*} |" + + it 'should detect bad adjustments', -> + assert.equal GN.clean("|~|(hl,foo|~|~)"), "| ~ | ( hl, {foo} | ~ | ~ )" + + it 'should detect undefined variables in variables', -> + gn = """ + $ = ~ + | $a | + """ + expected = """ + $ = ~ + | {$a} | ( hl ) + """ + assert.equal GN.clean(gn), expected + + it 'should detect undefined variables in grids', -> + assert.equal GN.clean("$ = $a"), "$ = {$a}" + + it 'should detect fill variables containing wildcards', -> + gn = """ + $ = ~ + $* + """ + expected = """ + $ = ~ + {$*} ( hl ) + """ + assert.equal GN.clean(gn), expected + + it 'should detect fill wildcards', -> + assert.equal GN.clean("~*"), "{~*} ( hl )" + + it 'should detect multiple fills', -> + assert.equal GN.clean("10px*|10px*"), "10px* | {10px*} ( hl )" + + it 'should detect multiple fills when used in variables', -> + gn = """ + $ = 10px* + $ | 10px* + """ + expected = """ + $ = {10px*} + {$} | {10px*} ( hl ) + """ + assert.equal GN.clean(gn), expected + + describe "Parsing", -> + + info = + hasOpenDocuments: true + isSelection: false + width: 100 + height: 100 + offsetX: 0 + offsetY: 0 + ruler: 'pixels' + existingGuides: [] + + it 'should fail when parsing invalid grid notation', -> + assert.deepEqual GN.parse(), null + + it 'should parse succesfully', -> + out = [ { location: 10, orientation: 'h' }, + { location: 20, orientation: 'h' }, + { location: 10, orientation: 'v' }, + { location: 20, orientation: 'v' } ] + + assert.deepEqual GN.parse(""" + $ = 10px | + $ | $ (hl) + $A = 10% + 10px | $A | (vl) + """, info), out + + describe "Objectification", -> + + it 'should objectify', -> + assert.equal GN.objectify("|$|~|foo| (10px|~|10px)").grids.length, 1 + + describe 'Stringification', -> + + it 'should stringify command strings', -> + assert.equal GN.stringifyCommands(GN.parseCommands("|10px|")), "| 10px |" + assert.equal GN.stringifyCommands(GN.parseCommands("|foo|")), "| {foo} |" + + it 'should stringify params', -> + gn = GN.parseGrid "|~|(vl, ~|~|~)" + assert.equal GN.stringifyParams(gn.params), "( vl, ~ | ~ | ~ )" + gn = GN.parseGrid "|~|(v)" + assert.equal GN.stringifyParams(gn.params), "( vl )" + + describe 'Parse grid', -> + + it 'should parse grids', -> + assert GN.parseGrid("|10px|").commands.length is 3 + assert.equal GN.parseGrid("|~|~|").wildcards.length, 2 + + describe 'Parse variable declarations', -> + + it 'should parse variables', -> + assert.deepEqual GN.parseVariable("$ = |"), + id: "$" + commands: [ + isValid: true + isGuide: true + ] + + it 'should safely parse empty variables', -> + assert.deepEqual GN.parseVariable("$ = "), + id: "$" + commands: [] + + describe 'Parse Commands', -> + + it 'should return an empty array when no commands are given', -> + assert.strictEqual GN.parseCommands().length, 0 + assert.strictEqual GN.parseCommands("").length, 0 + + it 'should parse unknown commands', -> + assert.equal GN.parseCommands("foo")[0].isValid, false + + it 'should parse guide commands', -> + assert GN.parseCommands("|")[0].isValid + + it 'should parse arbitrary commands', -> + assert GN.parseCommands("10px")[0].isValid + assert GN.parseCommands("10px*")[0].isValid + assert GN.parseCommands("10px*2")[0].isValid + + it 'should parse variable commands', -> + assert GN.parseCommands("$")[0].isValid + assert GN.parseCommands("$A*")[0].isValid + assert GN.parseCommands("$foo*2")[0].isValid + + it 'should parse wildcard commands', -> + assert GN.parseCommands("~")[0].isValid + assert GN.parseCommands("~*")[0].isValid is false + assert GN.parseCommands("~*2")[0].isValid + + describe 'Parse options', -> + + it 'should parse orientation', -> + assert GN.parseOptions("h").orientation, "h" + assert GN.parseOptions("v").orientation, "v" + + it 'should overwrite duplicate options', -> + assert GN.parseOptions("hv").orientation, "v" + + it 'should ignore case for options', -> + assert GN.parseOptions("H").orientation, "h" + + it 'should parse remainder', -> + assert GN.parseOptions("f").remainder, "f" + assert GN.parseOptions("c").remainder, "c" + assert GN.parseOptions("l").remainder, "l" + + it 'should parse calculation', -> + assert.equal GN.parseOptions("p").calculation, "p" + + it 'should find first offset', -> + assert GN.parseAdjustments("~|~|~").firstOffset + assert GN.parseAdjustments("~|~|").firstOffset + assert GN.parseAdjustments("~|~").firstOffset + assert GN.parseAdjustments("~|").firstOffset + assert GN.parseAdjustments("~").firstOffset + + it 'should find widths', -> + assert GN.parseAdjustments("~|~|~").width + assert GN.parseAdjustments("~|~|").width + assert GN.parseAdjustments("|~|~").width + + it 'should find last offset', -> + assert GN.parseAdjustments("~|~|~").lastOffset + assert GN.parseAdjustments("|~|~").lastOffset + assert GN.parseAdjustments("~|~").lastOffset + assert GN.parseAdjustments("|~").lastOffset + + it 'should mark single adjustments as first offset', -> + assert GN.parseAdjustments("~").firstOffset + assert !GN.parseAdjustments("~").lastOffset + + it 'should return no adjustments when none are given', -> + assert !GN.parseAdjustments("").firstOffset + assert !GN.parseAdjustments("").width + assert !GN.parseAdjustments("").lastOffset + + it 'should detect commands', -> + assert GN.isCommands '|' + assert GN.isCommands '10px' + + it 'should detect non-commands', -> + assert.equal GN.isCommands('foo'), false + assert.equal GN.isCommands(''), false + + describe 'Validation', -> + + it 'should reject fills in variables', -> + obj = GN.objectify("$ = 10px*") + assert.equal GN.validate(obj).isValid, false + + it 'should reject empty grids', -> + obj = GN.objectify("") + assert.equal GN.validate(obj).isValid, false + + it 'should reject fill variables containing wildcards', -> + obj = GN.objectify """ + $ = ~ + $* + """ + assert.equal GN.validate(obj).isValid, false + + it 'should reject undefined variables in variables', -> + obj = GN.objectify "$ = $a" + assert.equal GN.validate(obj).isValid, false + + it 'should reject undefined variables in grids', -> + obj = GN.objectify """ + $ = 10px + $a + """ + assert.equal GN.validate(obj).isValid, false + + it 'should reject multiple fills in grids', -> + obj = GN.objectify "|10px*|10px*|" + assert.equal GN.validate(obj).isValid, false + + it 'should reject fills if a variable already contains one', -> + obj = GN.objectify """ + $ = 10px* + |10px*| + """ + assert.equal GN.validate(obj).isValid, false + + it 'should reject bad alignment params', -> + assert GN.validate(GN.objectify("~ ( ~ | ~ | ~ )")).isValid + assert !GN.validate(GN.objectify("~ ( foo | ~ | ~ )")).isValid + assert !GN.validate(GN.objectify("~ ( ~ | foo | ~ )")).isValid + assert !GN.validate(GN.objectify("~ ( ~ | ~ | foo )")).isValid + + describe 'Utilities', -> + + it 'should clean pipes', -> + assert.equal GN.pipeCleaner("|10px|~|10px|"), "| 10px | ~ | 10px |" + + it 'should expand commands', -> + given = [ + isValid: true + isWildcard: true + isFill: false + multiplier: 2 + ] + expected = [ + isValid: true + isWildcard: true + isFill: false + multiplier: 1 + , + isValid: true + isWildcard: true + isFill: false + multiplier: 1 + ] + + assert.deepEqual GN.expandCommands(given), expected diff --git a/test/test-scenarios.coffee b/test/test-scenarios.coffee new file mode 100644 index 0000000..edd250a --- /dev/null +++ b/test/test-scenarios.coffee @@ -0,0 +1,300 @@ +assert = require "assert" +GridNotation = require " + #{ process.cwd() }/src/GridNotation.coffee +" +GN = GridNotation.notation +Unit = GridNotation.unit +Command = GridNotation.command + +info = + hasOpenDocuments: true + isSelection: false + width: 100 + height: 100 + offsetX: 0 + offsetY: 0 + ruler: 'pixels' + existingGuides: [] + +# There are millions of combinations of things to test. This file includes +# common grid scenarios to see if the math is being done correctly. +describe 'Scenarios', -> + + describe 'basic', -> + + it 'should place a single guide', -> + assert.deepEqual GN.parse(""" + | + """, info), [ + { location: 0, orientation: "h" } + ] + + it 'should work with arbitray commands', -> + assert.deepEqual GN.parse(""" + | 10px | + """, info), [ + { location: 0, orientation: "h" } + { location: 10, orientation: "h" } + ] + + it 'should handle percents', -> + assert.deepEqual GN.parse(""" + | 10% | + """, info), [ + { location: 0, orientation: "h" } + { location: 10, orientation: "h" } + ] + + it 'should convert inches', -> + assert.deepEqual GN.parse(""" + | 1in | + """, info), [ + { location: 0, orientation: "h" } + { location: 72, orientation: "h" } + ] + + describe 'wildcards', -> + + it 'should find midpoints', -> + assert.deepEqual GN.parse(""" + ~ | ~ (vl) + ~ | ~ (hl) + """, info), [ + { location: 50, orientation: "v" } + { location: 50, orientation: "h" } + ] + + it 'should find edges', -> + assert.deepEqual GN.parse(""" + | ~ | (vl) + | ~ | (hl) + """, info), [ + { location: 0, orientation: "v" } + { location: 100, orientation: "v" } + { location: 0, orientation: "h" } + { location: 100, orientation: "h" } + ] + + it 'should work with two columns', -> + assert.deepEqual GN.parse(""" + | ~ | ~ | (vl) + """, info), [ + { location: 0, orientation: "v" } + { location: 50, orientation: "v" } + { location: 100, orientation: "v" } + ] + + it 'should work with three rows', -> + assert.deepEqual GN.parse(""" + | ~ | ~ | ~ | (hlp) + """, info), [ + { location: 0, orientation: "h" } + { location: 33, orientation: "h" } + { location: 66, orientation: "h" } + { location: 100, orientation: "h" } + ] + + describe 'variables', -> + + it 'should work with single character variables', -> + assert.deepEqual GN.parse(""" + $ = 10px + | $ | + """, info), [ + { location: 0, orientation: "h" } + { location: 10, orientation: "h" } + ] + + it 'should work with multi-character variables', -> + assert.deepEqual GN.parse(""" + $foo = 10px + | $foo | + """, info), [ + { location: 0, orientation: "h" } + { location: 10, orientation: "h" } + ] + + it 'should work with cascading variables', -> + assert.deepEqual GN.parse(""" + $ = 10px + $a = $ | $ + | $a | + """, info), [ + { location: 0, orientation: "h" } + { location: 10, orientation: "h" } + { location: 20, orientation: "h" } + ] + + describe 'multiples', -> + + it 'should work with arbitrary multiples', -> + assert.deepEqual GN.parse(""" + 10px*3 | + """, info), [ + { location: 30, orientation: "h" } + ] + + it 'should work with fills', -> + assert.deepEqual GN.parse(""" + 10px* | + """, info), [ + { location: 100, orientation: "h" } + ] + + it 'should work with variable fills', -> + assert.deepEqual GN.parse(""" + $ = 10px + $* | + """, info), [ + { location: 100, orientation: "h" } + ] + + describe 'adjustments', -> + + it 'should work with a left offset', -> + assert.deepEqual GN.parse(""" + | 10px | ( 10px ) + """, info), [ + { location: 10, orientation: "h" } + { location: 20, orientation: "h" } + ] + + it 'should right align', -> + assert.deepEqual GN.parse(""" + | 10px | ( ~ | ) + """, info), [ + { location: 90, orientation: "h" } + { location: 100, orientation: "h" } + ] + + it 'should work with a right offset', -> + assert.deepEqual GN.parse(""" + | 10px | ( ~ | 10px ) + """, info), [ + { location: 80, orientation: "h" } + { location: 90, orientation: "h" } + ] + + it 'should work with a width', -> + assert.deepEqual GN.parse(""" + | ~ | ( | 50px | ) + """, info), [ + { location: 0, orientation: "h" } + { location: 50, orientation: "h" } + ] + + it 'should work with a left offset and a width', -> + assert.deepEqual GN.parse(""" + | ~ | ( 10px | 50px | ) + """, info), [ + { location: 10, orientation: "h" } + { location: 60, orientation: "h" } + ] + + it 'should work with a right offset and a width', -> + assert.deepEqual GN.parse(""" + | ~ | ( ~ | 50px | 10px ) + """, info), [ + { location: 40, orientation: "h" } + { location: 90, orientation: "h" } + ] + + it 'should work with a right offset and a left offset', -> + assert.deepEqual GN.parse(""" + | ~ | ( 10px | 10px ) + """, info), [ + { location: 10, orientation: "h" } + { location: 90, orientation: "h" } + ] + + it 'should work with a centered left and right offset', -> + assert.deepEqual GN.parse(""" + | 10px | ( ~ | ~ ) + """, info), [ + { location: 45, orientation: "h" } + { location: 55, orientation: "h" } + ] + + it 'should work with a centered left and right offset and wildcard', -> + assert.deepEqual GN.parse(""" + | ~ | ( ~ | ~ ) + """, info), [ + { location: 0, orientation: "h" } + { location: 100, orientation: "h" } + ] + + it 'should work with left, right, and width wildcards', -> + assert.deepEqual GN.parse(""" + | ~ | ( ~ | ~ | ~ ) + """, info), [ + { location: 0, orientation: "h" } + { location: 100, orientation: "h" } + ] + + it 'should work with left, right, and width wildcards', -> + assert.deepEqual GN.parse(""" + | ~ | ( 10px | ~ | 10px ) + """, info), [ + { location: 10, orientation: "h" } + { location: 90, orientation: "h" } + ] + + it 'should work with left, right, and width units', -> + assert.deepEqual GN.parse(""" + | ~ | ( 10px | 20px | 10px ) + """, info), [ + { location: 10, orientation: "h" } + { location: 30, orientation: "h" } + ] + + it 'should work with all guide markers', -> + assert.deepEqual GN.parse(""" + | ~ | ( | | | | | ) + """, info), [ + { location: 0, orientation: "h" } + { location: 100, orientation: "h" } + ] + + it 'should work with a left aligned width larger than the document', -> + assert.deepEqual GN.parse(""" + | ~ | ( | 200px | ) + """, info), [ + { location: 0, orientation: "h" } + { location: 200, orientation: "h" } + ] + + it 'should work with a center aligned width larger than the document', -> + assert.deepEqual GN.parse(""" + | ~ | ( ~ | 200px | ~ ) + """, info), [ + { location: -50, orientation: "h" } + { location: 150, orientation: "h" } + ] + + it 'should work with a right aligned width larger than the document', -> + assert.deepEqual GN.parse(""" + | ~ | ( ~ | 200px | ) + """, info), [ + { location: -100, orientation: "h" } + { location: 100, orientation: "h" } + ] + + describe 'resolution', -> + highRes = + hasOpenDocuments: true + isSelection: false + width: 100 + height: 100 + offsetX: 0 + offsetY: 0 + resolution: 300 + ruler: 'pixels' + existingGuides: [] + + it 'should respect resolution in inches', -> + assert.deepEqual GN.parse(""" + | 1in | + """, highRes), [ + { location: 0, orientation: "h" } + { location: 300, orientation: "h" } + ] diff --git a/test/test-units.coffee b/test/test-units.coffee new file mode 100644 index 0000000..c1af2e1 --- /dev/null +++ b/test/test-units.coffee @@ -0,0 +1,105 @@ +assert = require "assert" +GridNotation = require " + #{ process.cwd() }/src/GridNotation.coffee +" +GN = GridNotation.notation +Unit = GridNotation.unit +Command = GridNotation.command + +describe 'Units', -> + + describe 'Object from string', -> + + it 'should return null if given nothing', -> + assert.strictEqual Unit.parse(""), null + assert.strictEqual Unit.parse(), null + + it 'should return null when a bad value is given', -> + assert.strictEqual Unit.parse("foo"), null + assert.strictEqual Unit.parse("1foo"), null + + it 'should return null base if nothing is given', -> + assert.strictEqual Unit.parse("1foo"), null + + it 'should return a unit object when a unit pair is given', -> + assert.deepEqual Unit.parse("1px"), + string: "1px" + value: 1 + type: "px" + base: 1 + + it 'should should allow spaces', -> + assert.deepEqual Unit.parse("1 px"), + string: "1px" + value: 1 + type: "px" + base: 1 + + describe 'Base value from unit object', -> + + it 'should return null when a bad value is given', -> + assert.deepEqual Unit.asBaseUnit("foo"), null + + it 'should return an integer when one is given', -> + assert.deepEqual Unit.asBaseUnit(), null + assert.deepEqual Unit.asBaseUnit({}), null + assert.deepEqual Unit.asBaseUnit(""), null + + it 'should return an integer when given a unit object', -> + assert.deepEqual Unit.asBaseUnit(Unit.parse("1cm")), 28.346456692913385 + assert.deepEqual Unit.asBaseUnit(Unit.parse("1in")), 72 + assert.deepEqual Unit.asBaseUnit(Unit.parse("1mm")), 2.8346456692913384 + assert.deepEqual Unit.asBaseUnit(Unit.parse("1px")), 1 + assert.deepEqual Unit.asBaseUnit(Unit.parse("1pt")), 1 + assert.deepEqual Unit.asBaseUnit(Unit.parse("1pica")), 12 + + it 'should adjust for resolution when resolution is given', -> + Unit.resolution = 300 + assert.deepEqual Unit.asBaseUnit(Unit.parse("1in"), 300), 300 + Unit.resolution = 72 + + describe 'Preferred name', -> + + it 'should not get the preferred name if nothing is given', -> + assert.equal Unit.preferredName(), null + assert.equal Unit.preferredName(""), null + + it 'should get preferred name for unit strings', -> + + assert.equal Unit.preferredName(), null + assert.equal Unit.preferredName(""), null + + cm = ['centimeter', 'centimeters', 'centimetre', 'centimetres', 'cm'] + for str in cm + assert.equal Unit.preferredName(str), "cm" + + for str in ['inch', 'inches', 'in'] + assert.equal Unit.preferredName(str), "in" + + mm = ['millimeter', 'millimeters', 'millimetre', 'millimetres', 'mm'] + for str in mm + assert.equal Unit.preferredName(str), "mm" + + for str in ['pixel', 'pixels', 'px'] + assert.equal Unit.preferredName(str), "px" + + for str in ['point', 'points', 'pts', 'pt'] + assert.equal Unit.preferredName(str), "points" + + for str in ['pica', 'picas'] + assert.equal Unit.preferredName(str), "picas" + + for str in ['percent', 'pct', '%'] + assert.equal Unit.preferredName(str), "%" + + describe 'To string', -> + + it 'should return null when given nothing', -> + assert.strictEqual Unit.toString(), null + assert.strictEqual Unit.toString(""), null + + it 'should return string when given a unit object', -> + assert.equal Unit.toString(Unit.parse("1px")), "1px" + + it 'should return string when given a string', -> + assert.equal Unit.toString("1px"), "1px" From 6e0334e529f04ac92f6eb87ad30c2991029e2c92 Mon Sep 17 00:00:00 2001 From: Cameron McEfee Date: Sat, 21 Jun 2014 09:10:11 -0700 Subject: [PATCH 02/16] call it what it is --- test/{test-scenarios.coffee => test-math.coffee} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename test/{test-scenarios.coffee => test-math.coffee} (99%) diff --git a/test/test-scenarios.coffee b/test/test-math.coffee similarity index 99% rename from test/test-scenarios.coffee rename to test/test-math.coffee index edd250a..c17d0cc 100644 --- a/test/test-scenarios.coffee +++ b/test/test-math.coffee @@ -18,7 +18,7 @@ info = # There are millions of combinations of things to test. This file includes # common grid scenarios to see if the math is being done correctly. -describe 'Scenarios', -> +describe 'Math', -> describe 'basic', -> From 2a7ad9852b96c3e88a4f0b8a10f749ad9b22cf3f Mon Sep 17 00:00:00 2001 From: Cameron McEfee Date: Sat, 21 Jun 2014 09:17:47 -0700 Subject: [PATCH 03/16] update npm test --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2620e8d..05cf30d 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ }, "main": "lib/GridNotation.js", "scripts": { - "test": "script/test" + "test": "mocha --compilers coffee:coffee-script/register" }, "repository" : { "type" : "git", From c58e49426b30727b7d288bd7e063e0aeda4c6ee1 Mon Sep 17 00:00:00 2001 From: Cameron McEfee Date: Sat, 21 Jun 2014 10:38:03 -0700 Subject: [PATCH 04/16] add script/compile --- script/compile | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100755 script/compile diff --git a/script/compile b/script/compile new file mode 100755 index 0000000..01d209b --- /dev/null +++ b/script/compile @@ -0,0 +1,6 @@ +#!/bin/sh +# usage: script/compile +# +# compile Grid Notation for publishing + +coffee -o . -c ./src/GridNotation.coffee From c97da41738aef0a3dbb34b631e5e4e60785f71d8 Mon Sep 17 00:00:00 2001 From: Cameron McEfee Date: Sat, 21 Jun 2014 10:48:14 -0700 Subject: [PATCH 05/16] update readme --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e015611..c2365aa 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,25 @@ # Grid notation -**This repository is a work in progress. The contents likely will not work yet.** +**This repository is a work in progress and is not yet documented.** Grid notation is a way to “write” grids. For more information, see the [spec](SPEC.md). -## Setup +### Setup ``` npm install ``` -## Development +### Development + +Grid notation's tests use [Mocha](http://visionmedia.github.io/mocha/). To run the test watcher, run the following in the terminal: ``` script/test ``` + +### Compile + +``` +script/compile +``` From fdf0e0934bc4c8ae5226c8be4900e77c01649536 Mon Sep 17 00:00:00 2001 From: Cameron McEfee Date: Sat, 21 Jun 2014 11:11:42 -0700 Subject: [PATCH 06/16] Revise the spec to match new terminology --- SPEC.md | 76 +++++++++++++++++++++++++++------------------------------ 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/SPEC.md b/SPEC.md index da1b5e4..a504ecd 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,19 +1,18 @@ -# GuideGuide Notation +# Grid notation -GuideGuide’s grid form is a great tool, but it is somewhat limited in what it can do. To give you more flexibility with your grids, GuideGuide includes a feature called GuideGuide Notation, a special language that allows you to give instructions to GuideGuide, telling it where you would like to put guides within your document or selection. +After a few years of using GuideGuide, I became frustrated that I couldn't move beyond simple grid structures. What if I wanted a sidebar? What if I wanted to reposition the grid in the document? Grid notation is written language for given commands to a grid parser. A string goes in, an array of guides comes out. ## Grids -> <commands> ( <options>, <first offset> | <width> | <last offset> ) +> <commands> [( [<options>][, <first offset> | <width> | <last offset> ])] -A grid is a collection of guides and gaps across a single dimentional plane. GuideGuide will split the string into an array of guides and gaps and iterate through them, following them like instructions. Starting at 0, for each gap, GuideGuide will advance its insertion point by the value of the gap. When GuideGuide encounters a guide, it will place a guide at the current location of the insertion point. This will continue until all guides and gaps have been parsed. +A grid is a collection of commands across a single dimensional plane. The parser will split the string into an array of guide and gaps commands and iterate through them, following them like instructions. Starting at 0, for each gap, the parser will advance its insertion point by the value of the gap. When the parser encounters a guide command, it will place a guide at the current location of the insertion point. This will continue until all guides and gaps have been parsed. -GuideGuide takes into acount the size of the document or selection when calculating percentages, wildcards, and fills. +The parser takes into account the size of the document or selection when calculating percentages, wildcards, and fills. -Each guide or gap must be separated by a space character. Newlines are used to define multiple grids in one string. +Each command must be separated by a space character. Newlines are used to define multiple grids in one string. -It is possible to change the way GuideGuide renders the grid by specifiying options at the end of the grid, within parentheses. A width for the grid can be specified, as well as an offset to start rendering the grid. Whitespace in the options are is ignored. -e. +It is possible to change the way the parser renders the grid by specifying options at the end of the grid, within parentheses. A width for the grid can be specified, as well as left and right offsets to position the grid. Whitespace in the options are is ignored. #### Examples @@ -48,7 +47,7 @@ e. > <value><unit> -Unit objects are value-unit pairs that indicate a measurement. +Unit objects are value-unit pairs that indicate a measurement. The unit is required. #### Examples @@ -62,17 +61,17 @@ Unit objects are value-unit pairs that indicate a measurement. ## Guides -Guides are represented by a pipe `|`. These tell GuideGuide to place a guide at the current insertion point. +Guides are represented by a pipe `|`. These tell the parser to place a guide at the current insertion point. -## Gaps +## Commands -Gaps are unit objects or variables combined with multipliers to define spaces between gaps. +Commands are unit objects or variables combined with multipliers to define spaces between guides. -### Arbitrary gaps +### Arbitrary commands -> <value><unit>*<multiplier> +> <value><unit>[*[<multiplier>]] -An arbitrary gap is represented by a Unit Object and an optional multiplier. Arbitrary gaps are the width of the unit specified. Arbitrary can be positive or negative. Due to this, it is possible to traverse backwards and forwards. +An arbitrary command is represented by a Unit Object and an optional multiplier. Arbitrary commands are the width of the unit specified. Arbitrary can be positive or negative. Due to this, it is possible to traverse backwards and forwards. #### Examples @@ -80,16 +79,15 @@ An arbitrary gap is represented by a Unit Object and an optional multiplier. Arb `| 10px | 10px | 10px|` - - one half inch column, one inch column, one half inch column `| .5in | 1in | .5in |` -### Wildcard gaps +### Wildcard commands -A wildcard gap is represented by a tilde `~`. Any area within a grid that remains after all of the arbitrary gaps have been calculated will be evenly distributed amongst the wildcards present in a grid. +> ~[*[<multiplier>]] -Due to their flexible nature, wildcards can be used to position a grid. When a single wildcard is placed to the left of a GGN string, it will force the grid to render on the right side. Similarly, a GGN string with wildcards on either end will be centered. +A wildcard command is represented by a tilde `~`. Any area within a grid that remains after all of the arbitrary commands have been calculated will be evenly distributed amongst the wildcards present in a grid. #### Examples @@ -103,19 +101,19 @@ Due to their flexible nature, wildcards can be used to position a grid. When a s ### Variables -Variables allow you to define and reuse collections of gaps within a grid. Variables are composed of a definition and a call. +Variables allow you to define and reuse collections of guides and commands within a grid. Variables are composed of two parts: a definition and a call. #### Define -> $<id> = <gaps> +> $[<id>] = <gaps> -A variable definition is represented by a dollar sign `$`, an optional id, an equals sign, and then a collection of gaps and guides separated by spaces. +A variable definition is represented by a dollar sign `$`, an optional id, an equals sign, and then a collection of commands and guides separated by spaces. While it is possible to define a variable that contains no guides, this won't often be useful as the results of the variable will not be visible (since it contains no guides). #### Call -> $<id>*<multiplier> +> $[<id>][*[<multiplier>]] A variable call is represented by a dollar sign `$`, an optional id, and an optional multiplier. Anywhere a variable call occurs GuideGuide will replace its contents with the contents of its variable definition. A variable must be defined before it is called. @@ -137,15 +135,15 @@ a three column grid ### Multiples and fills -Arbitray, wildcard, and variable gaps can accept a final modifier that will duplicate that gap the number of times specified. These are most helpful when used with variables, as it is possible to specify both gaps and guide together. Multiples and fills can be specified on gaps, but since the result of the mutiplied gap is not visible, their usefulness is rare. +Arbitrary, wildcard, and variable commands can accept a final modifier that will duplicate that command the number of times specified. These are most helpful when used with variables, as it is possible to specify both commands and guide together. Multiples and fills can be specified on non-guide commands, but since the result of the multiplied command is not visible, their usefulness is rare. #### Multiple -A multiple is represented by an asterisk `*` followed by a number. The gap will be recreated sequentially the number of times specified by the multiple +A multiple is represented by an asterisk `*` followed by a number. The command will be recreated sequentially the number of times specified by the multiple #### Examples -- Two thirds column, one third column +- Two thirds width column, one third width column `| ~*2 | ~ |` @@ -158,7 +156,7 @@ A multiple is represented by an asterisk `*` followed by a number. The gap will #### Fill -A fill is represented by a asterisk `*` folowed by nothing and is a gap that will be recreated squentially until it fills the remaining space in the grid. This is useful for cases such as creating a baseline grid, or filling a space with as many columns and gutters of a certain width as will fit. +A fill is represented by a asterisk `*` followed by nothing and is a gap that will be recreated sequentially until it fills the remaining space in the grid. This is useful for cases such as creating a baseline grid, or filling a space with as many columns and gutters of a certain width as will fit. - A sixteen pixel baseline grid @@ -170,7 +168,7 @@ A fill is represented by a asterisk `*` folowed by nothing and is a gap that wil ## Grid Options -> (<modifiers>, <adjustments>) +> ([<modifiers>][, <adjustments>]) Optional values to modify how the grid is created. They are wrapped in parens and broken into two sections separated by a comma. @@ -191,19 +189,19 @@ Determines the orientation of the guides in the grid. ### Remainder pixel distribution -Determines to which wildcards GuideGuide adds remainder pixels when the columns do not divide equally into the total width of the grid area. +Determines to which wildcards the parser adds remainder pixels when the columns do not divide equally into the total width of the grid area. This setting is only used when "pixel" calculation is specified. #### Values: -- `f`*(default)* first (left/top) +- `f` first (left/top) - `c` center -- `l` last (right/bottom) +- `l` *(default)* last (right/bottom) ### Calculation -Determines whether GuideGuide is strict about integers when calculating pixels +Determines whether Parser is strict about integers when calculating pixels. #### Values: @@ -213,11 +211,11 @@ Determines whether GuideGuide is strict about integers when calculating pixels ### Grid adjust -> <left offset> | <width> | <right offset> +> [<left offset>][ | <width> | ][ <right offset>] -A string similar to Grid notation that specifies the left and right offsets and width of the grid, separated by pipes (which represent the edges of the grid). +A string similar to grid notation that specifies the left and right offsets and width of the grid, separated by pipes (which represent the edges of the grid). -Width is defined by enclosing a unit object in pipes. The tilde `~` can be used similarly to the way wildcards are used. +Width is defined by enclosing a unit object in pipes. The tilde `~` is used similarly to the way wildcards are used. #### Examples: @@ -241,7 +239,7 @@ Position works similarly to how CSS works. Think of the `~` as "auto". To define A one hundred pixel wide grid, twenty pixels from the left side (the right offset is ignored if a left and right offset is specified with a defined width). - `(v, 20px|~|20px)` - A grid with a width that is 40px less than the width of the document, with 20px space on either side. + A grid with a (automaticlly calculated) width that is 40px less than the width of the document, with 20px space on either side. - `(v, ~|100px|)` A right aligned, one hundred pixel wide grid. @@ -249,7 +247,7 @@ Position works similarly to how CSS works. Think of the `~` as "auto". To define - `(v, ~|100px|~)` A centered, one hundred pixel wide grid. -For width to be specified, it **must** have a pipe on either side. If only one pipe between two values is specified, the values will be treated as left and right offsets. If a third pipe exists, it and anything after it is ignored. +For width to be specified, it **must** have a pipe on either side. If only one pipe between two values is specified, the values will be treated as left and right offsets. #### Examples: @@ -258,10 +256,8 @@ For width to be specified, it **must** have a pipe on either side. If only one p ## Errors -GuideGuide String Notation errors will be denoted in curly brackets. Directly following a bracketed error will be a set of brackets containing a comma separated list of error IDs. Explanation of the errors will be printed below the grid. +When cleaning a guide notation string, errors will be denoted in curly brackets. Directly following a bracketed error will be a set of brackets containing a comma separated list of error IDs. ``` | 10px | { 10foo [1]} | 10px| - -# 1. Unrecognized unit type ``` From 6c9a59172919f02aeeef9ee96859232bd7487df9 Mon Sep 17 00:00:00 2001 From: Cameron McEfee Date: Sat, 21 Jun 2014 11:14:12 -0700 Subject: [PATCH 07/16] change arbitrary to explicit --- SPEC.md | 8 ++++---- src/GridNotation.coffee | 38 ++++++++++++++++++------------------- test/test-commands.coffee | 40 +++++++++++++++++++-------------------- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/SPEC.md b/SPEC.md index a504ecd..55fbfc2 100644 --- a/SPEC.md +++ b/SPEC.md @@ -67,11 +67,11 @@ Guides are represented by a pipe `|`. These tell the parser to place a guide at Commands are unit objects or variables combined with multipliers to define spaces between guides. -### Arbitrary commands +### Explicit commands > <value><unit>[*[<multiplier>]] -An arbitrary command is represented by a Unit Object and an optional multiplier. Arbitrary commands are the width of the unit specified. Arbitrary can be positive or negative. Due to this, it is possible to traverse backwards and forwards. +An explicit command is represented by a Unit Object and an optional multiplier. Explicit commands are the width of the unit specified. Explicit can be positive or negative. Due to this, it is possible to traverse backwards and forwards. #### Examples @@ -87,7 +87,7 @@ An arbitrary command is represented by a Unit Object and an optional multiplier. > ~[*[<multiplier>]] -A wildcard command is represented by a tilde `~`. Any area within a grid that remains after all of the arbitrary commands have been calculated will be evenly distributed amongst the wildcards present in a grid. +A wildcard command is represented by a tilde `~`. Any area within a grid that remains after all of the explicit commands have been calculated will be evenly distributed amongst the wildcards present in a grid. #### Examples @@ -135,7 +135,7 @@ a three column grid ### Multiples and fills -Arbitrary, wildcard, and variable commands can accept a final modifier that will duplicate that command the number of times specified. These are most helpful when used with variables, as it is possible to specify both commands and guide together. Multiples and fills can be specified on non-guide commands, but since the result of the multiplied command is not visible, their usefulness is rare. +Explicit, wildcard, and variable commands can accept a final modifier that will duplicate that command the number of times specified. These are most helpful when used with variables, as it is possible to specify both commands and guide together. Multiples and fills can be specified on non-guide commands, but since the result of the multiplied command is not visible, their usefulness is rare. #### Multiple diff --git a/src/GridNotation.coffee b/src/GridNotation.coffee index c01adf8..eaaf0e2 100644 --- a/src/GridNotation.coffee +++ b/src/GridNotation.coffee @@ -33,17 +33,17 @@ class GridNotation adjustRemainder = 0 wildcards = find grid.commands, (el) -> el.isWildcard - # Measure arbitrary commands - arbitrary = find grid.commands, (el) -> el.isArbitrary and !el.isFill - arbitrarySum = 0 - arbitrarySum += command.unit.base for command in arbitrary + # Measure explicit commands + explicit = find grid.commands, (el) -> el.isExplicit and !el.isFill + explicitSum = 0 + explicitSum += command.unit.base for command in explicit # If a width was specified, position it. If it wasn't, subtract the offsets # from the boundaries. if grid.params.width?.unit?.base adjustRemainder = originalWidth - grid.params.width?.unit.base else - adjustRemainder = originalWidth - arbitrarySum if wildcards.length == 0 + adjustRemainder = originalWidth - explicitSum if wildcards.length == 0 measuredWidth -= grid.params.firstOffset?.unit?.base || 0 measuredWidth -= grid.params.lastOffset?.unit?.base || 0 if adjustRemainder > 0 @@ -64,7 +64,7 @@ class GridNotation # Adjust the first offset. offset += grid.params.firstOffset?.unit.base || 0 - wildcardArea = measuredWidth - arbitrarySum + wildcardArea = measuredWidth - explicitSum # Calculate fills if wildcardArea and fill @@ -93,7 +93,7 @@ class GridNotation for command in wildcards command.isWildcard = false - command.isArbitrary = true + command.isExplicit = true command.isFill = true command.multiplier = 1 command.isPercent = false @@ -460,7 +460,7 @@ class GridNotation # class Command variableRegexp: /^\$([^\*]+)?(\*(\d+)?)?$/i - arbitraryRegexp: /^(([-0-9\.]+)?[a-z%]+)(\*(\d+)?)?$/i + explicitRegexp: /^(([-0-9\.]+)?[a-z%]+)(\*(\d+)?)?$/i wildcardRegexp: /^~(\*(\d*))?$/i constructor: (args = {}) -> @@ -494,13 +494,13 @@ class Command # string - command string to test # # Returns a Boolean - isArbitrary: (command = "") => + isExplicit: (command = "") => if typeof command is "string" - return false if !@arbitraryRegexp.test command.replace /\s/g, '' + return false if !@explicitRegexp.test command.replace /\s/g, '' return false if @unit.parse(command) == null true else - command.isArbitrary || false + command.isExplicit || false # Test if a command is a wildcard # @@ -536,8 +536,8 @@ class Command if @isVariable string bits = @variableRegexp.exec string return bits[2] && !bits[3] || false - else if @isArbitrary string - bits = @arbitraryRegexp.exec string + else if @isExplicit string + bits = @explicitRegexp.exec string return bits[3] && !bits[4] || false else if @isWildcard string bits = @wildcardRegexp.exec string @@ -554,8 +554,8 @@ class Command string = string.replace /\s/g, '' if @isVariable string parseInt(@variableRegexp.exec(string)[3]) || 1 - else if @isArbitrary string - parseInt(@arbitraryRegexp.exec(string)[4]) || 1 + else if @isExplicit string + parseInt(@explicitRegexp.exec(string)[4]) || 1 else if @isWildcard string parseInt(@wildcardRegexp.exec(string)[2]) || 1 else @@ -578,9 +578,9 @@ class Command isFill: @isFill string id: if bits[1] then "$#{ bits[1] }" else "$" multiplier: @count string - else if @isArbitrary string + else if @isExplicit string isValid: true - isArbitrary: true + isExplicit: true isPercent: @isPercent string isFill: @isFill string unit: @unit.parse(string) @@ -607,7 +607,7 @@ class Command string += "|" else if command.isVariable string += command.id - else if command.isArbitrary + else if command.isExplicit string += @unit.toString(command.unit) else if command.isWildcard string += "~" @@ -615,7 +615,7 @@ class Command return "" if command.string is "" string += command.string - if command.isVariable or command.isArbitrary or command.isWildcard + if command.isVariable or command.isExplicit or command.isWildcard string += '*' if command.isFill or command.multiplier > 1 string += command.multiplier if command.multiplier > 1 diff --git a/test/test-commands.coffee b/test/test-commands.coffee index 9c9369e..85cfcec 100644 --- a/test/test-commands.coffee +++ b/test/test-commands.coffee @@ -27,21 +27,21 @@ describe 'Commands', -> assert.strictEqual Command.isVariable("1"), false assert.strictEqual Command.isVariable("1px"), false - it 'should succeed for arbitrary commands', -> - assert.strictEqual Command.isArbitrary("1cm"), true - assert.strictEqual Command.isArbitrary("1in"), true - assert.strictEqual Command.isArbitrary("1mm"), true - assert.strictEqual Command.isArbitrary("1px"), true - assert.strictEqual Command.isArbitrary("1pt"), true - assert.strictEqual Command.isArbitrary("1pica"), true - assert.strictEqual Command.isArbitrary("1%"), true - - it 'should fail for non-arbitrary commands', -> - assert.strictEqual Command.isArbitrary(""), false - assert.strictEqual Command.isArbitrary("1"), false - assert.strictEqual Command.isArbitrary("foo"), false - assert.strictEqual Command.isArbitrary("$"), false - assert.strictEqual Command.isArbitrary("$A = | 10px |"), false + it 'should succeed for explicit commands', -> + assert.strictEqual Command.isExplicit("1cm"), true + assert.strictEqual Command.isExplicit("1in"), true + assert.strictEqual Command.isExplicit("1mm"), true + assert.strictEqual Command.isExplicit("1px"), true + assert.strictEqual Command.isExplicit("1pt"), true + assert.strictEqual Command.isExplicit("1pica"), true + assert.strictEqual Command.isExplicit("1%"), true + + it 'should fail for non-explicit commands', -> + assert.strictEqual Command.isExplicit(""), false + assert.strictEqual Command.isExplicit("1"), false + assert.strictEqual Command.isExplicit("foo"), false + assert.strictEqual Command.isExplicit("$"), false + assert.strictEqual Command.isExplicit("$A = | 10px |"), false it 'should succeed for wildcards', -> assert.strictEqual Command.isWildcard("~"), true @@ -97,10 +97,10 @@ describe 'Commands', -> isFill: false multiplier: 2 - it 'should parse arbitrary', -> + it 'should parse explicit', -> assert.deepEqual Command.parse("10px*2"), isValid: true - isArbitrary: true + isExplicit: true isFill: false isPercent: false unit: @@ -134,7 +134,7 @@ describe 'Commands', -> assert.equal Command.count("~*"), 1 assert.equal Command.count("~*2"), 2 - it 'should count arbitrary multiples', -> + it 'should count explicit multiples', -> assert.equal Command.count("1px"), 1 assert.equal Command.count("1px*"), 1 assert.equal Command.count("1px*2"), 2 @@ -177,10 +177,10 @@ describe 'Commands', -> isFill: false multiplier: 2 - it 'should convert arbitrary commands to strings', -> + it 'should convert explicit commands to strings', -> assert.equal "10px*2", Command.toString isValid: true - isArbitrary: true + isExplicit: true isFill: false isPercent: false unit: From b076fd50d549a31bf8e422be912062527b7f76f2 Mon Sep 17 00:00:00 2001 From: Cameron McEfee Date: Sat, 21 Jun 2014 12:13:50 -0700 Subject: [PATCH 08/16] document error codes --- SPEC.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/SPEC.md b/SPEC.md index 55fbfc2..e3f4202 100644 --- a/SPEC.md +++ b/SPEC.md @@ -261,3 +261,31 @@ When cleaning a guide notation string, errors will be denoted in curly brackets. ``` | 10px | { 10foo [1]} | 10px| ``` + +### Possible errors + +These scenarios will invalidate guide notation + +##### Error: 1 — Unrecognized command + +The parser does not understand the given command, or no unit was specified. + +##### Error: 2 — No grids + +A guide notation string must contain at least one grid. + +##### Error: 3 — Wildcards cannot be fills + +Because wildcards have no width of their own, trying to use them as a fill is dividing by zero. + +#### Error: 4 — Grids can only contain one fill + +Because fills are used to fill up all available space, it isn't possible to have more than one fill. + +##### Error: 5 — Variables cannot contain fills + +Because variables are intended for using multiple times, placing a fill in a variable would result in multiple fills. Technically this *should* be valid if the variable is only used once, however the logic to support this isn't worth supporting a case that technically shouldn't be used. + +##### Error: 6 — A variable must be defined + +If a variable has not been defined at the time it is called, it cannot be used. From 15b5f860bcfff9a6d9891a1dd5482b0f183956a7 Mon Sep 17 00:00:00 2001 From: Cameron McEfee Date: Sat, 21 Jun 2014 12:14:08 -0700 Subject: [PATCH 09/16] switch to error based validation --- src/GridNotation.coffee | 63 +++++++++++++++++++++------------- test/test-commands.coffee | 28 +++++++-------- test/test-grid-notation.coffee | 52 ++++++++++++++-------------- 3 files changed, 80 insertions(+), 63 deletions(-) diff --git a/src/GridNotation.coffee b/src/GridNotation.coffee index eaaf0e2..2e9def3 100644 --- a/src/GridNotation.coffee +++ b/src/GridNotation.coffee @@ -15,7 +15,7 @@ class GridNotation @cmd.unit = @unit guides = [] tested = @validate(@objectify(string)) - return null if !tested.isValid + return null if tested.errors.length > 0 gn = tested.obj for key, variable of gn.variables @@ -191,22 +191,23 @@ class GridNotation # # Returns an Object. validate: (obj) => - isValid = if obj.grids.length > 0 then true else false variablesWithWildcards = {} + errors = [] + error(2, errors) if obj.grids.length <= 0 for key, commands of obj.variables for command in commands - isValid = false if command.isValid is false + error(command.errors, errors) if command.errors.length > 0 id = command.id variable = obj.variables[id] if id # If an undefined variable is called, we can't do anything with it. - isValid = command.isValid = false if id and !variable + error(6, errors, command) if id and !variable # Fills are only meant to be used once, in one place. Including a fill # in a variable likely means it will be used in multiple places. In # theory this *could* be used once, but for now, let's just invalidate. - isValid = command.isValid = false if command.isFill + error(5, errors, command) if command.isFill variablesWithWildcards[key] = true if command.isWildcard @@ -217,24 +218,24 @@ class GridNotation first = grid.params.firstOffset width = grid.params.width last = grid.params.lastOffset - isValid = false if first and !first.isValid - isValid = false if width and !width.isValid - isValid = false if last and !last.isValid + error(1, errors) if first and first.errors.length > 0 + error(1, errors) if width and width.errors.length > 0 + error(1, errors) if last and last.errors.length > 0 for command in grid.commands - isValid = false if command.isValid is false + error(command.errors, errors) if command.errors.length > 0 id = command.id variable = obj.variables[id] if id # If an undefined variable is called, we can't do anything with it. - isValid = command.isValid = false if id and !variable + error(6, errors, command) if id and !variable # Since wildcards don't have an inherent value, it's impossible to # calculate a fill variable containing one. varHasWildcard = find(variable, (el) -> el.isWildcard).length > 0 - if command.isFill and varHasWildcard - isValid = command.isValid = false + # Fill variables cannot contain wildcards + error(3, errors, command) if command.isFill and varHasWildcard fills++ if command.isFill varHasFill = find(variable, (el) -> el.isFill).length > 0 @@ -244,11 +245,10 @@ class GridNotation fills++ # Fills can only be used once. - isValid = command.isValid = false if fills > 1 - if id and variable and varHasFill - isValid = command.isValid = false + error(4, errors, command) if fills > 1 + error(5, errors, command) if id and variable and varHasFill - isValid: isValid + errors: errors obj: obj # Convert a string of command and guide commands into an object. @@ -319,7 +319,7 @@ class GridNotation return true if string.indexOf("|") >= 0 # it has pipes commands = @parseCommands string return true if commands.length > 1 # it has multiple commands - return true if commands[0].isValid # it has a valid first command + return true if commands[0].errors.length is 0 # it has a valid first command false # Convert a grid string into an object. @@ -569,29 +569,29 @@ class Command parse: (string = "") -> string = string.replace /\s/g, '' if @isGuide string - isValid: true + errors: [] isGuide: true else if @isVariable string bits = @variableRegexp.exec string - isValid: true + errors: [] isVariable: true isFill: @isFill string id: if bits[1] then "$#{ bits[1] }" else "$" multiplier: @count string else if @isExplicit string - isValid: true + errors: [] isExplicit: true isPercent: @isPercent string isFill: @isFill string unit: @unit.parse(string) multiplier: @count string else if @isWildcard string - isValid: if @isFill(string) then false else true + errors: if @isFill(string) then [3] else [] isWildcard: true isFill: @isFill string multiplier: @count string else - isValid: false + errors: [1] string: string # Output a command as a string. If it is unrecognized, format it properly. @@ -619,7 +619,7 @@ class Command string += '*' if command.isFill or command.multiplier > 1 string += command.multiplier if command.multiplier > 1 - if command.isValid then string else "{#{ string }}" + if command.errors.length is 0 then string else "{#{ string }}" # Create a command string without a multiplier # @@ -756,6 +756,23 @@ lengthOf = (command, variables) -> sum += command.unit.value sum +# Assign an error code to a command and a master list +# +# code - error code to assign +# master - the master error array +# command - the command that caused the error +# +# Returns nothing. +error = (codes, master, command) -> + codes = [codes] if typeof codes is "number" + for code in codes + exists = find(master, ((e, i) -> true if e is code)).length > 0 + master.push code if !exists + return unless command + command.errors ||= [] + command.isValid = false + exists = find(command.errors, ((e, i) -> true if e is code)).length > 0 + command.errors.push code if !exists if (typeof module != 'undefined' && typeof module.exports != 'undefined') module.exports = diff --git a/test/test-commands.coffee b/test/test-commands.coffee index 85cfcec..6c35f73 100644 --- a/test/test-commands.coffee +++ b/test/test-commands.coffee @@ -68,18 +68,18 @@ describe 'Commands', -> it 'should parse guides', -> assert.deepEqual Command.parse("|"), - isValid: true + errors: [] isGuide: true it 'should parse variables', -> assert.deepEqual Command.parse("$"), - isValid: true + errors: [] isVariable: true isFill: false id: "$" multiplier: 1 assert.deepEqual Command.parse("$foo*2"), - isValid: true + errors: [] isVariable: true isFill: false id: "$foo" @@ -87,19 +87,19 @@ describe 'Commands', -> it 'should parse wildcards', -> assert.deepEqual Command.parse("~"), - isValid: true + errors: [] isWildcard: true isFill: false multiplier: 1 assert.deepEqual Command.parse("~*2"), - isValid: true + errors: [] isWildcard: true isFill: false multiplier: 2 it 'should parse explicit', -> assert.deepEqual Command.parse("10px*2"), - isValid: true + errors: [] isExplicit: true isFill: false isPercent: false @@ -112,7 +112,7 @@ describe 'Commands', -> it 'should parse unknown', -> assert.deepEqual Command.parse("foo"), - isValid: false + errors: [1] string: "foo" describe 'Multiples', -> @@ -148,18 +148,18 @@ describe 'Commands', -> it 'should convert guide commands to strings', -> assert.equal "|", Command.toString - isValid: true + errors: [] isGuide: true it 'should convert variable commands to strings', -> assert.equal "$", Command.toString - isValid: true + errors: [] isVariable: true isFill: false id: "$" multiplier: 1 assert.equal "$foo*2", Command.toString - isValid: true + errors: [] isVariable: true isFill: false id: "$foo" @@ -167,19 +167,19 @@ describe 'Commands', -> it 'should convert wildcard commands to strings', -> assert.equal "~", Command.toString - isValid: true + errors: [] isWildcard: true isFill: false multiplier: 1 assert.equal "~*2", Command.toString - isValid: true + errors: [] isWildcard: true isFill: false multiplier: 2 it 'should convert explicit commands to strings', -> assert.equal "10px*2", Command.toString - isValid: true + errors: [] isExplicit: true isFill: false isPercent: false @@ -193,7 +193,7 @@ describe 'Commands', -> it 'should simplify strings', -> assert.equal "~", Command.toSimpleString("~*2") assert.equal "~", Command.toSimpleString - isValid: true + errors: [] isWildcard: true isFill: false multiplier: 2 diff --git a/test/test-grid-notation.coffee b/test/test-grid-notation.coffee index f6d107b..c8847a4 100644 --- a/test/test-grid-notation.coffee +++ b/test/test-grid-notation.coffee @@ -144,7 +144,7 @@ describe 'Grid Notation', -> assert.deepEqual GN.parseVariable("$ = |"), id: "$" commands: [ - isValid: true + errors: [] isGuide: true ] @@ -160,25 +160,25 @@ describe 'Grid Notation', -> assert.strictEqual GN.parseCommands("").length, 0 it 'should parse unknown commands', -> - assert.equal GN.parseCommands("foo")[0].isValid, false + assert GN.parseCommands("foo")[0].errors.length > 0 it 'should parse guide commands', -> - assert GN.parseCommands("|")[0].isValid + assert GN.parseCommands("|")[0].errors.length is 0 it 'should parse arbitrary commands', -> - assert GN.parseCommands("10px")[0].isValid - assert GN.parseCommands("10px*")[0].isValid - assert GN.parseCommands("10px*2")[0].isValid + assert GN.parseCommands("10px")[0].errors.length is 0 + assert GN.parseCommands("10px*")[0].errors.length is 0 + assert GN.parseCommands("10px*2")[0].errors.length is 0 it 'should parse variable commands', -> - assert GN.parseCommands("$")[0].isValid - assert GN.parseCommands("$A*")[0].isValid - assert GN.parseCommands("$foo*2")[0].isValid + assert GN.parseCommands("$")[0].errors.length is 0 + assert GN.parseCommands("$A*")[0].errors.length is 0 + assert GN.parseCommands("$foo*2")[0].errors.length is 0 it 'should parse wildcard commands', -> - assert GN.parseCommands("~")[0].isValid - assert GN.parseCommands("~*")[0].isValid is false - assert GN.parseCommands("~*2")[0].isValid + assert GN.parseCommands("~")[0].errors.length is 0 + assert GN.parseCommands("~*")[0].errors.length > 0 + assert GN.parseCommands("~*2")[0].errors.length is 0 describe 'Parse options', -> @@ -239,46 +239,46 @@ describe 'Grid Notation', -> it 'should reject fills in variables', -> obj = GN.objectify("$ = 10px*") - assert.equal GN.validate(obj).isValid, false + assert GN.validate(obj).errors.length > 0 it 'should reject empty grids', -> obj = GN.objectify("") - assert.equal GN.validate(obj).isValid, false + assert GN.validate(obj).errors.length > 0 it 'should reject fill variables containing wildcards', -> obj = GN.objectify """ $ = ~ $* """ - assert.equal GN.validate(obj).isValid, false + assert GN.validate(obj).errors.length > 0 it 'should reject undefined variables in variables', -> obj = GN.objectify "$ = $a" - assert.equal GN.validate(obj).isValid, false + assert GN.validate(obj).errors.length > 0 it 'should reject undefined variables in grids', -> obj = GN.objectify """ $ = 10px $a """ - assert.equal GN.validate(obj).isValid, false + assert GN.validate(obj).errors.length > 0 it 'should reject multiple fills in grids', -> obj = GN.objectify "|10px*|10px*|" - assert.equal GN.validate(obj).isValid, false + assert GN.validate(obj).errors.length > 0 it 'should reject fills if a variable already contains one', -> obj = GN.objectify """ $ = 10px* |10px*| """ - assert.equal GN.validate(obj).isValid, false + assert GN.validate(obj).errors.length > 0 it 'should reject bad alignment params', -> - assert GN.validate(GN.objectify("~ ( ~ | ~ | ~ )")).isValid - assert !GN.validate(GN.objectify("~ ( foo | ~ | ~ )")).isValid - assert !GN.validate(GN.objectify("~ ( ~ | foo | ~ )")).isValid - assert !GN.validate(GN.objectify("~ ( ~ | ~ | foo )")).isValid + assert GN.validate(GN.objectify("~ ( ~ | ~ | ~ )")).errors.length is 0 + assert GN.validate(GN.objectify("~ ( foo | ~ | ~ )")).errors.length > 0 + assert GN.validate(GN.objectify("~ ( ~ | foo | ~ )")).errors.length > 0 + assert GN.validate(GN.objectify("~ ( ~ | ~ | foo )")).errors.length > 0 describe 'Utilities', -> @@ -287,18 +287,18 @@ describe 'Grid Notation', -> it 'should expand commands', -> given = [ - isValid: true + errors: [] isWildcard: true isFill: false multiplier: 2 ] expected = [ - isValid: true + errors: [] isWildcard: true isFill: false multiplier: 1 , - isValid: true + errors: [] isWildcard: true isFill: false multiplier: 1 From 2fdd36a27a01b36db443703e08cfceb06eff2294 Mon Sep 17 00:00:00 2001 From: Cameron McEfee Date: Sat, 21 Jun 2014 12:15:17 -0700 Subject: [PATCH 10/16] give the error codes in order --- src/GridNotation.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GridNotation.coffee b/src/GridNotation.coffee index 2e9def3..59d3f92 100644 --- a/src/GridNotation.coffee +++ b/src/GridNotation.coffee @@ -248,7 +248,7 @@ class GridNotation error(4, errors, command) if fills > 1 error(5, errors, command) if id and variable and varHasFill - errors: errors + errors: errors.sort() obj: obj # Convert a string of command and guide commands into an object. From f5efda41589780f6bf00864a9f0effaef04bd808 Mon Sep 17 00:00:00 2001 From: Cameron McEfee Date: Sat, 21 Jun 2014 12:19:35 -0700 Subject: [PATCH 11/16] toString -> stringify --- src/GridNotation.coffee | 18 +++++++++--------- test/test-commands.coffee | 12 ++++++------ test/test-units.coffee | 8 ++++---- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/GridNotation.coffee b/src/GridNotation.coffee index 59d3f92..51ffc35 100644 --- a/src/GridNotation.coffee +++ b/src/GridNotation.coffee @@ -430,7 +430,7 @@ class GridNotation # Returns a String. stringifyCommands: (commands) => string = "" - string += @cmd.toString(command) for command in commands + string += @cmd.stringify(command) for command in commands @pipeCleaner string # Convert a grid's params to a guiden notation spec compliant string. @@ -447,10 +447,10 @@ class GridNotation if params.firstOffset or params.width or params.lastOffset string += ", " if string.length > 0 - string += @cmd.toString(params.firstOffset) if params.firstOffset - string += "|#{ @cmd.toString(params.width) }|" if params.width + string += @cmd.stringify(params.firstOffset) if params.firstOffset + string += "|#{ @cmd.stringify(params.width) }|" if params.width string += "|" if params.firstOffset and params.lastOffset and !params.width - string += @cmd.toString(params.lastOffset) if params.firstOffset + string += @cmd.stringify(params.lastOffset) if params.firstOffset if string then "( #{ @pipeCleaner(string) } )" else '' @@ -599,7 +599,7 @@ class Command # command - command to be converted to a string # # Returns an Integer. - toString: (command = "") -> + stringify: (command = "") -> return command if typeof command is "string" string = "" @@ -608,7 +608,7 @@ class Command else if command.isVariable string += command.id else if command.isExplicit - string += @unit.toString(command.unit) + string += @unit.stringify(command.unit) else if command.isWildcard string += "~" else @@ -628,7 +628,7 @@ class Command # Returns a String. toSimpleString: (command = "") -> return command.replace(/\*.*/gi, "") if typeof command is "string" - @toString(command).replace(/[\{\}]|\*.*/gi, "") + @stringify(command).replace(/[\{\}]|\*.*/gi, "") # # Unit is a utility for parsing and validating unit strings @@ -716,9 +716,9 @@ class Unit # unit = string or object # # Returns a string - toString: (unit = "") => + stringify: (unit = "") => return null if unit == "" - return @toString(@parse(unit)) if typeof unit == "string" + return @stringify(@parse(unit)) if typeof unit == "string" "#{ unit.value }#{ unit.type }" diff --git a/test/test-commands.coffee b/test/test-commands.coffee index 6c35f73..132e1fd 100644 --- a/test/test-commands.coffee +++ b/test/test-commands.coffee @@ -147,18 +147,18 @@ describe 'Commands', -> describe 'Strings', -> it 'should convert guide commands to strings', -> - assert.equal "|", Command.toString + assert.equal "|", Command.stringify errors: [] isGuide: true it 'should convert variable commands to strings', -> - assert.equal "$", Command.toString + assert.equal "$", Command.stringify errors: [] isVariable: true isFill: false id: "$" multiplier: 1 - assert.equal "$foo*2", Command.toString + assert.equal "$foo*2", Command.stringify errors: [] isVariable: true isFill: false @@ -166,19 +166,19 @@ describe 'Commands', -> multiplier: 2 it 'should convert wildcard commands to strings', -> - assert.equal "~", Command.toString + assert.equal "~", Command.stringify errors: [] isWildcard: true isFill: false multiplier: 1 - assert.equal "~*2", Command.toString + assert.equal "~*2", Command.stringify errors: [] isWildcard: true isFill: false multiplier: 2 it 'should convert explicit commands to strings', -> - assert.equal "10px*2", Command.toString + assert.equal "10px*2", Command.stringify errors: [] isExplicit: true isFill: false diff --git a/test/test-units.coffee b/test/test-units.coffee index c1af2e1..e06a194 100644 --- a/test/test-units.coffee +++ b/test/test-units.coffee @@ -95,11 +95,11 @@ describe 'Units', -> describe 'To string', -> it 'should return null when given nothing', -> - assert.strictEqual Unit.toString(), null - assert.strictEqual Unit.toString(""), null + assert.strictEqual Unit.stringify(), null + assert.strictEqual Unit.stringify(""), null it 'should return string when given a unit object', -> - assert.equal Unit.toString(Unit.parse("1px")), "1px" + assert.equal Unit.stringify(Unit.parse("1px")), "1px" it 'should return string when given a string', -> - assert.equal Unit.toString("1px"), "1px" + assert.equal Unit.stringify("1px"), "1px" From a59a9dee954e8d0f9af659c32e777d1397a5ec40 Mon Sep 17 00:00:00 2001 From: Cameron McEfee Date: Sat, 21 Jun 2014 12:33:28 -0700 Subject: [PATCH 12/16] include error codes in cleaned strings --- src/GridNotation.coffee | 4 ++-- test/test-grid-notation.coffee | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/GridNotation.coffee b/src/GridNotation.coffee index 51ffc35..e326d90 100644 --- a/src/GridNotation.coffee +++ b/src/GridNotation.coffee @@ -619,7 +619,7 @@ class Command string += '*' if command.isFill or command.multiplier > 1 string += command.multiplier if command.multiplier > 1 - if command.errors.length is 0 then string else "{#{ string }}" + if command.errors.length is 0 then string else "{#{ string } [#{ command.errors.join(',') }]}" # Create a command string without a multiplier # @@ -628,7 +628,7 @@ class Command # Returns a String. toSimpleString: (command = "") -> return command.replace(/\*.*/gi, "") if typeof command is "string" - @stringify(command).replace(/[\{\}]|\*.*/gi, "") + @stringify(command).replace(/[\{\}]|\*.*|\[\d*\]/gi, "") # # Unit is a utility for parsing and validating unit strings diff --git a/test/test-grid-notation.coffee b/test/test-grid-notation.coffee index c8847a4..50c82e9 100644 --- a/test/test-grid-notation.coffee +++ b/test/test-grid-notation.coffee @@ -32,18 +32,18 @@ describe 'Grid Notation', -> """ expected = """ $ = | 10px | - | $ | ~ | {foo} | ( vl ) + | $ | ~ | {foo [1]} | ( vl ) """ assert.equal GN.clean(gn), expected it 'should detect bad commands in variables', -> - assert.equal GN.clean("$=|foo|"), "$ = | {foo} |" + assert.equal GN.clean("$=|foo|"), "$ = | {foo [1]} |" it 'should detect fills in variables', -> - assert.equal GN.clean("$=|10px*|"), "$ = | {10px*} |" + assert.equal GN.clean("$=|10px*|"), "$ = | {10px* [5]} |" - it 'should detect bad adjustments', -> - assert.equal GN.clean("|~|(hl,foo|~|~)"), "| ~ | ( hl, {foo} | ~ | ~ )" + it.only 'should detect bad adjustments', -> + assert.equal GN.clean("|~|(hl,foo|~|~)"), "| ~ | ( hl, {foo [1]} | ~ | ~ )" it 'should detect undefined variables in variables', -> gn = """ @@ -52,12 +52,12 @@ describe 'Grid Notation', -> """ expected = """ $ = ~ - | {$a} | ( hl ) + | {$a [6]} | ( hl ) """ assert.equal GN.clean(gn), expected it 'should detect undefined variables in grids', -> - assert.equal GN.clean("$ = $a"), "$ = {$a}" + assert.equal GN.clean("$ = $a"), "$ = {$a [6]}" it 'should detect fill variables containing wildcards', -> gn = """ @@ -66,15 +66,15 @@ describe 'Grid Notation', -> """ expected = """ $ = ~ - {$*} ( hl ) + {$* [3]} ( hl ) """ assert.equal GN.clean(gn), expected it 'should detect fill wildcards', -> - assert.equal GN.clean("~*"), "{~*} ( hl )" + assert.equal GN.clean("~*"), "{~* [3]} ( hl )" it 'should detect multiple fills', -> - assert.equal GN.clean("10px*|10px*"), "10px* | {10px*} ( hl )" + assert.equal GN.clean("10px*|10px*"), "10px* | {10px* [4]} ( hl )" it 'should detect multiple fills when used in variables', -> gn = """ @@ -82,8 +82,8 @@ describe 'Grid Notation', -> $ | 10px* """ expected = """ - $ = {10px*} - {$} | {10px*} ( hl ) + $ = {10px* [5]} + {$ [5]} | {10px* [4]} ( hl ) """ assert.equal GN.clean(gn), expected @@ -124,7 +124,7 @@ describe 'Grid Notation', -> it 'should stringify command strings', -> assert.equal GN.stringifyCommands(GN.parseCommands("|10px|")), "| 10px |" - assert.equal GN.stringifyCommands(GN.parseCommands("|foo|")), "| {foo} |" + assert.equal GN.stringifyCommands(GN.parseCommands("|foo|")), "| {foo [1]} |" it 'should stringify params', -> gn = GN.parseGrid "|~|(vl, ~|~|~)" From 7438fb9a897c4507b92dd6eac15c10b8f0d01685 Mon Sep 17 00:00:00 2001 From: Cameron McEfee Date: Sat, 21 Jun 2014 12:38:40 -0700 Subject: [PATCH 13/16] compile the asset --- GridNotation.js | 1021 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1021 insertions(+) create mode 100644 GridNotation.js diff --git a/GridNotation.js b/GridNotation.js new file mode 100644 index 0000000..14a0215 --- /dev/null +++ b/GridNotation.js @@ -0,0 +1,1021 @@ +// Generated by CoffeeScript 1.7.1 +(function() { + var Command, GridNotation, Unit, error, find, lengthOf, trim, + __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + + GridNotation = (function() { + function GridNotation(args) { + if (args == null) { + args = {}; + } + this.stringifyParams = __bind(this.stringifyParams, this); + this.stringifyCommands = __bind(this.stringifyCommands, this); + this.parseVariable = __bind(this.parseVariable, this); + this.parseParams = __bind(this.parseParams, this); + this.parseGrid = __bind(this.parseGrid, this); + this.isCommands = __bind(this.isCommands, this); + this.validate = __bind(this.validate, this); + this.objectify = __bind(this.objectify, this); + this.clean = __bind(this.clean, this); + this.unit = new Unit(); + this.cmd = new Command(); + } + + GridNotation.prototype.parse = function(string, info) { + var adjust, adjustRemainder, command, expandOpts, explicit, explicitSum, fill, fillCollection, fillIterations, fillWidth, gn, grid, guideOrientation, guides, i, insertMarker, key, measuredWidth, newCommand, offset, originalWidth, percentValue, percents, remainderOffset, remainderPixels, stretchDivisions, tested, variable, wholePixels, wildcardArea, wildcardWidth, wildcards, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _m, _n, _o, _ref, _ref1, _ref10, _ref11, _ref12, _ref13, _ref14, _ref15, _ref16, _ref17, _ref18, _ref19, _ref2, _ref20, _ref21, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9; + if (string == null) { + string = ""; + } + if (info == null) { + info = {}; + } + if (info.resolution) { + this.unit.resolution = info.resolution; + } + this.cmd.unit = this.unit; + guides = []; + tested = this.validate(this.objectify(string)); + if (tested.errors.length > 0) { + return null; + } + gn = tested.obj; + _ref = gn.variables; + for (key in _ref) { + variable = _ref[key]; + gn.variables[key] = this.expandCommands(variable, { + variables: gn.variables + }); + } + _ref1 = gn.grids; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + grid = _ref1[_i]; + guideOrientation = grid.params.orientation; + wholePixels = grid.params.calculation === 'p'; + fill = find(grid.commands, function(el) { + return el.isFill; + })[0] || null; + originalWidth = guideOrientation === 'h' ? info.height : info.width; + measuredWidth = guideOrientation === 'h' ? info.height : info.width; + if ((_ref2 = grid.params.width) != null ? (_ref3 = _ref2.unit) != null ? _ref3.base : void 0 : void 0) { + measuredWidth = grid.params.width.unit.base; + } + offset = guideOrientation === 'h' ? info.offsetY : info.offsetX; + stretchDivisions = 0; + adjustRemainder = 0; + wildcards = find(grid.commands, function(el) { + return el.isWildcard; + }); + explicit = find(grid.commands, function(el) { + return el.isExplicit && !el.isFill; + }); + explicitSum = 0; + for (_j = 0, _len1 = explicit.length; _j < _len1; _j++) { + command = explicit[_j]; + explicitSum += command.unit.base; + } + if ((_ref4 = grid.params.width) != null ? (_ref5 = _ref4.unit) != null ? _ref5.base : void 0 : void 0) { + adjustRemainder = originalWidth - ((_ref6 = grid.params.width) != null ? _ref6.unit.base : void 0); + } else { + if (wildcards.length === 0) { + adjustRemainder = originalWidth - explicitSum; + } + measuredWidth -= ((_ref7 = grid.params.firstOffset) != null ? (_ref8 = _ref7.unit) != null ? _ref8.base : void 0 : void 0) || 0; + measuredWidth -= ((_ref9 = grid.params.lastOffset) != null ? (_ref10 = _ref9.unit) != null ? _ref10.base : void 0 : void 0) || 0; + } + if (adjustRemainder > 0) { + adjustRemainder -= ((_ref11 = grid.params.firstOffset) != null ? (_ref12 = _ref11.unit) != null ? _ref12.base : void 0 : void 0) || 0; + adjustRemainder -= ((_ref13 = grid.params.lastOffset) != null ? (_ref14 = _ref13.unit) != null ? _ref14.base : void 0 : void 0) || 0; + } + if ((_ref15 = grid.params.firstOffset) != null ? _ref15.isWildcard : void 0) { + stretchDivisions++; + } + if ((_ref16 = grid.params.lastOffset) != null ? _ref16.isWildcard : void 0) { + stretchDivisions++; + } + adjust = adjustRemainder / stretchDivisions; + if ((_ref17 = grid.params.firstOffset) != null ? _ref17.isWildcard : void 0) { + if (wholePixels) { + adjust = Math.ceil(adjust); + } + grid.params.firstOffset = this.cmd.parse("" + adjust + "px"); + } + if ((_ref18 = grid.params.lastOffset) != null ? _ref18.isWildcard : void 0) { + if (wholePixels) { + adjust = Math.floor(adjust); + } + grid.params.lastOffset = this.cmd.parse("" + adjust + "px"); + } + offset += ((_ref19 = grid.params.firstOffset) != null ? _ref19.unit.base : void 0) || 0; + wildcardArea = measuredWidth - explicitSum; + if (wildcardArea && fill) { + fillIterations = Math.floor(wildcardArea / lengthOf(fill, gn.variables)); + fillCollection = []; + fillWidth = 0; + for (i = _k = 1; 1 <= fillIterations ? _k <= fillIterations : _k >= fillIterations; i = 1 <= fillIterations ? ++_k : --_k) { + if (fill.isVariable) { + fillCollection = fillCollection.concat(gn.variables[fill.id]); + fillWidth += lengthOf(fill, gn.variables); + } else { + newCommand = this.cmd.parse(this.cmd.toSimpleString(fill)); + fillCollection.push(newCommand); + fillWidth += newCommand.unit.base; + } + } + wildcardArea -= fillWidth; + } + if (wildcardArea && wildcards) { + wildcardWidth = wildcardArea / wildcards.length; + if (wholePixels) { + wildcardWidth = Math.floor(wildcardWidth); + remainderPixels = wildcardArea % wildcards.length; + } + for (_l = 0, _len2 = wildcards.length; _l < _len2; _l++) { + command = wildcards[_l]; + command.isWildcard = false; + command.isExplicit = true; + command.isFill = true; + command.multiplier = 1; + command.isPercent = false; + command.unit = this.unit.parse("" + wildcardWidth + "px"); + } + } + if (remainderPixels) { + remainderOffset = 0; + if (grid.params.remainder === 'c') { + remainderOffset = Math.floor((wildcards.length - remainderPixels) / 2); + } + if (grid.params.remainder === 'l') { + remainderOffset = wildcards.length - remainderPixels; + } + for (i = _m = 0, _len3 = wildcards.length; _m < _len3; i = ++_m) { + command = wildcards[i]; + if (i >= remainderOffset && i < remainderOffset + remainderPixels) { + command.unit = this.unit.parse("" + (wildcardWidth + 1) + "px"); + } + } + } + insertMarker = (_ref20 = grid.params.firstOffset) != null ? _ref20.unit.base : void 0; + insertMarker || (insertMarker = offset); + expandOpts = { + variables: gn.variables, + fillCollection: fillCollection + }; + grid.commands = this.expandCommands(grid.commands, expandOpts); + percents = find(grid.commands, function(el) { + return el.isPercent; + }); + for (_n = 0, _len4 = percents.length; _n < _len4; _n++) { + command = percents[_n]; + percentValue = measuredWidth * (command.unit.value / 100); + if (wholePixels) { + percentValue = Math.floor(percentValue); + } + command.unit = this.unit.parse("" + percentValue + "px"); + } + _ref21 = grid.commands; + for (_o = 0, _len5 = _ref21.length; _o < _len5; _o++) { + command = _ref21[_o]; + if (command.isGuide) { + guides.push({ + location: insertMarker, + orientation: guideOrientation + }); + } else { + insertMarker += command.unit.base; + } + } + } + return guides; + }; + + GridNotation.prototype.clean = function(string) { + var gn, grid, key, line, variable, _i, _len, _ref, _ref1; + if (string == null) { + string = ""; + } + gn = this.validate(this.objectify(string)).obj; + string = ""; + _ref = gn.variables; + for (key in _ref) { + variable = _ref[key]; + string += "" + key + " = " + (this.stringifyCommands(variable)) + "\n"; + } + if (gn.variables.length > 0) { + string += "\n"; + } + _ref1 = gn.grids; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + grid = _ref1[_i]; + line = ""; + line += this.stringifyCommands(grid.commands); + line += " " + (this.stringifyParams(grid.params)); + string += "" + (trim(line)) + "\n"; + } + return trim(string.replace(/\n\n\n+/g, "\n")); + }; + + GridNotation.prototype.objectify = function(string) { + var grid, grids, line, lines, variable, variables, _i, _len; + if (string == null) { + string = ""; + } + lines = string.split(/\n/g); + string = ""; + variables = {}; + grids = []; + for (_i = 0, _len = lines.length; _i < _len; _i++) { + line = lines[_i]; + if (/^\$.*?\s?=.*$/i.test(line)) { + variable = this.parseVariable(line); + variables[variable.id] = variable.commands; + } else if (/^\s*#/i.test(line)) { + + } else { + grid = this.parseGrid(line); + if (grid.commands.length > 0) { + grids.push(grid); + } + } + } + return { + variables: variables, + grids: grids + }; + }; + + GridNotation.prototype.validate = function(obj) { + var command, commands, errors, fills, first, grid, id, key, last, varHasFill, varHasWildcard, variable, variablesWithWildcards, width, _i, _j, _len, _len1, _ref, _ref1, _ref2; + variablesWithWildcards = {}; + errors = []; + if (obj.grids.length <= 0) { + error(2, errors); + } + _ref = obj.variables; + for (key in _ref) { + commands = _ref[key]; + for (_i = 0, _len = commands.length; _i < _len; _i++) { + command = commands[_i]; + if (command.errors.length > 0) { + error(command.errors, errors); + } + id = command.id; + if (id) { + variable = obj.variables[id]; + } + if (id && !variable) { + error(6, errors, command); + } + if (command.isFill) { + error(5, errors, command); + } + if (command.isWildcard) { + variablesWithWildcards[key] = true; + } + } + } + _ref1 = obj.grids; + for (key in _ref1) { + grid = _ref1[key]; + fills = 0; + first = grid.params.firstOffset; + width = grid.params.width; + last = grid.params.lastOffset; + if (first && first.errors.length > 0) { + error(1, errors); + } + if (width && width.errors.length > 0) { + error(1, errors); + } + if (last && last.errors.length > 0) { + error(1, errors); + } + _ref2 = grid.commands; + for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) { + command = _ref2[_j]; + if (command.errors.length > 0) { + error(command.errors, errors); + } + id = command.id; + if (id) { + variable = obj.variables[id]; + } + if (id && !variable) { + error(6, errors, command); + } + varHasWildcard = find(variable, function(el) { + return el.isWildcard; + }).length > 0; + if (command.isFill && varHasWildcard) { + error(3, errors, command); + } + if (command.isFill) { + fills++; + } + varHasFill = find(variable, function(el) { + return el.isFill; + }).length > 0; + if (id && variable && varHasFill) { + fills++; + } + if (fills > 1) { + error(4, errors, command); + } + if (id && variable && varHasFill) { + error(5, errors, command); + } + } + } + return { + errors: errors.sort(), + obj: obj + }; + }; + + GridNotation.prototype.parseCommands = function(string) { + var commands, token, tokens, _i, _len; + if (string == null) { + string = ""; + } + string = this.pipeCleaner(string); + commands = []; + if (string === "") { + return commands; + } + tokens = string.replace(/^\s+|\s+$/g, '').replace(/\s\s+/g, ' ').split(/\s/); + for (_i = 0, _len = tokens.length; _i < _len; _i++) { + token = tokens[_i]; + commands.push(this.cmd.parse(token)); + } + return commands; + }; + + GridNotation.prototype.expandCommands = function(commands, args) { + var command, i, loops, newCommands, _i, _j, _k, _l, _len, _len1, _len2, _len3, _m, _ref; + if (commands == null) { + commands = []; + } + if (args == null) { + args = {}; + } + if (typeof commands === "string") { + commands = this.parseCommands(commands); + } + newCommands = []; + for (i = _i = 0, _len = commands.length; _i < _len; i = ++_i) { + command = commands[i]; + if (args.fillCollection && command.isFill) { + newCommands = newCommands.concat(args.fillCollection); + } else { + newCommands.push(command); + } + } + commands = [].concat(newCommands); + newCommands = []; + for (i = _j = 0, _len1 = commands.length; _j < _len1; i = ++_j) { + command = commands[i]; + if (command.isVariable && args.variables && args.variables[command.id]) { + newCommands = newCommands.concat(args.variables[command.id]); + } else { + newCommands.push(command); + } + } + commands = [].concat(newCommands); + newCommands = []; + for (_k = 0, _len2 = commands.length; _k < _len2; _k++) { + command = commands[_k]; + loops = command.multiplier || 1; + for (i = _l = 0; _l < loops; i = _l += 1) { + newCommands.push(this.cmd.parse(this.cmd.toSimpleString(command))); + } + } + commands = [].concat(newCommands); + newCommands = []; + for (i = _m = 0, _len3 = commands.length; _m < _len3; i = ++_m) { + command = commands[i]; + if (!command.isGuide || (command.isGuide && !((_ref = commands[i - 1]) != null ? _ref.isGuide : void 0))) { + newCommands.push(command); + } + } + return newCommands; + }; + + GridNotation.prototype.isCommands = function(string) { + var commands; + if (string == null) { + string = ""; + } + if (string === "") { + return false; + } + if (string.indexOf("|") >= 0) { + return true; + } + commands = this.parseCommands(string); + if (commands.length > 1) { + return true; + } + if (commands[0].errors.length === 0) { + return true; + } + return false; + }; + + GridNotation.prototype.parseGrid = function(string) { + var commands, params, regex; + if (string == null) { + string = ""; + } + regex = /\((.*?)\)/i; + params = regex.exec(string) || []; + string = trim(string.replace(regex, '')); + commands = this.parseCommands(string); + return { + commands: commands, + wildcards: find(commands, function(el) { + return el.isWildcard; + }), + params: this.parseParams(params[1] || '') + }; + }; + + GridNotation.prototype.parseParams = function(string) { + var bits, k, obj, v, _ref, _ref1, _ref2, _ref3; + if (string == null) { + string = ""; + } + bits = string.replace(/[\s\(\)]/g, '').split(','); + obj = { + orientation: "h", + remainder: "l", + calculation: "" + }; + if (bits.length > 1) { + _ref = this.parseOptions(bits[0]); + for (k in _ref) { + v = _ref[k]; + obj[k] = v; + } + _ref1 = this.parseAdjustments(bits[1] || ""); + for (k in _ref1) { + v = _ref1[k]; + obj[k] = v; + } + return obj; + } else if (bits.length === 1) { + if (this.isCommands(bits[0])) { + _ref2 = this.parseAdjustments(bits[0] || ""); + for (k in _ref2) { + v = _ref2[k]; + obj[k] = v; + } + } else { + _ref3 = this.parseOptions(bits[0]); + for (k in _ref3) { + v = _ref3[k]; + obj[k] = v; + } + } + } + return obj; + }; + + GridNotation.prototype.parseOptions = function(string) { + var obj, option, options, _i, _len; + if (string == null) { + string = ""; + } + options = string.split(''); + obj = {}; + for (_i = 0, _len = options.length; _i < _len; _i++) { + option = options[_i]; + switch (option.toLowerCase()) { + case "h": + case "v": + obj.orientation = option; + break; + case "f": + case "c": + case "l": + obj.remainder = option; + break; + case "p": + obj.calculation = option; + } + } + return obj; + }; + + GridNotation.prototype.parseAdjustments = function(string) { + var adj, bits, el, end, i, _i, _len, _ref, _ref1; + if (string == null) { + string = ""; + } + adj = { + firstOffset: null, + width: null, + lastOffset: null + }; + if (string === "") { + return adj; + } + bits = this.expandCommands(string.replace(/\s/, '')).splice(0, 5); + end = bits.length - 1; + if (bits.length > 1 && !bits[end].isGuide) { + adj.lastOffset = bits[end]; + } + if (!bits[0].isGuide) { + adj.firstOffset = bits[0]; + } + for (i = _i = 0, _len = bits.length; _i < _len; i = ++_i) { + el = bits[i]; + if (((_ref = bits[i - 1]) != null ? _ref.isGuide : void 0) && ((_ref1 = bits[i + 1]) != null ? _ref1.isGuide : void 0)) { + if (!el.isGuide) { + adj.width = el; + } + } + } + return adj; + }; + + GridNotation.prototype.parseVariable = function(string) { + var bits; + bits = /^\$([^=\s]+)?\s?=\s?(.*)$/i.exec(string); + if (bits[2] == null) { + return null; + } + return { + id: bits[1] ? "$" + bits[1] : "$", + commands: this.parseCommands(bits[2]) + }; + }; + + GridNotation.prototype.pipeCleaner = function(string) { + if (string == null) { + string = ""; + } + return string.replace(/[^\S\n]*\|[^\S\n]*/g, '|').replace(/\|+/g, ' | ').replace(/^\s+|\s+$/g, ''); + }; + + GridNotation.prototype.stringifyCommands = function(commands) { + var command, string, _i, _len; + string = ""; + for (_i = 0, _len = commands.length; _i < _len; _i++) { + command = commands[_i]; + string += this.cmd.stringify(command); + } + return this.pipeCleaner(string); + }; + + GridNotation.prototype.stringifyParams = function(params) { + var string; + string = ""; + string += "" + (params.orientation || ''); + string += "" + (params.remainder || ''); + string += "" + (params.calculation || ''); + if (params.firstOffset || params.width || params.lastOffset) { + if (string.length > 0) { + string += ", "; + } + } + if (params.firstOffset) { + string += this.cmd.stringify(params.firstOffset); + } + if (params.width) { + string += "|" + (this.cmd.stringify(params.width)) + "|"; + } + if (params.firstOffset && params.lastOffset && !params.width) { + string += "|"; + } + if (params.firstOffset) { + string += this.cmd.stringify(params.lastOffset); + } + if (string) { + return "( " + (this.pipeCleaner(string)) + " )"; + } else { + return ''; + } + }; + + return GridNotation; + + })(); + + Command = (function() { + Command.prototype.variableRegexp = /^\$([^\*]+)?(\*(\d+)?)?$/i; + + Command.prototype.explicitRegexp = /^(([-0-9\.]+)?[a-z%]+)(\*(\d+)?)?$/i; + + Command.prototype.wildcardRegexp = /^~(\*(\d*))?$/i; + + function Command(args) { + if (args == null) { + args = {}; + } + this.isWildcard = __bind(this.isWildcard, this); + this.isExplicit = __bind(this.isExplicit, this); + this.isVariable = __bind(this.isVariable, this); + this.unit = new Unit(); + } + + Command.prototype.isGuide = function(command) { + if (command == null) { + command = ""; + } + if (typeof command === "string") { + return command.replace(/\s/g, '') === "|"; + } else { + return command.isGuide || false; + } + }; + + Command.prototype.isVariable = function(command) { + if (command == null) { + command = ""; + } + if (typeof command === "string") { + return this.variableRegexp.test(command.replace(/\s/g, '')); + } else { + return command.isVariable || false; + } + }; + + Command.prototype.isExplicit = function(command) { + if (command == null) { + command = ""; + } + if (typeof command === "string") { + if (!this.explicitRegexp.test(command.replace(/\s/g, ''))) { + return false; + } + if (this.unit.parse(command) === null) { + return false; + } + return true; + } else { + return command.isExplicit || false; + } + }; + + Command.prototype.isWildcard = function(command) { + if (command == null) { + command = ""; + } + if (typeof command === "string") { + return this.wildcardRegexp.test(command.replace(/\s/g, '')); + } else { + return command.isWildcard || false; + } + }; + + Command.prototype.isPercent = function(command) { + var unit; + if (command == null) { + command = ""; + } + if (typeof command === "string") { + unit = this.unit.parse(command.replace(/\s/g, '')); + return (unit != null) && unit.type === '%'; + } else { + return command.isPercent || false; + } + }; + + Command.prototype.isFill = function(string) { + var bits; + if (string == null) { + string = ""; + } + if (this.isVariable(string)) { + bits = this.variableRegexp.exec(string); + return bits[2] && !bits[3] || false; + } else if (this.isExplicit(string)) { + bits = this.explicitRegexp.exec(string); + return bits[3] && !bits[4] || false; + } else if (this.isWildcard(string)) { + bits = this.wildcardRegexp.exec(string); + return bits[1] && !bits[2] || false; + } else { + return false; + } + }; + + Command.prototype.count = function(string) { + if (string == null) { + string = ""; + } + string = string.replace(/\s/g, ''); + if (this.isVariable(string)) { + return parseInt(this.variableRegexp.exec(string)[3]) || 1; + } else if (this.isExplicit(string)) { + return parseInt(this.explicitRegexp.exec(string)[4]) || 1; + } else if (this.isWildcard(string)) { + return parseInt(this.wildcardRegexp.exec(string)[2]) || 1; + } else { + return null; + } + }; + + Command.prototype.parse = function(string) { + var bits; + if (string == null) { + string = ""; + } + string = string.replace(/\s/g, ''); + if (this.isGuide(string)) { + return { + errors: [], + isGuide: true + }; + } else if (this.isVariable(string)) { + bits = this.variableRegexp.exec(string); + return { + errors: [], + isVariable: true, + isFill: this.isFill(string), + id: bits[1] ? "$" + bits[1] : "$", + multiplier: this.count(string) + }; + } else if (this.isExplicit(string)) { + return { + errors: [], + isExplicit: true, + isPercent: this.isPercent(string), + isFill: this.isFill(string), + unit: this.unit.parse(string), + multiplier: this.count(string) + }; + } else if (this.isWildcard(string)) { + return { + errors: this.isFill(string) ? [3] : [], + isWildcard: true, + isFill: this.isFill(string), + multiplier: this.count(string) + }; + } else { + return { + errors: [1], + string: string + }; + } + }; + + Command.prototype.stringify = function(command) { + var string; + if (command == null) { + command = ""; + } + if (typeof command === "string") { + return command; + } + string = ""; + if (command.isGuide) { + string += "|"; + } else if (command.isVariable) { + string += command.id; + } else if (command.isExplicit) { + string += this.unit.stringify(command.unit); + } else if (command.isWildcard) { + string += "~"; + } else { + if (command.string === "") { + return ""; + } + string += command.string; + } + if (command.isVariable || command.isExplicit || command.isWildcard) { + if (command.isFill || command.multiplier > 1) { + string += '*'; + } + if (command.multiplier > 1) { + string += command.multiplier; + } + } + if (command.errors.length === 0) { + return string; + } else { + return "{" + string + " [" + (command.errors.join(',')) + "]}"; + } + }; + + Command.prototype.toSimpleString = function(command) { + if (command == null) { + command = ""; + } + if (typeof command === "string") { + return command.replace(/\*.*/gi, ""); + } + return this.stringify(command).replace(/[\{\}]|\*.*|\[\d*\]/gi, ""); + }; + + return Command; + + })(); + + Unit = (function() { + Unit.prototype.resolution = 72; + + function Unit(args) { + if (args == null) { + args = {}; + } + this.stringify = __bind(this.stringify, this); + this.parse = __bind(this.parse, this); + } + + Unit.prototype.parse = function(string) { + var bits, value; + if (string == null) { + string = ""; + } + string = string.replace(/\s/g, ''); + bits = string.match(/([-0-9\.]+)([a-z%]+)?/i); + if (!string || string === "" || (bits == null)) { + return null; + } + if (bits[2] && !this.preferredName(bits[2])) { + return null; + } + if (bits[1] && !bits[2]) { + value = parseFloat(bits[1]); + if (value.toString() === bits[1]) { + return value; + } else { + return null; + } + } + return { + string: string, + value: parseFloat(bits[1]), + type: this.preferredName(bits[2]), + base: this.asBaseUnit({ + value: parseFloat(bits[1]), + type: this.preferredName(bits[2]) + }) + }; + }; + + Unit.prototype.preferredName = function(string) { + switch (string) { + case 'centimeter': + case 'centimeters': + case 'centimetre': + case 'centimetres': + case 'cm': + return 'cm'; + case 'inch': + case 'inches': + case 'in': + return 'in'; + case 'millimeter': + case 'millimeters': + case 'millimetre': + case 'millimetres': + case 'mm': + return 'mm'; + case 'pixel': + case 'pixels': + case 'px': + return 'px'; + case 'point': + case 'points': + case 'pts': + case 'pt': + return 'points'; + case 'pica': + case 'picas': + return 'picas'; + case 'percent': + case 'pct': + case '%': + return '%'; + default: + return null; + } + }; + + Unit.prototype.asBaseUnit = function(unit) { + if (!((unit != null) && (unit.value != null) && (unit.type != null))) { + return null; + } + switch (unit.type) { + case 'cm': + unit.value = unit.value / 2.54; + break; + case 'in': + unit.value = unit.value / 1; + break; + case 'mm': + unit.value = unit.value / 25.4; + break; + case 'px': + unit.value = unit.value / this.resolution; + break; + case 'points': + unit.value = unit.value / this.resolution; + break; + case 'picas': + unit.value = unit.value / 6; + break; + default: + return null; + } + return unit.value * this.resolution; + }; + + Unit.prototype.stringify = function(unit) { + if (unit == null) { + unit = ""; + } + if (unit === "") { + return null; + } + if (typeof unit === "string") { + return this.stringify(this.parse(unit)); + } + return "" + unit.value + unit.type; + }; + + return Unit; + + })(); + + trim = function(string) { + return string.replace(/^\s+|\s+$/g, ''); + }; + + find = function(arr, iterator) { + var el, i, matches, _i, _len; + if (!(arr && iterator)) { + return []; + } + matches = []; + for (i = _i = 0, _len = arr.length; _i < _len; i = ++_i) { + el = arr[i]; + if (iterator(el) === true) { + matches.push(el); + } + } + return matches; + }; + + lengthOf = function(command, variables) { + var sum, _i, _len, _ref; + if (!command.isVariable) { + return command.unit.value * command.multiplier; + } + if (!variables[command.id]) { + return 0; + } + sum = 0; + _ref = variables[command.id]; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + command = _ref[_i]; + sum += command.unit.value; + } + return sum; + }; + + error = function(codes, master, command) { + var code, exists, _i, _len; + if (typeof codes === "number") { + codes = [codes]; + } + for (_i = 0, _len = codes.length; _i < _len; _i++) { + code = codes[_i]; + exists = find(master, (function(e, i) { + if (e === code) { + return true; + } + })).length > 0; + if (!exists) { + master.push(code); + } + if (!command) { + return; + } + command.errors || (command.errors = []); + command.isValid = false; + exists = find(command.errors, (function(e, i) { + if (e === code) { + return true; + } + })).length > 0; + if (!exists) { + command.errors.push(code); + } + } + }; + + if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + module.exports = { + notation: new GridNotation(), + unit: new Unit(), + command: new Command() + }; + } else { + window.GridNotation = new GridNotation(); + window.Unit = new Unit(); + window.Command = new Command(); + } + +}).call(this); From 0e467036fdd5b82a2c192962c4576475fd3a4003 Mon Sep 17 00:00:00 2001 From: Cameron McEfee Date: Sat, 21 Jun 2014 12:45:52 -0700 Subject: [PATCH 14/16] use lower case file names --- GridNotation.js => grid-notation.js | 0 script/compile | 2 +- src/{GridNotation.coffee => grid-notation.coffee} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename GridNotation.js => grid-notation.js (100%) rename src/{GridNotation.coffee => grid-notation.coffee} (100%) diff --git a/GridNotation.js b/grid-notation.js similarity index 100% rename from GridNotation.js rename to grid-notation.js diff --git a/script/compile b/script/compile index 01d209b..f154865 100755 --- a/script/compile +++ b/script/compile @@ -3,4 +3,4 @@ # # compile Grid Notation for publishing -coffee -o . -c ./src/GridNotation.coffee +coffee -o . -c ./src/grid-notation.coffee diff --git a/src/GridNotation.coffee b/src/grid-notation.coffee similarity index 100% rename from src/GridNotation.coffee rename to src/grid-notation.coffee From 7ea7d1a0a58fcdba05668db2560b4784f14fa68a Mon Sep 17 00:00:00 2001 From: Cameron McEfee Date: Sat, 21 Jun 2014 12:45:59 -0700 Subject: [PATCH 15/16] update the description --- README.md | 4 +++- SPEC.md | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c2365aa..ef5e4c1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ **This repository is a work in progress and is not yet documented.** -Grid notation is a way to “write” grids. For more information, see the [spec](SPEC.md). +After a few years of using GuideGuide, I became frustrated that I couldn't move beyond simple grid structures. What if I wanted a sidebar? What if I wanted to reposition the grid in the document? Grid notation is a human friendly(ish), written grid language. A string goes in, an array of guides comes out. + +For more information, see the [spec](SPEC.md). ### Setup diff --git a/SPEC.md b/SPEC.md index e3f4202..3b0f32a 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,7 +1,5 @@ # Grid notation -After a few years of using GuideGuide, I became frustrated that I couldn't move beyond simple grid structures. What if I wanted a sidebar? What if I wanted to reposition the grid in the document? Grid notation is written language for given commands to a grid parser. A string goes in, an array of guides comes out. - ## Grids > <commands> [( [<options>][, <first offset> | <width> | <last offset> ])] From f442f282b8d2ba2e899a81b0a535c18effc43b3f Mon Sep 17 00:00:00 2001 From: Cameron McEfee Date: Sat, 21 Jun 2014 12:47:02 -0700 Subject: [PATCH 16/16] add bower manifest --- bower.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 bower.json diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..33782a9 --- /dev/null +++ b/bower.json @@ -0,0 +1,25 @@ +{ + "name": "grid-notation", + "homepage": "https://github.com/guideguide/grid-notation", + "authors": [ + "Cameron McEfee " + ], + "description": "A human friendly(ish), written grid language", + "main": "grid-notation.js", + "keywords": [ + "guides", + "grids", + "guideguide" + ], + "license": "MIT", + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "src", + "script", + "SPEC.md" + ] +}