hosts 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,252 @@
1
+ # encoding: UTF-8
2
+ =begin
3
+ Copyright Alexander E. Fischer <aef@raxys.net>, 2012
4
+
5
+ This file is part of Hosts.
6
+
7
+ Permission to use, copy, modify, and/or distribute this software for any
8
+ purpose with or without fee is hereby granted, provided that the above
9
+ copyright notice and this permission notice appear in all copies.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
13
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17
+ PERFORMANCE OF THIS SOFTWARE.
18
+ =end
19
+
20
+ require 'aef/hosts'
21
+
22
+ module Aef
23
+ module Hosts
24
+
25
+ # This class represents a hosts file and aggregates its elements.
26
+ #
27
+ # It is able to parse host files from file-system or String and can
28
+ # generate a String representation of itself to String or file-system.
29
+ class File
30
+
31
+ include Helpers
32
+
33
+ # Regular expression to extract a comment line.
34
+ #
35
+ # @private
36
+ COMMENT_LINE_PATTERN = /^\s*#(.*)$/
37
+
38
+ # Regular expression to extract section headers and footers.
39
+ #
40
+ # @private
41
+ SECTION_MARKER_PATTERN = /^ -----(BEGIN|END) SECTION (.*)-----(?:[\r])?$/
42
+
43
+ # Regular expression to extract entry lines.
44
+ #
45
+ # @private
46
+ ENTRY_LINE_PATTERN = /^([^#]*)(?:#(.*))?$/
47
+
48
+ # The hosts file's elements.
49
+ #
50
+ # @return [Array<Aef::Hosts::Element>]
51
+ attr_accessor :elements
52
+
53
+ # The filesystem path of the hosts file.
54
+ #
55
+ # @return [Pathname, nil]
56
+ attr_reader :path
57
+
58
+ class << self
59
+ # Parses a hosts file given as path.
60
+ #
61
+ # @param [Pathname] the hosts file path
62
+ # @return [Aef::Hosts::File] a file
63
+ def read(path)
64
+ new(path).read
65
+ end
66
+
67
+ # Parses a hosts file given as String.
68
+ #
69
+ # @param [String] data a String representation of the hosts file
70
+ # @return [Aef::Hosts::File] a file
71
+ def parse(data)
72
+ new.parse(data)
73
+ end
74
+ end
75
+
76
+ # Initializes a file.
77
+ #
78
+ # @param [Pathname] path path to the hosts file
79
+ def initialize(path = nil)
80
+ reset
81
+ self.path = path
82
+ end
83
+
84
+ # Removes all elements.
85
+ #
86
+ # @return [Aef::Hosts::File] a self reference
87
+ def reset
88
+ @elements = []
89
+
90
+ self
91
+ end
92
+
93
+ # Deletes the cached String representations of all elements.
94
+ #
95
+ # @return [Aef::Hosts::File] a self reference
96
+ def invalidate_cache!
97
+ elements.each do |element|
98
+ element.invalidate_cache!
99
+ end
100
+
101
+ self
102
+ end
103
+
104
+ # Sets the filesystem path of the hosts file.
105
+ def path=(path)
106
+ @path = to_pathname(path)
107
+ end
108
+
109
+ # Parses a hosts file given as path.
110
+ #
111
+ # @param [Pathname] path override the path attribute for this operation
112
+ # @return [Aef::Hosts::File] a self reference
113
+ def read(path = nil)
114
+ path = path.nil? ? @path : to_pathname(path)
115
+
116
+ raise ArgumentError, 'No path given' unless path
117
+
118
+ parse(path.read)
119
+
120
+ self
121
+ end
122
+
123
+ # Parses a hosts file given as String.
124
+ #
125
+ # @param [String] data a String representation of the hosts file
126
+ # @return [Aef::Hosts::File] a self reference
127
+ def parse(data)
128
+ current_section = self
129
+
130
+ data.to_s.lines.each_with_index do |line, line_number|
131
+ line = Linebreak.encode(line, :unix)
132
+
133
+ if COMMENT_LINE_PATTERN =~ line
134
+ comment = $1
135
+
136
+ if SECTION_MARKER_PATTERN =~ comment
137
+ type = $1
138
+ name = $2
139
+
140
+ case type
141
+ when 'BEGIN'
142
+ unless current_section.is_a?(Section)
143
+ current_section = Section.new(
144
+ name,
145
+ :cache => {:header => line, :footer => nil}
146
+ )
147
+ else
148
+ raise ParserError, "Invalid cascading of sections. Cannot start new section '#{name}' without first closing section '#{current_section.name}' on line #{line_number + 1}."
149
+ end
150
+ when 'END'
151
+ if name == current_section.name
152
+ current_section.cache[:footer] = line
153
+ elements << current_section
154
+ current_section = self
155
+ else
156
+ raise ParserError, "Invalid closing of section. Found attempt to close section '#{name}' in body of section '#{current_section.name}' on line #{line_number + 1}."
157
+ end
158
+ end
159
+ else
160
+ current_section.elements << Comment.new(
161
+ comment,
162
+ :cache => line
163
+ )
164
+ end
165
+ else
166
+ ENTRY_LINE_PATTERN =~ line
167
+
168
+ entry = $1
169
+ comment = $2
170
+
171
+ if entry and not entry =~ /^\s+$/
172
+
173
+ split = entry.split(/\s+/)
174
+ split.shift if split.first == ''
175
+
176
+ address, name, *aliases = *split
177
+
178
+ current_section.elements << Entry.new(
179
+ address, name,
180
+ :aliases => aliases,
181
+ :comment => comment,
182
+ :cache => line
183
+ )
184
+ else
185
+ current_section.elements << EmptyElement.new(
186
+ :cache => line
187
+ )
188
+ end
189
+ end
190
+ end
191
+
192
+ self
193
+ end
194
+
195
+ # Generates a hosts file and writes it to a path.
196
+ #
197
+ # @param [Hash] options
198
+ # @option options [Pathname] :path overrides the path attribute for this
199
+ # operation
200
+ # @option options [true, false] :force_generation if set to true, the
201
+ # cache won't be used, even if it not empty
202
+ # @option options [:unix, :windows, :mac] :linebreak_encoding the
203
+ # linebreak encoding of the result. If nothing is specified the result
204
+ # will be encoded as if :unix was specified.
205
+ # @see Aef::Linebreak#encode
206
+ def write(options = {})
207
+ validate_options(options, :force_generation, :linebreak_encoding, :path)
208
+
209
+ path = options[:path].nil? ? @path : to_pathname(options[:path])
210
+
211
+ raise ArgumentError, 'No path given' unless path
212
+
213
+ options.delete(:path)
214
+
215
+ path.open('w') do |file|
216
+ file.write(to_s(options))
217
+ end
218
+
219
+ true
220
+ end
221
+
222
+ # A String representation for debugging purposes.
223
+ #
224
+ # @return [String]
225
+ def inspect
226
+ generate_inspect(self, :path, :elements)
227
+ end
228
+
229
+ # A String representation of the hosts file.
230
+ #
231
+ # @param [Hash] options
232
+ # @option options [true, false] :force_generation if set to true, the
233
+ # cache won't be used, even if it not empty
234
+ # @option options [:unix, :windows, :mac] :linebreak_encoding the
235
+ # linebreak encoding of the result. If nothing is specified the result
236
+ # will be encoded as if :unix was specified.
237
+ # @see Aef::Linebreak#encode
238
+ def to_s(options = {})
239
+ validate_options(options, :force_generation, :linebreak_encoding)
240
+
241
+ string = ''
242
+
243
+ @elements.each do |element|
244
+ string << element.to_s(options)
245
+ end
246
+
247
+ string
248
+ end
249
+
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,121 @@
1
+ # encoding: UTF-8
2
+ =begin
3
+ Copyright Alexander E. Fischer <aef@raxys.net>, 2012
4
+
5
+ This file is part of Hosts.
6
+
7
+ Permission to use, copy, modify, and/or distribute this software for any
8
+ purpose with or without fee is hereby granted, provided that the above
9
+ copyright notice and this permission notice appear in all copies.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
13
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17
+ PERFORMANCE OF THIS SOFTWARE.
18
+ =end
19
+
20
+ require 'aef/hosts'
21
+
22
+ module Aef
23
+ module Hosts
24
+
25
+ # Helper methods used internally in the hosts library
26
+ #
27
+ # @private
28
+ module Helpers
29
+
30
+ protected
31
+
32
+ # Ensures that an options Hash has includes only valid keys
33
+ #
34
+ # @param [Hash] options the options Hash to verify
35
+ # @param [Array<Symbol>] valid_keys a list of valid keys for the Hash
36
+ # @raise [ArgumentError] if Hash includes invalid keys
37
+ def validate_options(options, *valid_keys)
38
+ remainder = options.keys - valid_keys
39
+
40
+ unless remainder.empty?
41
+ raise ArgumentError, "Invalid option keys: #{remainder.map(&:inspect).join(',')}"
42
+ end
43
+ end
44
+
45
+ # Casts a given object to Pathname or passes through a given nil
46
+ #
47
+ # @param [String, Pathname, nil] path a filesystem path
48
+ # @return [Pathname, nil]
49
+ def to_pathname(path)
50
+ path.nil? ? nil : Pathname.new(path)
51
+ end
52
+
53
+ # Generates a String representation for debugging purposes.
54
+ #
55
+ # @param [Object] model a model for which the String is generated
56
+ # @param [Array<Symbol, String>] attributes Attributes to be displayed.
57
+ # If given as Symbol, the attribute's value will be presented by name
58
+ # and the inspect output of its value. If given as String, the String
59
+ # will represent it instead.
60
+ # @return [String] a string representation for debugging purposes
61
+ def generate_inspect(model, *attributes)
62
+ string = "#<#{model.class}"
63
+
64
+ components = []
65
+
66
+ attributes.each do |attribute|
67
+ if attribute == :cache
68
+ components << 'cached!' if model.send(:cache_filled?)
69
+ elsif attribute == :elements
70
+ components << generate_elements_partial(model.elements)
71
+ elsif attribute.is_a?(Symbol)
72
+ components << "#{attribute}=#{model.send(attribute).inspect}"
73
+ else
74
+ components << attribute
75
+ end
76
+ end
77
+
78
+ components.unshift(':') unless components.empty?
79
+
80
+ string << components.join(' ')
81
+ string << '>'
82
+ end
83
+
84
+ # Generate a partial String for an element listing in inspect output.
85
+ #
86
+ # @return [String] element partial
87
+ def generate_elements_partial(elements)
88
+ elements_string = 'elements=['
89
+
90
+ unless elements.empty?
91
+ elements_string << "\n"
92
+ elements_string << elements.map{|element|
93
+ element.inspect.lines.map{|line| " #{line}"}.join
94
+ }.join(",\n")
95
+ elements_string << "\n"
96
+ end
97
+
98
+ elements_string << "]"
99
+ end
100
+
101
+ # Sets a given attribute and executes the block if the current value
102
+ # differs from the given.
103
+ #
104
+ # @param [Symbol] attribute the attribute's name
105
+ # @param [Object] new_value the value to be assigned to the attribute if
106
+ # it differs from its current value
107
+ # @yield Executed if current value differs from the given
108
+ def set_if_changed(attribute, new_value)
109
+ variable_name = :"@#{attribute}"
110
+ old_value = instance_variable_get(variable_name)
111
+
112
+ if new_value != old_value
113
+ instance_variable_set(variable_name, new_value)
114
+
115
+ yield if block_given?
116
+ end
117
+ end
118
+
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,141 @@
1
+ # encoding: UTF-8
2
+ =begin
3
+ Copyright Alexander E. Fischer <aef@raxys.net>, 2012
4
+
5
+ This file is part of Hosts.
6
+
7
+ Permission to use, copy, modify, and/or distribute this software for any
8
+ purpose with or without fee is hereby granted, provided that the above
9
+ copyright notice and this permission notice appear in all copies.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
13
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17
+ PERFORMANCE OF THIS SOFTWARE.
18
+ =end
19
+
20
+ require 'aef/hosts'
21
+
22
+ module Aef
23
+ module Hosts
24
+
25
+ # This represents a section as element of a hosts file. It consists of a
26
+ # header, futher included elements and a footer
27
+ class Section < Element
28
+
29
+ # Title of the section
30
+ #
31
+ # @return [String]
32
+ attr_reader :name
33
+
34
+ # Elements inside the section
35
+ #
36
+ # @note If the Array is modified in place, you need to manually
37
+ # invalidate the cache with option :only_section => true.
38
+ # @see #invalidate_cache!
39
+ # @return [Array<Aef::Host::Element>]
40
+ attr_reader :elements
41
+
42
+ # Initializes a section
43
+ #
44
+ # @param [String] name title of the section
45
+ # @param [Hash] options
46
+ # @option options [String] :cache sets a cached String representation
47
+ # @option options [Array<Aef::Hosts::Element>] :elements a list of
48
+ # elements in the section
49
+ def initialize(name, options = {})
50
+ validate_options(options, :cache, :elements)
51
+
52
+ raise ArgumentError, 'Name cannot be empty' unless name
53
+
54
+ @name = name.to_s
55
+ @elements = options[:elements] || []
56
+ @cache = options[:cache] || {:header => nil, :footer => nil}
57
+ end
58
+
59
+ # Deletes the cached String representation
60
+ #
61
+ # @param [Hash] options
62
+ # @option [true, false] :only_section if set to true, the invalidation
63
+ # will not cascade onto the elements. Default is false.
64
+ # @return [Aef::Hosts::Section] a self reference
65
+ def invalidate_cache!(options = {})
66
+ @cache = {:header => nil, :footer => nil}
67
+
68
+ unless options[:only_section]
69
+ elements.each do |element|
70
+ element.invalidate_cache!
71
+ end
72
+ end
73
+ end
74
+
75
+ # Tells if a String representation is cached or not
76
+ #
77
+ # @return [true, false] true if cache is not empty
78
+ def cache_filled?
79
+ !!@cache[:header] && !!@cache[:footer]
80
+ end
81
+
82
+ # Sets the title of the section
83
+ def name=(name)
84
+ set_if_changed(:name, name.to_s) do
85
+ invalidate_cache!(:only_section => true)
86
+ end
87
+ end
88
+
89
+ # Sets the elements inside the section
90
+ def elements=(elements)
91
+ set_if_changed(:elements, [*elements]) do
92
+ invalidate_cache!(:only_section => true)
93
+ end
94
+ end
95
+
96
+ # A String representation for debugging purposes
97
+ #
98
+ # @return [String]
99
+ def inspect
100
+ generate_inspect(self, :name, :cache, :elements)
101
+ end
102
+
103
+ protected
104
+
105
+ # Defines the algorithm to generate a String representation from scratch.
106
+ #
107
+ # @return [String] a generated String representation
108
+ def generate_string(options = {})
109
+ string = ''
110
+
111
+ string << "# -----BEGIN SECTION #{name}-----\n"
112
+
113
+ @elements.each do |element|
114
+ string << element.to_s(options)
115
+ end
116
+
117
+ string << "# -----END SECTION #{name}-----\n"
118
+
119
+ string
120
+ end
121
+
122
+ # Defines the algorithm to construct the String representation from cache
123
+ #
124
+ # @return [String] the cached String representation
125
+ def cache_string(options = {})
126
+ string = ''
127
+
128
+ string << @cache[:header]
129
+
130
+ @elements.each do |element|
131
+ string << element.to_s(options)
132
+ end
133
+
134
+ string << @cache[:footer]
135
+
136
+ string
137
+ end
138
+
139
+ end
140
+ end
141
+ end