github-linguist 1.0.0

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