github-linguist 1.0.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.
@@ -0,0 +1,56 @@
1
+ require 'linguist/blob_helper'
2
+
3
+ module Linguist
4
+ # A FileBlob is a wrapper around a File object to make it quack
5
+ # like a Grit::Blob. It provides the basic interface: `name`,
6
+ # `data`, and `size`.
7
+ class FileBlob
8
+ include BlobHelper
9
+
10
+ # Public: Initialize a new FileBlob from a path
11
+ #
12
+ # path - A path String that exists on the file system.
13
+ # base_path - Optional base to relativize the path
14
+ #
15
+ # Returns a FileBlob.
16
+ def initialize(path, base_path = nil)
17
+ @path = path
18
+ @name = base_path ? path.sub("#{base_path}/", '') : path
19
+ end
20
+
21
+ # Public: Filename
22
+ #
23
+ # Examples
24
+ #
25
+ # FileBlob.new("/path/to/linguist/lib/linguist.rb").name
26
+ # # => "/path/to/linguist/lib/linguist.rb"
27
+ #
28
+ # FileBlob.new("/path/to/linguist/lib/linguist.rb",
29
+ # "/path/to/linguist").name
30
+ # # => "lib/linguist.rb"
31
+ #
32
+ # Returns a String
33
+ attr_reader :name
34
+
35
+ # Public: Read file permissions
36
+ #
37
+ # Returns a String like '100644'
38
+ def mode
39
+ File.stat(@path).mode.to_s(8)
40
+ end
41
+
42
+ # Public: Read file contents.
43
+ #
44
+ # Returns a String.
45
+ def data
46
+ File.read(@path)
47
+ end
48
+
49
+ # Public: Get byte size
50
+ #
51
+ # Returns an Integer.
52
+ def size
53
+ File.size(@path)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,474 @@
1
+ require 'escape_utils'
2
+ require 'pygments'
3
+ require 'yaml'
4
+
5
+ module Linguist
6
+ # Language names that are recognizable by GitHub. Defined languages
7
+ # can be highlighted, searched and listed under the Top Languages page.
8
+ #
9
+ # Languages are defined in `lib/linguist/languages.yml`.
10
+ class Language
11
+ @languages = []
12
+ @overrides = {}
13
+ @index = {}
14
+ @name_index = {}
15
+ @alias_index = {}
16
+ @extension_index = {}
17
+ @filename_index = {}
18
+
19
+ # Valid Languages types
20
+ TYPES = [:data, :markup, :programming]
21
+
22
+ # Internal: Test if extension maps to multiple Languages.
23
+ #
24
+ # Returns true or false.
25
+ def self.ambiguous?(extension)
26
+ @overrides.include?(extension)
27
+ end
28
+
29
+ # Include?: Return overridden extensions.
30
+ #
31
+ # Returns extensions Array.
32
+ def self.overridden_extensions
33
+ @overrides.keys
34
+ end
35
+
36
+ # Internal: Create a new Language object
37
+ #
38
+ # attributes - A hash of attributes
39
+ #
40
+ # Returns a Language object
41
+ def self.create(attributes = {})
42
+ language = new(attributes)
43
+
44
+ @languages << language
45
+
46
+ # All Language names should be unique. Raise if there is a duplicate.
47
+ if @name_index.key?(language.name)
48
+ raise ArgumentError, "Duplicate language name: #{language.name}"
49
+ end
50
+
51
+ # Language name index
52
+ @index[language.name] = @name_index[language.name] = language
53
+
54
+ language.aliases.each do |name|
55
+ # All Language aliases should be unique. Raise if there is a duplicate.
56
+ if @alias_index.key?(name)
57
+ raise ArgumentError, "Duplicate alias: #{name}"
58
+ end
59
+
60
+ @index[name] = @alias_index[name] = language
61
+ end
62
+
63
+ language.extensions.each do |extension|
64
+ if extension !~ /^\./
65
+ raise ArgumentError, "Extension is missing a '.': #{extension.inspect}"
66
+ end
67
+
68
+ unless ambiguous?(extension)
69
+ # Index the extension with a leading ".": ".rb"
70
+ @extension_index[extension] = language
71
+
72
+ # Index the extension without a leading ".": "rb"
73
+ @extension_index[extension.sub(/^\./, '')] = language
74
+ end
75
+ end
76
+
77
+ language.overrides.each do |extension|
78
+ if extension !~ /^\./
79
+ raise ArgumentError, "Extension is missing a '.': #{extension.inspect}"
80
+ end
81
+
82
+ if l = @overrides[extension]
83
+ raise ArgumentError, "#{extension} is already overridden by #{l.name}"
84
+ end
85
+
86
+ @overrides[extension] = language
87
+ end
88
+
89
+ language.filenames.each do |filename|
90
+ @filename_index[filename] = language
91
+ end
92
+
93
+ language
94
+ end
95
+
96
+ # Public: Get all Languages
97
+ #
98
+ # Returns an Array of Languages
99
+ def self.all
100
+ @languages
101
+ end
102
+
103
+ # Public: Look up Language by its proper name.
104
+ #
105
+ # name - The String name of the Language
106
+ #
107
+ # Examples
108
+ #
109
+ # Language.find_by_name('Ruby')
110
+ # # => #<Language name="Ruby">
111
+ #
112
+ # Returns the Language or nil if none was found.
113
+ def self.find_by_name(name)
114
+ @name_index[name]
115
+ end
116
+
117
+ # Public: Look up Language by one of its aliases.
118
+ #
119
+ # name - A String alias of the Language
120
+ #
121
+ # Examples
122
+ #
123
+ # Language.find_by_alias('cpp')
124
+ # # => #<Language name="C++">
125
+ #
126
+ # Returns the Lexer or nil if none was found.
127
+ def self.find_by_alias(name)
128
+ @alias_index[name]
129
+ end
130
+
131
+ # Public: Look up Language by extension.
132
+ #
133
+ # extension - The extension String. May include leading "."
134
+ #
135
+ # Examples
136
+ #
137
+ # Language.find_by_extension('.rb')
138
+ # # => #<Language name="Ruby">
139
+ #
140
+ # Returns the Language or nil if none was found.
141
+ def self.find_by_extension(extension)
142
+ @extension_index[extension]
143
+ end
144
+
145
+ # Public: Look up Language by filename.
146
+ #
147
+ # filename - The path String.
148
+ #
149
+ # Examples
150
+ #
151
+ # Language.find_by_filename('foo.rb')
152
+ # # => #<Language name="Ruby">
153
+ #
154
+ # Returns the Language or nil if none was found.
155
+ def self.find_by_filename(filename)
156
+ basename, extname = File.basename(filename), File.extname(filename)
157
+ @filename_index[basename] || @extension_index[extname]
158
+ end
159
+
160
+ # Public: Look up Language by its name or lexer.
161
+ #
162
+ # name - The String name of the Language
163
+ #
164
+ # Examples
165
+ #
166
+ # Language['Ruby']
167
+ # # => #<Language name="Ruby">
168
+ #
169
+ # Language['ruby']
170
+ # # => #<Language name="Ruby">
171
+ #
172
+ # Returns the Language or nil if none was found.
173
+ def self.[](name)
174
+ @index[name]
175
+ end
176
+
177
+ # Public: A List of popular languages
178
+ #
179
+ # Popular languages are sorted to the top of language chooser
180
+ # dropdowns.
181
+ #
182
+ # This list is configured in "popular.yml".
183
+ #
184
+ # Returns an Array of Lexers.
185
+ def self.popular
186
+ @popular ||= all.select(&:popular?).sort_by { |lang| lang.name.downcase }
187
+ end
188
+
189
+ # Public: A List of non-popular languages
190
+ #
191
+ # Unpopular languages appear below popular ones in language
192
+ # chooser dropdowns.
193
+ #
194
+ # This list is created from all the languages not listed in "popular.yml".
195
+ #
196
+ # Returns an Array of Lexers.
197
+ def self.unpopular
198
+ @unpopular ||= all.select(&:unpopular?).sort_by { |lang| lang.name.downcase }
199
+ end
200
+
201
+ # Public: A List of languages with assigned colors.
202
+ #
203
+ # Returns an Array of Languages.
204
+ def self.colors
205
+ @colors ||= all.select(&:color).sort_by { |lang| lang.name.downcase }
206
+ end
207
+
208
+ # Public: A List of languages compatible with Ace.
209
+ #
210
+ # Returns an Array of Languages.
211
+ def self.ace_modes
212
+ @ace_modes ||= all.select(&:ace_mode).sort_by { |lang| lang.name.downcase }
213
+ end
214
+
215
+ # Internal: Initialize a new Language
216
+ #
217
+ # attributes - A hash of attributes
218
+ def initialize(attributes = {})
219
+ # @name is required
220
+ @name = attributes[:name] || raise(ArgumentError, "missing name")
221
+
222
+ # Set type
223
+ @type = attributes[:type] ? attributes[:type].to_sym : nil
224
+ if @type && !TYPES.include?(@type)
225
+ raise ArgumentError, "invalid type: #{@type}"
226
+ end
227
+
228
+ @color = attributes[:color]
229
+
230
+ # Set aliases
231
+ @aliases = [default_alias_name] + (attributes[:aliases] || [])
232
+
233
+ # Lookup Lexer object
234
+ @lexer = Pygments::Lexer.find_by_name(attributes[:lexer] || name) ||
235
+ raise(ArgumentError, "#{@name} is missing lexer")
236
+
237
+ @ace_mode = attributes[:ace_mode]
238
+
239
+ # Set legacy search term
240
+ @search_term = attributes[:search_term] || default_alias_name
241
+
242
+ # Set extensions or default to [].
243
+ @extensions = attributes[:extensions] || []
244
+ @overrides = attributes[:overrides] || []
245
+ @filenames = attributes[:filenames] || []
246
+
247
+ @primary_extension = attributes[:primary_extension] || default_primary_extension || extensions.first
248
+
249
+ # Prepend primary extension unless its already included
250
+ if primary_extension && !extensions.include?(primary_extension)
251
+ @extensions = [primary_extension] + extensions
252
+ end
253
+
254
+ # Set popular, and searchable flags
255
+ @popular = attributes.key?(:popular) ? attributes[:popular] : false
256
+ @searchable = attributes.key?(:searchable) ? attributes[:searchable] : true
257
+
258
+ # If group name is set, save the name so we can lazy load it later
259
+ if attributes[:group_name]
260
+ @group = nil
261
+ @group_name = attributes[:group_name]
262
+
263
+ # Otherwise we can set it to self now
264
+ else
265
+ @group = self
266
+ end
267
+ end
268
+
269
+ # Public: Get proper name
270
+ #
271
+ # Examples
272
+ #
273
+ # # => "Ruby"
274
+ # # => "Python"
275
+ # # => "Perl"
276
+ #
277
+ # Returns the name String
278
+ attr_reader :name
279
+
280
+ # Public: Get type.
281
+ #
282
+ # Returns a type Symbol or nil.
283
+ attr_reader :type
284
+
285
+ # Public: Get color.
286
+ #
287
+ # Returns a hex color String.
288
+ attr_reader :color
289
+
290
+ # Public: Get aliases
291
+ #
292
+ # Examples
293
+ #
294
+ # Language['C++'].aliases
295
+ # # => ["cpp"]
296
+ #
297
+ # Returns an Array of String names
298
+ attr_reader :aliases
299
+
300
+ # Deprecated: Get code search term
301
+ #
302
+ # Examples
303
+ #
304
+ # # => "ruby"
305
+ # # => "python"
306
+ # # => "perl"
307
+ #
308
+ # Returns the name String
309
+ attr_reader :search_term
310
+
311
+ # Public: Get Lexer
312
+ #
313
+ # Returns the Lexer
314
+ attr_reader :lexer
315
+
316
+ # Public: Get Ace mode
317
+ #
318
+ # Examples
319
+ #
320
+ # # => "text"
321
+ # # => "javascript"
322
+ # # => "c_cpp"
323
+ #
324
+ # Returns a String name or nil
325
+ attr_reader :ace_mode
326
+
327
+ # Public: Get extensions
328
+ #
329
+ # Examples
330
+ #
331
+ # # => ['.rb', '.rake', ...]
332
+ #
333
+ # Returns the extensions Array
334
+ attr_reader :extensions
335
+
336
+ # Deprecated: Get primary extension
337
+ #
338
+ # Defaults to the first extension but can be overriden
339
+ # in the languages.yml.
340
+ #
341
+ # The primary extension can not be nil. Tests should verify this.
342
+ #
343
+ # This attribute is only used by app/helpers/gists_helper.rb for
344
+ # creating the language dropdown. It really should be using `name`
345
+ # instead. Would like to drop primary extension.
346
+ #
347
+ # Returns the extension String.
348
+ attr_reader :primary_extension
349
+
350
+ # Internal: Get overridden extensions.
351
+ #
352
+ # Returns the extensions Array.
353
+ attr_reader :overrides
354
+
355
+ # Public: Get filenames
356
+ #
357
+ # Examples
358
+ #
359
+ # # => ['Rakefile', ...]
360
+ #
361
+ # Returns the extensions Array
362
+ attr_reader :filenames
363
+
364
+ # Public: Get URL escaped name.
365
+ #
366
+ # Examples
367
+ #
368
+ # "C%23"
369
+ # "C%2B%2B"
370
+ # "Common%20Lisp"
371
+ #
372
+ # Returns the escaped String.
373
+ def escaped_name
374
+ EscapeUtils.escape_url(name).gsub('+', '%20')
375
+ end
376
+
377
+ # Internal: Get default alias name
378
+ #
379
+ # Returns the alias name String
380
+ def default_alias_name
381
+ name.downcase.gsub(/\s/, '-')
382
+ end
383
+
384
+ # Internal: Get default primary extension.
385
+ #
386
+ # Returns the extension String.
387
+ def default_primary_extension
388
+ extensions.first
389
+ end
390
+
391
+ # Public: Get Language group
392
+ #
393
+ # Returns a Language
394
+ def group
395
+ @group ||= Language.find_by_name(@group_name)
396
+ end
397
+
398
+ # Public: Is it popular?
399
+ #
400
+ # Returns true or false
401
+ def popular?
402
+ @popular
403
+ end
404
+
405
+ # Public: Is it not popular?
406
+ #
407
+ # Returns true or false
408
+ def unpopular?
409
+ !popular?
410
+ end
411
+
412
+ # Public: Is it searchable?
413
+ #
414
+ # Unsearchable languages won't by indexed by solr and won't show
415
+ # up in the code search dropdown.
416
+ #
417
+ # Returns true or false
418
+ def searchable?
419
+ @searchable
420
+ end
421
+
422
+ # Public: Highlight syntax of text
423
+ #
424
+ # text - String of code to be highlighted
425
+ # options - A Hash of options (defaults to {})
426
+ #
427
+ # Returns html String
428
+ def colorize(text, options = {})
429
+ lexer.highlight(text, options = {})
430
+ end
431
+
432
+ # Public: Return name as String representation
433
+ def to_s
434
+ name
435
+ end
436
+
437
+ def ==(other)
438
+ eql?(other)
439
+ end
440
+
441
+ def eql?(other)
442
+ equal?(other)
443
+ end
444
+
445
+ def hash
446
+ name.hash
447
+ end
448
+
449
+ def inspect
450
+ "#<#{self.class} name=#{name}>"
451
+ end
452
+ end
453
+
454
+ popular = YAML.load_file(File.expand_path("../popular.yml", __FILE__))
455
+
456
+ YAML.load_file(File.expand_path("../languages.yml", __FILE__)).each do |name, options|
457
+ Language.create(
458
+ :name => name,
459
+ :color => options['color'],
460
+ :type => options['type'],
461
+ :aliases => options['aliases'],
462
+ :lexer => options['lexer'],
463
+ :ace_mode => options['ace_mode'],
464
+ :group_name => options['group'],
465
+ :searchable => options.key?('searchable') ? options['searchable'] : true,
466
+ :search_term => options['search_term'],
467
+ :extensions => options['extensions'],
468
+ :primary_extension => options['primary_extension'],
469
+ :overrides => options['overrides'],
470
+ :filenames => options['filenames'],
471
+ :popular => popular.include?(name)
472
+ )
473
+ end
474
+ end