forked from crystal-lang/crystal
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathini.cr
88 lines (77 loc) · 2.46 KB
/
ini.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
module INI
# Exception thrown on an INI parse error.
class ParseException < Exception
getter line_number : Int32
getter column_number : Int32
def initialize(message, @line_number, @column_number)
super "#{message} at line #{@line_number}, column #{@column_number}"
end
def location
{line_number, column_number}
end
end
# Parses INI-style configuration from the given string.
# Raises a `ParseException` on any errors.
#
# ```
# require "ini"
#
# INI.parse("[foo]\na = 1") # => {"foo" => {"a" => "1"}}
# ```
def self.parse(string_or_io : String | IO) : Hash(String, Hash(String, String))
ini = Hash(String, Hash(String, String)).new
current_section = ini[""] = Hash(String, String).new
lineno = 0
string_or_io.each_line do |line|
lineno += 1
next if line.empty?
offset = 0
line.each_char do |char|
break unless char.ascii_whitespace?
offset += 1
end
case line[offset]
when '#', ';'
next
when '['
end_idx = line.index(']', offset)
raise ParseException.new("unterminated section", lineno, line.size) unless end_idx
raise ParseException.new("data after section", lineno, end_idx + 1) unless end_idx == line.size - 1
current_section_name = line[offset + 1...end_idx]
current_section = ini[current_section_name] ||= Hash(String, String).new
else
key, eq, value = line.partition('=')
raise ParseException.new("expected declaration", lineno, key.size) if eq != "="
current_section[key.strip] = value.strip
end
end
ini.delete("") if ini[""].empty?
ini
end
# Generates an INI-style configuration from a given hash.
#
# ```
# require "ini"
#
# INI.build({"foo" => {"a" => "1"}}, true) # => "[foo]\na = 1\n\n"
# ```
def self.build(ini, space : Bool = false) : String
String.build { |str| build str, ini, space }
end
# Appends INI data to the given IO.
def self.build(io : IO, ini, space : Bool = false) : Nil
# An empty section has to be at first, to prevent being included in another one.
ini[""]?.try &.each do |key, value|
io << key << (space ? " = " : '=') << value << '\n'
end
ini.each do |section, contents|
next if section.to_s.empty?
io << '[' << section << "]\n"
contents.each do |key, value|
io << key << (space ? " = " : '=') << value << '\n'
end
io.puts
end
io.flush
end
end