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