editorconfig 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: {}