editorconfig 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7e98dc00d0f7381091b9a6e113edd9daeb81c17a
4
- data.tar.gz: e3e6ce431efd27f8923ac24b62db91b8cf308cd5
3
+ metadata.gz: 64a6b74e60347b7f0f46844b74d685a4cd137dab
4
+ data.tar.gz: 5f46d8b91c90c9b6c5a6fe92cf46445f88cd20ab
5
5
  SHA512:
6
- metadata.gz: eb431952375d5514474e35274db0e3ef24050774c7052a2c677ce1e5a8aef1010c91c90f67de603f9dc2e8fdd9be294ff5951da2e85ef4b3d42377466ea19813
7
- data.tar.gz: 5fd815440431dac67177cf84443e78362cf5581d58663b9ebbb80575243f15dab116b6fd24c1cfb5126409286baf34c3296be35b1600c65a76a4e703f51746e1
6
+ metadata.gz: cc966a7f53f0e581f1ebc4eaadcf8e391681d592ee323b42628cf17995a11e833aee2d53773411941c543eef931d4ab53c17a3e0f47be0435ab46178de00e2cf
7
+ data.tar.gz: 7f0db69b7e53300db3d2fc095ec7cb6fa6cb5eb9d8d4288b18d0cc2e3b2ba5d18ca817a4d4b596e8792a865d4944c0619aff42c6103e41d3f79272b75548bcca
data/lib/editor_config.rb CHANGED
@@ -1,8 +1,12 @@
1
1
  require "editor_config/version"
2
2
 
3
3
  module EditorConfig
4
+ # Public: Default config basename.
4
5
  CONFIG_FILENAME = ".editorconfig".freeze
5
6
 
7
+ # Public: Universal property names.
8
+ #
9
+ # https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties
6
10
  INDENT_STYLE = "indent_style".freeze
7
11
  INDENT_SIZE = "indent_size".freeze
8
12
  TAB_WIDTH = "tab_width".freeze
@@ -12,22 +16,38 @@ module EditorConfig
12
16
  INSERT_FINAL_NEWLINE = "insert_final_newline".freeze
13
17
  MAX_LINE_LENGTH = "max_line_length".freeze
14
18
 
19
+ # Public: Possible boolean values.
20
+ #
21
+ # https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties
15
22
  TRUE = "true".freeze
16
23
  FALSE = "false".freeze
17
24
 
25
+ # Public: Possible indent style values.
26
+ #
27
+ # https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties#indent_style
18
28
  SPACE = "space".freeze
19
29
  TAB = "tab".freeze
20
30
 
31
+ # Public: Possible EOL values.
32
+ #
33
+ # https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties#end_of_line
21
34
  CR = "cr".freeze
22
35
  LF = "lf".freeze
23
36
  CRLF = "crlf".freeze
24
37
 
38
+ # Public: Possible charset values.
39
+ #
40
+ # https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties#charset
25
41
  LATIN1 = "latin1".freeze
26
42
  UTF_8 = "utf-8".freeze
27
43
  UTF_8_BOM = "utf-8-bom".freeze
28
44
  UTF_16BE = "utf-16be".freeze
29
45
  UTF_16LE = "utf-16le".freeze
30
46
 
47
+ # Internal: Default filename to use if path is too long or has too many
48
+ # components.
49
+ DEFAULT_FILENAME = "filename".freeze
50
+
31
51
  # Internal: Maximum number of bytes to read per line. Lines over this limit
32
52
  # will be truncated.
33
53
  MAX_LINE = 200
@@ -40,69 +60,60 @@ module EditorConfig
40
60
  # will be truncated.
41
61
  MAX_PROPERTY_NAME = 500
42
62
 
43
- # Public: Parse a `.editorconfig` from a string.
44
- #
45
- # io - a String containing the contents of a `.editorconfig` file.
46
- #
47
- # Returns a hash of sections from the config file. Each section will be a
48
- # hash of key/value pairs for that section. The only top-level key that
49
- # won't have a Hash value is "root" which if it exists will be set to
50
- # `true`.
63
+ # Internal: Maximum byte length of filename path. Paths over this limit will
64
+ # default to global "*" properties.
65
+ MAX_FILENAME = 4096
66
+
67
+ # Internal: Maximum number of directories a filename can have. Paths this
68
+ # deep will default to global "*" properties.
69
+ MAX_FILENAME_COMPONENTS = 25
70
+
71
+ # Public: Parse the contents of an `.editorconfig`.
51
72
  #
52
- # Possible key/value pairs for sections are as follows:
53
- # "indent_style" - :tab, :space or nil.
54
- # "indent_size" - :tab, an integer between 1-64, or nil.
55
- # "tab_width" - an integer between 1-64, or nil.
56
- # "end_of_line" - :lf, :cr, :crlf or nil.
57
- # "charset" - "latin1", "utf-8", "utf-8-bom", "utf-16be",
58
- # "utf-16le" or nil.
59
- # "trim_trailing_whitespace" - true, false or nil.
60
- # "insert_final_newline" - true, false or nil.
73
+ # io - An IO or String with the raw contents of an `.editorconfig` file.
61
74
  #
62
- # If either of these keys exist but the value is nil, the key existed in the
63
- # editorconfig but it's value was invalid or not supported.
75
+ # Returns a tuple of a parsed Hash of information and a boolean flag if the
76
+ # file was marked as "root". The hash contains string keys of each section
77
+ # of the config file.
64
78
  #
65
79
  # An example hash would look like this:
66
80
  # {
67
- # "root" => true,
68
81
  # "*.rb" => {
69
- # "indent_style" => :space
70
- # "indent_size" => 2,
82
+ # "indent_style" => "space",
83
+ # "indent_size" => "2",
71
84
  # "charset" => "utf-8"
72
85
  # }
73
86
  # }
87
+ #
74
88
  def self.parse(io, version: SPEC_VERSION)
75
- # if !io.force_encoding("UTF-8").valid_encoding?
76
- # raise ArgumentError, "editorconfig syntax must be valid UTF-8"
77
- # end
78
-
79
- root = false
80
- out_hash = {}
89
+ config, root = {}, false
81
90
  last_section = nil
82
91
 
83
92
  io.each_line do |line|
84
- case line.chomp
85
- when /\Aroot(\s+)?\=(\s+)?true\Z/
93
+ line = line.sub(/\s+(;|#).+$/, "").chomp
94
+ case line
95
+ when /\Aroot(\s+)?\=(\s+)?true\Z/i
86
96
  root = true
87
- when /\A\[(?<name>.+)\]\Z/
97
+ when /\A\s*\[(?<name>.+)\]\s*\Z/
88
98
  # section marker
89
99
  last_section = Regexp.last_match[:name][0, MAX_SECTION_NAME]
90
- out_hash[last_section] = {}
91
- when /\A(?<name>[[:word:]]+)(\s+)?\=(\s+)?(?<value>.+)\Z/
100
+ config[last_section] ||= {}
101
+ when /\A\s*(?<name>[[:word:]]+)\s*(\=|:)\s*(?<value>.+)\s*\Z/
92
102
  match = Regexp.last_match
93
- name, value = match[:name][0, MAX_PROPERTY_NAME], match[:value]
94
-
95
- if last_section
96
- out_hash[last_section][name] = value
97
- else
98
- out_hash[name] = value
99
- end
103
+ name, value = match[:name][0, MAX_PROPERTY_NAME].strip, match[:value].strip
104
+ config[last_section][name] = value if last_section
100
105
  end
101
106
  end
102
107
 
103
- return out_hash, root
108
+ return config, root
104
109
  end
105
110
 
111
+ # Public: Normalize known universal properties.
112
+ #
113
+ # config - Hash configuration
114
+ # version - String spec version
115
+ #
116
+ # Returns new preprocessed Hash.
106
117
  def self.preprocess(config, version: SPEC_VERSION)
107
118
  config = config.reduce({}) { |h, (k, v)| h[k.downcase] = v; h }
108
119
 
@@ -138,29 +149,108 @@ module EditorConfig
138
149
  config
139
150
  end
140
151
 
152
+ # Internal: Temporary replacement constants used within fnmatch.
153
+ FNMATCH_ESCAPED_LBRACE = "FNMATCH-ESCAPED-LBRACE".freeze
154
+ FNMATCH_ESCAPED_RBRACE = "FNMATCH-ESCAPED-RBRACE".freeze
155
+
156
+ # Public: Test shell pattern against a path.
157
+ #
158
+ # Modeled after editorconfig/fnmatch.py.
159
+ # https://github.com/editorconfig/editorconfig-core-py/blob/master/editorconfig/fnmatch.py
160
+ #
161
+ # pattern - String shell pattern
162
+ # path - String pathname
163
+ #
164
+ # Returns true if path matches pattern, otherwise false.
141
165
  def self.fnmatch?(pattern, path)
142
166
  flags = File::FNM_PATHNAME | File::FNM_EXTGLOB
143
- File.fnmatch?(pattern, path, flags) ||
144
- File.fnmatch?(pattern, File.basename(path), flags)
167
+ pattern = pattern.dup
168
+
169
+ pattern.gsub!(/(\{\w*\})/) {
170
+ $1.gsub("{", FNMATCH_ESCAPED_LBRACE).gsub("}", FNMATCH_ESCAPED_RBRACE)
171
+ }
172
+ pattern.gsub!(/(\{[^}]+$)/) {
173
+ $1.gsub("{", FNMATCH_ESCAPED_LBRACE)
174
+ }
175
+ pattern.gsub!(/^([^\{]+\})/) {
176
+ $1.gsub("}", FNMATCH_ESCAPED_RBRACE)
177
+ }
178
+
179
+ pattern.gsub!(/(\{(.*)\})/) {
180
+ bracket = $1
181
+ inner = $2
182
+
183
+ if inner =~ /^(\d+)\.\.(\d+)$/
184
+ "{#{($1.to_i..$2.to_i).to_a.join(",")}}"
185
+ elsif inner.include?(",")
186
+ bracket
187
+ else
188
+ "#{FNMATCH_ESCAPED_LBRACE}#{inner}#{FNMATCH_ESCAPED_RBRACE}"
189
+ end
190
+ }
191
+
192
+ pattern.gsub!(FNMATCH_ESCAPED_LBRACE, "\\{")
193
+ pattern.gsub!(FNMATCH_ESCAPED_RBRACE, "\\}")
194
+
195
+ pattern.gsub!(/\[(.*\/.*)\]/) {
196
+ "\\[#{$1}\\]"
197
+ }
198
+
199
+ patterns = []
200
+
201
+ # Expand "**" to match over path separators
202
+ # TODO: Optimize the number of patterns we need
203
+ patterns << pattern.gsub(/\/\*\*/, "/**/*")
204
+ patterns << pattern.gsub(/\/\*\*/, "")
205
+ patterns << pattern.gsub(/\*\*/, "**/*")
206
+ patterns << pattern.gsub(/\*\*/, "/**/*")
207
+
208
+ patterns.any? { |p| File.fnmatch?(p, path, flags) }
145
209
  end
146
210
 
211
+ # Public: Load EditorConfig with a custom loader implementation.
212
+ #
213
+ # path - String filename on file system
214
+ # config - Basename of config to search for (default: .editorconfig)
215
+ #
216
+ # loader block
217
+ # config_path - String "path/to/.editorconfig" to attempt to read from
218
+ #
219
+ # Returns Hash of String properties and values.
147
220
  def self.load(path, config: CONFIG_FILENAME, version: SPEC_VERSION)
148
221
  hash = {}
149
222
 
150
- traverse(path).each do |subpath|
223
+ # Use default filename if path is too long
224
+ path = DEFAULT_FILENAME if path.length > MAX_FILENAME
225
+
226
+ components = traverse(path)
227
+
228
+ # Use default filename if path has too many directories
229
+ path = DEFAULT_FILENAME if components.length > MAX_FILENAME_COMPONENTS
230
+
231
+ components.each do |subpath|
151
232
  config_path = subpath == "" ? config : "#{subpath}/#{config}"
152
233
  config_data = yield config_path
153
234
  next unless config_data
154
235
 
155
- ini, root = parse(config_data, version: version)
236
+ sections, root = parse(config_data, version: version)
237
+ section_properties = {}
156
238
 
157
- ini.each do |pattern, properties|
158
- matcher = subpath == "" ? pattern : "#{subpath}/#{pattern}"
159
- if fnmatch?(matcher, path)
160
- hash = properties.merge(hash)
239
+ sections.each do |section, properties|
240
+ if section.include?("/")
241
+ section = section[1..-1] if section[0] == "/"
242
+ pattern = subpath == "" ? section : "#{subpath}/#{section}"
243
+ else
244
+ pattern = "**/#{section}"
245
+ end
246
+
247
+ if fnmatch?(pattern, path)
248
+ section_properties.merge!(properties)
161
249
  end
162
250
  end
163
251
 
252
+ hash = section_properties.merge(hash)
253
+
164
254
  if root
165
255
  break
166
256
  end
@@ -169,6 +259,11 @@ module EditorConfig
169
259
  hash
170
260
  end
171
261
 
262
+ # Internal: Generate subpaths for given path walking upwards to the root.
263
+ #
264
+ # path - String pathname
265
+ #
266
+ # Returns an Array of String paths.
172
267
  def self.traverse(path)
173
268
  paths = []
174
269
  parts = path.split("/", -1)
@@ -189,6 +284,15 @@ module EditorConfig
189
284
  paths
190
285
  end
191
286
 
287
+ # Public: Load EditorConfig for a specific file.
288
+ #
289
+ # Starts at filename and walks up each directory gathering any .editorconfig
290
+ # files until it reaches a config marked as "root".
291
+ #
292
+ # path - String filename on file system
293
+ # config - Basename of config to search for (default: .editorconfig)
294
+ #
295
+ # Returns Hash of String properties and values.
192
296
  def self.load_file(*args)
193
297
  EditorConfig.load(*args) do |path|
194
298
  File.read(path) if File.exist?(path)
@@ -1,4 +1,4 @@
1
1
  module EditorConfig
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  SPEC_VERSION = "0.9.1"
4
4
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: editorconfig
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-13 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2015-05-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
13
41
  description:
14
42
  email:
15
43
  executables: []
@@ -19,7 +47,7 @@ files:
19
47
  - lib/editor_config.rb
20
48
  - lib/editor_config/version.rb
21
49
  - lib/editorconfig.rb
22
- homepage:
50
+ homepage: https://github.com/editorconfig/editorconfig-core-ruby
23
51
  licenses:
24
52
  - MIT
25
53
  metadata: {}