module Archetype::SassExtensions::Styleguide # # exposes the grammar used when interpreting styleguide calls # # *Parameters*: # - sentence {String|List} the sentence describing the component # - theme {String} the theme to use # - state {String} the name of a state to return # *Returns*: # - {Map} a map including the `identifier` and `modifiers` def _styleguide_grammar(sentence, theme = nil, state = nil) keys = ['identifier', 'modifiers', 'token'] id, modifiers, token = grammar(sentence, theme, state) # if the id is empty, then it means that the sentence didn't contain a valid component id # so we set everything to `null` unless id id = null modifiers = null # otherwise we ensure that we're sending back appropriate values else id = identifier(id) modifiers = modifiers.empty? ? null : list(modifiers.map{|m| identifier(m)}, :space) end return Sass::Script::Value::Map.new({ identifier('identifier') => id, identifier('modifiers') => modifiers }); end private # # given a sentence, deconstruct it into it's identifier and verbages # # *Parameters*: # - sentence {String|List} the sentence describing the component # - theme {String} the theme to use # - state {String} the name of a state to return # *Returns*: # - {Array} an array containing the identifer, modifiers, and a token # def grammar(sentence, theme = nil, state = nil) _styleguide_debug "converting `#{sentence}` to grammar", :grammar theme = get_theme(theme) components = theme[:components] # get a list of valid ids styleguideIds = components.keys # convert the sentence to a string and then split into an array # this ensures that all the pieces are treated as strings and not other primitive types (e.g. a list of strings in the middle of a sentence) sentence = helpers.to_str(sentence).split # update the sentence to be aware of it's current styleguide context withins = [] styleguide_stack.reverse_each do |context| sentence << 'in' context = context.to_a sentence.concat(context) withins.concat(context) end unless withins.empty? sentence << 'within' sentence.concat(withins) end id, modifiers = grammarize(sentence, styleguideIds) # if there was no id, return a list of valid IDs for reporting modifiers = styleguideIds if id.nil? # get the list of currenty installed component extensions extensions = theme[:extensions] if not id.nil? # TODO - low - eoneill: make sure we always want to return unique modifiers # i can't think of a case where we wouldn't want to remove dups # maybe in the case where we're looking for strict keys on the lookup? modifiers = modifiers.uniq token = memoizer.tokenize(theme[:name], extensions, id, modifiers, state) if id _styleguide_debug "the computed grammar is...", :grammar _styleguide_debug " identifier: #{id}", :grammar unless modifiers.empty? _styleguide_debug " modifiers: #{modifiers.join(', ')}", :grammar end end return id, modifiers, token end # # given a sentence, convert it to it's internal representation # # *Parameters*: # - sentence {Array|List} the sentence describing the component # - ids {Array} the list of identifiers # *Returns*: # - {Array} an array containing the identifer and modifiers # def grammarize(sentence, ids = []) sentence = sentence.to_a id = nil modifiers = [] unless sentence.empty? prefix = '' order = '' # these define various attributes for modifiers (e.g. `button with a shadow`) extras = %w(on with without) # these are things that are useless to us, so we just leave them out ignore = %w(a an also the this that is was it) # these are our context switches (e.g. `headline in a button`) local_contexts = %w(in) # these are the contexts that match all surrounding items global_contexts = %w(within) sentence.each do |item| item = item.value if not item.is_a?(String) # find the ID if id.nil? and ids.include?(item) and prefix.empty? and order.empty? id = item # if it's a `local context`, we need to increase the depth and reset the prefix elsif local_contexts.include?(item) order = "#{item}-#{order}" prefix = '' # if it's a `global context`, we need to reset the order, but also update the prefix elsif global_contexts.include?(item) order = ''#"#{item}-" prefix = "#{item}-" # if it's an `extra`, we update the prefix elsif extras.include?(item) prefix = "#{item}-" # finally, check that it's not on the ignore (useless) list. if it is, we just skip over it # (maybe this should be the first thing we check?) elsif not ignore.include?(item) modifiers.push("#{order}#{prefix}#{item}") end end end return id, modifiers end end