Module: Hecks::Literal
- Defined in:
- lib/hecks/literal.rb
Constant Summary collapse
- ESCAPED =
{ '"' => '\\"', "\\" => "\\\\" }.freeze
Class Method Summary collapse
- .quote(text) ⇒ Object
- .quoted?(raw) ⇒ Boolean
-
.read(text) ⇒ Object
Read one back.
- .read_array(raw) ⇒ Object
- .read_hash(raw) ⇒ Object
- .render(value) ⇒ Object
-
.split_items(body) ⇒ Object
Split on the commas that are actually SEPARATORS — never one inside a quoted string or a nested brace/bracket.
- .unquote(raw) ⇒ Object
Class Method Details
.quote(text) ⇒ Object
80 |
# File 'lib/hecks/literal.rb', line 80 def quote(text) = "\"#{text.gsub(/["\\]/) { |char| ESCAPED[char] }}\"" |
.quoted?(raw) ⇒ Boolean
82 |
# File 'lib/hecks/literal.rb', line 82 def quoted?(raw) = raw.length >= 2 && raw.start_with?('"') && raw.end_with?('"') |
.read(text) ⇒ Object
Read one back. Tolerant of a bare word on purpose: the language stores a closed set's members and a few hand-written fields as plain text that was never rendered, and those must stay the strings they are.
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/hecks/literal.rb', line 62 def read(text) raw = text.to_s.strip return nil if raw.empty? || raw == "nil" return true if raw == "true" return false if raw == "false" return raw.to_i if raw.match?(/\A-?\d+\z/) return raw.to_f if raw.match?(/\A-?\d+\.\d+\z/) return raw[1..].to_sym if raw.start_with?(":") return StateRef.new(raw[7..-2].to_sym) if raw.match?(/\Astate\(:[A-Za-z_][A-Za-z0-9_]*\)\z/) return unquote(raw) if quoted?(raw) return read_hash(raw) if raw.start_with?("{") && raw.end_with?("}") return read_array(raw) if raw.start_with?("[") && raw.end_with?("]") raw end |
.read_array(raw) ⇒ Object
93 |
# File 'lib/hecks/literal.rb', line 93 def read_array(raw) = split_items(raw[1..-2]).map { |item| read(item) } |
.read_hash(raw) ⇒ Object
86 87 88 89 90 91 |
# File 'lib/hecks/literal.rb', line 86 def read_hash(raw) split_items(raw[1..-2]).to_h do |item| key, _, held = item.partition(":") [key.strip.to_sym, read(held)] end end |
.render(value) ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/hecks/literal.rb', line 43 def render(value) case value when nil then "nil" when StateRef then value.to_s when true, false then value.to_s when Symbol then ":#{value}" when Integer, Float then value.to_s when String then quote(value) when Hash then "{#{value.map { |key, held| "#{key}: #{render(held)}" }.join(', ')}}" when Array then "[#{value.map { |held| render(held) }.join(', ')}]" else raise ArgumentError, "#{value.class} has no pinned literal spelling — teach Literal.render one " \ "rather than letting #to_s decide it" end end |
.split_items(body) ⇒ Object
Split on the commas that are actually SEPARATORS — never one inside a
quoted string or a nested brace/bracket. Scanned rather than
String#split(", "), which tore "a, b" in half and lost the second
field of anything nested.
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/hecks/literal.rb', line 99 def split_items(body) items = [] current = +"" depth = 0 quoting = false escaping = false body.each_char do |char| current << char next (escaping = false) if escaping next (escaping = true) if quoting && char == "\\" next (quoting = !quoting) if char == '"' next if quoting depth += 1 if "{[".include?(char) depth -= 1 if "}]".include?(char) next unless char == "," && depth.zero? current.chop! items << current current = +"" end items << current items.map(&:strip).reject(&:empty?) end |
.unquote(raw) ⇒ Object
84 |
# File 'lib/hecks/literal.rb', line 84 def unquote(raw) = raw[1..-2].gsub(/\\(.)/) { ::Regexp.last_match(1) } |