lib/metaheader.rb in metaheader-1.1.1 vs lib/metaheader.rb in metaheader-1.2

- old
+ new

@@ -26,14 +26,15 @@ def parse(raw_input) raise NotImplementedError end end - REQUIRED = Object.new.freeze + BOOLEAN = Object.new.freeze OPTIONAL = Object.new.freeze - VALUE = Object.new.freeze + REQUIRED = Object.new.freeze SINGLELINE = Object.new.freeze + VALUE = Object.new.freeze # Whether to fail validation if unknown tags are encoutered. # @see #validate # @return [Boolean] attr_accessor :strict @@ -69,19 +70,27 @@ parser.parse input } end # Returns the value of a tag by its name, or nil if not found. + # @param key [Symbol] tag name + # @param default [Object] value to return if key doesn't exist # @return [Object, nil] - def [](key) - tag = @data[key] and tag.value + def [](key, default = nil) + if tag = @data[key] + tag.value + else + default + end end # Replaces the value of a tag. # @param value the new value # @return value def []=(key, value) + raise ArgumentError, 'value cannot be nil' if value.nil? + @data[key] ||= Tag.new key @data[key].value = value end # Returns how many tags were found in the input. @@ -94,14 +103,27 @@ # @return [Boolean] def empty? @data.empty? end + # Whether a tag was found in the input. + # @param tag [Symbol] the tag to lookup + # @return [Boolean] + def has?(tag) + @data.has_key? tag.to_sym + end + + # Removes a given tag from the list. + # @param tag [Symbol] the tag to remove + def delete(tag) + @data.delete tag + end + # Make a hash from the parsed data # @return [Hash] def to_h - Hash[@data.map {|v| [v.first, v.last.value] }] + Hash[@data.map {|name, tag| [name, tag.value] }] end # Makes a human-readable representation of the current instance. # @return [String] def inspect @@ -114,14 +136,15 @@ # mh.validate \ # hello: [MetaHeader::REQUIRED, MetaHeader::SINGLELINE, /\d/], # chunky: proc {|value| 'not bacon' unless value == 'bacon' } # @param rules [Hash] tag_name => rule or array_of_rules # @return [Array, nil] error list or nil - # @see REQUIRED + # @see BOOLEAN # @see OPTIONAL - # @see VALUE + # @see REQUIRED # @see SINGLELINE + # @see VALUE def validate(rules) errors = Array.new if @strict @data.each_key {|key| @@ -146,10 +169,12 @@ (?:@(?<key>\w+)|(?<key>[\w][\w\s]*?)\s*:) (?:\s+(?<value>[^\n]+))? \Z/x.freeze def parse(line) + line.rstrip! + # multiline value must have the same prefix if @last_key && line.index(@last_prefix) == 0 # remove the line prefix mline = line[@last_prefix.size..-1] stripped = mline.strip @@ -157,16 +182,12 @@ indent_level = mline.index stripped if indent_level > 0 tag = @data[@last_key] - if tag.value.is_a? String - tag.value += "\n" - else - tag.value = String.new - end - + tag.value = @raw_value.to_s unless tag.value.is_a? String + tag.value += "\n" unless tag.value.empty? tag.value += stripped return else @last_key = nil @@ -175,17 +196,34 @@ return unless match = REGEX.match(line) # single line @last_prefix = match[:prefix] - @last_key = match[:key].downcase.gsub(/[^\w]/, '_').to_sym + key = match[:key].downcase.gsub(/[^\w]/, '_') - value = match[:value] || true + @raw_value = match[:value] + key, value = parse_value key, @raw_value + + @last_key = key.to_sym @data[@last_key] = Tag.new match[:key].freeze, value end + def parse_value(key, value) + value ||= true + case value + when 'true' + value = true + when 'false' + value = false + when String + value = nil if value.empty? + end + + [key, value] + end + def validate_key(key, rules) rules = Array(rules) return if rules.empty? unless @data.has_key? key @@ -210,19 +248,23 @@ end when VALUE if str_value.empty? return "missing value for tag '%s'" % tag.name end + when BOOLEAN + unless [TrueClass, FalseClass].include? tag.value.class + return "tag '%s' cannot have a value" % tag.name + end when Regexp unless rule.match str_value return "invalid value for tag '%s'" % tag.name end when Proc, Method if error = rule.call(tag.value) return "invalid value for tag '%s': %s" % [tag.name, error] end else - raise ArgumentError + raise ArgumentError, "unsupported validator #{rule}" end } nil end