mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-10-29 17:50:22 +00:00
Merge branch 'master' into saltstack-states
This commit is contained in:
@@ -4,4 +4,5 @@ require 'linguist/heuristics'
|
||||
require 'linguist/language'
|
||||
require 'linguist/repository'
|
||||
require 'linguist/samples'
|
||||
require 'linguist/shebang'
|
||||
require 'linguist/version'
|
||||
|
||||
@@ -3,6 +3,25 @@ require 'linguist/tokenizer'
|
||||
module Linguist
|
||||
# Language bayesian classifier.
|
||||
class Classifier
|
||||
# Public: Use the classifier to detect language of the blob.
|
||||
#
|
||||
# blob - An object that quacks like a blob.
|
||||
# possible_languages - Array of Language objects
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# Classifier.call(FileBlob.new("path/to/file"), [
|
||||
# Language["Ruby"], Language["Python"]
|
||||
# ])
|
||||
#
|
||||
# Returns an Array of Language objects, most probable first.
|
||||
def self.call(blob, possible_languages)
|
||||
language_names = possible_languages.map(&:name)
|
||||
classify(Samples.cache, blob.data, language_names).map do |name, _|
|
||||
Language[name] # Return the actual Language objects
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Train classifier that data is a certain language.
|
||||
#
|
||||
# db - Hash classifier database object
|
||||
|
||||
@@ -57,14 +57,20 @@ module Linguist
|
||||
#
|
||||
# Returns a String.
|
||||
def extension
|
||||
# File.extname returns nil if the filename is an extension.
|
||||
extension = File.extname(name)
|
||||
basename = File.basename(name)
|
||||
# Checks if the filename is an extension.
|
||||
if extension.empty? && basename[0] == "."
|
||||
basename
|
||||
else
|
||||
extension
|
||||
extensions.last || ""
|
||||
end
|
||||
|
||||
# Public: Return an array of the file extensions
|
||||
#
|
||||
# >> Linguist::FileBlob.new("app/views/things/index.html.erb").extensions
|
||||
# => [".html.erb", ".erb"]
|
||||
#
|
||||
# Returns an Array
|
||||
def extensions
|
||||
basename, *segments = File.basename(name).split(".")
|
||||
|
||||
segments.map.with_index do |segment, index|
|
||||
"." + segments[index..-1].join(".")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,158 +1,160 @@
|
||||
module Linguist
|
||||
# A collection of simple heuristics that can be used to better analyze languages.
|
||||
class Heuristics
|
||||
ACTIVE = true
|
||||
|
||||
# Public: Given an array of String language names,
|
||||
# apply heuristics against the given data and return an array
|
||||
# of matching languages, or nil.
|
||||
# Public: Use heuristics to detect language of the blob.
|
||||
#
|
||||
# data - Array of tokens or String data to analyze.
|
||||
# languages - Array of language name Strings to restrict to.
|
||||
# blob - An object that quacks like a blob.
|
||||
# possible_languages - Array of Language objects
|
||||
#
|
||||
# Returns an array of Languages or []
|
||||
def self.find_by_heuristics(data, languages)
|
||||
if active?
|
||||
result = []
|
||||
# Examples
|
||||
#
|
||||
# Heuristics.call(FileBlob.new("path/to/file"), [
|
||||
# Language["Ruby"], Language["Python"]
|
||||
# ])
|
||||
#
|
||||
# Returns an Array of languages, or empty if none matched or were inconclusive.
|
||||
def self.call(blob, languages)
|
||||
data = blob.data
|
||||
|
||||
if languages.all? { |l| ["Perl", "Prolog"].include?(l) }
|
||||
result = disambiguate_pl(data)
|
||||
end
|
||||
if languages.all? { |l| ["ECL", "Prolog"].include?(l) }
|
||||
result = disambiguate_ecl(data)
|
||||
end
|
||||
if languages.all? { |l| ["IDL", "Prolog"].include?(l) }
|
||||
result = disambiguate_pro(data)
|
||||
end
|
||||
if languages.all? { |l| ["Common Lisp", "OpenCL"].include?(l) }
|
||||
result = disambiguate_cl(data)
|
||||
end
|
||||
if languages.all? { |l| ["Hack", "PHP"].include?(l) }
|
||||
result = disambiguate_hack(data)
|
||||
end
|
||||
if languages.all? { |l| ["Scala", "SuperCollider"].include?(l) }
|
||||
result = disambiguate_sc(data)
|
||||
end
|
||||
if languages.all? { |l| ["AsciiDoc", "AGS Script"].include?(l) }
|
||||
result = disambiguate_asc(data)
|
||||
end
|
||||
if languages.all? { |l| ["FORTRAN", "Forth"].include?(l) }
|
||||
result = disambiguate_f(data)
|
||||
end
|
||||
return result
|
||||
@heuristics.each do |heuristic|
|
||||
return Array(heuristic.call(data)) if heuristic.matches?(languages)
|
||||
end
|
||||
|
||||
[] # No heuristics matched
|
||||
end
|
||||
|
||||
# Internal: Define a new heuristic.
|
||||
#
|
||||
# languages - String names of languages to disambiguate.
|
||||
# heuristic - Block which takes data as an argument and returns a Language or nil.
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# disambiguate "Perl", "Prolog" do |data|
|
||||
# if data.include?("use strict")
|
||||
# Language["Perl"]
|
||||
# elsif data.include?(":-")
|
||||
# Language["Prolog"]
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def self.disambiguate(*languages, &heuristic)
|
||||
@heuristics << new(languages, &heuristic)
|
||||
end
|
||||
|
||||
# Internal: Array of defined heuristics
|
||||
@heuristics = []
|
||||
|
||||
# Internal
|
||||
def initialize(languages, &heuristic)
|
||||
@languages = languages
|
||||
@heuristic = heuristic
|
||||
end
|
||||
|
||||
# Internal: Check if this heuristic matches the candidate languages.
|
||||
def matches?(candidates)
|
||||
candidates.any? && candidates.all? { |l| @languages.include?(l.name) }
|
||||
end
|
||||
|
||||
# Internal: Perform the heuristic
|
||||
def call(data)
|
||||
@heuristic.call(data)
|
||||
end
|
||||
|
||||
disambiguate "Objective-C", "C++", "C" do |data|
|
||||
if (/@(interface|class|protocol|property|end|synchronised|selector|implementation)\b/.match(data))
|
||||
Language["Objective-C"]
|
||||
elsif (/^\s*#\s*include <(cstdint|string|vector|map|list|array|bitset|queue|stack|forward_list|unordered_map|unordered_set|(i|o|io)stream)>/.match(data) ||
|
||||
/^\s*template\s*</.match(data) || /^[^@]class\s+\w+/.match(data) || /^[^@](private|public|protected):$/.match(data) || /std::.+$/.match(data))
|
||||
Language["C++"]
|
||||
end
|
||||
end
|
||||
|
||||
# .h extensions are ambiguous between C, C++, and Objective-C.
|
||||
# We want to shortcut look for Objective-C _and_ now C++ too!
|
||||
#
|
||||
# Returns an array of Languages or []
|
||||
def self.disambiguate_c(data)
|
||||
matches = []
|
||||
if data.include?("@interface")
|
||||
matches << Language["Objective-C"]
|
||||
elsif data.include?("#include <cstdint>")
|
||||
matches << Language["C++"]
|
||||
end
|
||||
matches
|
||||
end
|
||||
|
||||
def self.disambiguate_pl(data)
|
||||
matches = []
|
||||
if data.include?("use strict")
|
||||
matches << Language["Perl"]
|
||||
disambiguate "Perl", "Perl6", "Prolog" do |data|
|
||||
if data.include?("use v6")
|
||||
Language["Perl6"]
|
||||
elsif data.include?("use strict")
|
||||
Language["Perl"]
|
||||
elsif data.include?(":-")
|
||||
matches << Language["Prolog"]
|
||||
Language["Prolog"]
|
||||
end
|
||||
matches
|
||||
end
|
||||
|
||||
def self.disambiguate_ecl(data)
|
||||
matches = []
|
||||
disambiguate "ECL", "Prolog" do |data|
|
||||
if data.include?(":-")
|
||||
matches << Language["Prolog"]
|
||||
Language["Prolog"]
|
||||
elsif data.include?(":=")
|
||||
matches << Language["ECL"]
|
||||
Language["ECL"]
|
||||
end
|
||||
matches
|
||||
end
|
||||
|
||||
def self.disambiguate_pro(data)
|
||||
matches = []
|
||||
if (data.include?(":-"))
|
||||
matches << Language["Prolog"]
|
||||
disambiguate "IDL", "Prolog" do |data|
|
||||
if data.include?(":-")
|
||||
Language["Prolog"]
|
||||
else
|
||||
matches << Language["IDL"]
|
||||
Language["IDL"]
|
||||
end
|
||||
matches
|
||||
end
|
||||
|
||||
def self.disambiguate_ts(data)
|
||||
matches = []
|
||||
if (data.include?("</translation>"))
|
||||
matches << Language["XML"]
|
||||
else
|
||||
matches << Language["TypeScript"]
|
||||
end
|
||||
matches
|
||||
end
|
||||
|
||||
def self.disambiguate_cl(data)
|
||||
matches = []
|
||||
disambiguate "Common Lisp", "OpenCL", "Cool" do |data|
|
||||
if data.include?("(defun ")
|
||||
matches << Language["Common Lisp"]
|
||||
Language["Common Lisp"]
|
||||
elsif /^class/x.match(data)
|
||||
Language["Cool"]
|
||||
elsif /\/\* |\/\/ |^\}/.match(data)
|
||||
matches << Language["OpenCL"]
|
||||
Language["OpenCL"]
|
||||
end
|
||||
matches
|
||||
end
|
||||
|
||||
def self.disambiguate_r(data)
|
||||
matches = []
|
||||
matches << Language["Rebol"] if /\bRebol\b/i.match(data)
|
||||
matches << Language["R"] if data.include?("<-")
|
||||
matches
|
||||
end
|
||||
|
||||
def self.disambiguate_hack(data)
|
||||
matches = []
|
||||
disambiguate "Hack", "PHP" do |data|
|
||||
if data.include?("<?hh")
|
||||
matches << Language["Hack"]
|
||||
Language["Hack"]
|
||||
elsif /<?[^h]/.match(data)
|
||||
matches << Language["PHP"]
|
||||
Language["PHP"]
|
||||
end
|
||||
matches
|
||||
end
|
||||
|
||||
def self.disambiguate_sc(data)
|
||||
matches = []
|
||||
if (/\^(this|super)\./.match(data) || /^\s*(\+|\*)\s*\w+\s*{/.match(data) || /^\s*~\w+\s*=\./.match(data))
|
||||
matches << Language["SuperCollider"]
|
||||
disambiguate "Scala", "SuperCollider" do |data|
|
||||
if /\^(this|super)\./.match(data) || /^\s*(\+|\*)\s*\w+\s*{/.match(data) || /^\s*~\w+\s*=\./.match(data)
|
||||
Language["SuperCollider"]
|
||||
elsif /^\s*import (scala|java)\./.match(data) || /^\s*val\s+\w+\s*=/.match(data) || /^\s*class\b/.match(data)
|
||||
Language["Scala"]
|
||||
end
|
||||
if (/^\s*import (scala|java)\./.match(data) || /^\s*val\s+\w+\s*=/.match(data) || /^\s*class\b/.match(data))
|
||||
matches << Language["Scala"]
|
||||
end
|
||||
matches
|
||||
end
|
||||
|
||||
def self.disambiguate_asc(data)
|
||||
matches = []
|
||||
matches << Language["AsciiDoc"] if /^=+(\s|\n)/.match(data)
|
||||
matches
|
||||
disambiguate "AsciiDoc", "AGS Script" do |data|
|
||||
Language["AsciiDoc"] if /^=+(\s|\n)/.match(data)
|
||||
end
|
||||
|
||||
def self.disambiguate_f(data)
|
||||
matches = []
|
||||
disambiguate "FORTRAN", "Forth" do |data|
|
||||
if /^: /.match(data)
|
||||
matches << Language["Forth"]
|
||||
Language["Forth"]
|
||||
elsif /^([c*][^a-z]| subroutine\s)/i.match(data)
|
||||
matches << Language["FORTRAN"]
|
||||
Language["FORTRAN"]
|
||||
end
|
||||
matches
|
||||
end
|
||||
|
||||
def self.active?
|
||||
!!ACTIVE
|
||||
disambiguate "F#", "Forth", "GLSL" do |data|
|
||||
if /^(: |new-device)/.match(data)
|
||||
Language["Forth"]
|
||||
elsif /^\s*(#light|import|let|module|namespace|open|type)/.match(data)
|
||||
Language["F#"]
|
||||
elsif /^\s*(#include|#pragma|precision|uniform|varying|void)/.match(data)
|
||||
Language["GLSL"]
|
||||
end
|
||||
end
|
||||
|
||||
disambiguate "Gosu", "JavaScript" do |data|
|
||||
Language["Gosu"] if /^uses java\./.match(data)
|
||||
end
|
||||
|
||||
disambiguate "LoomScript", "LiveScript" do |data|
|
||||
if /^\s*package\s*[\w\.\/\*\s]*\s*{/.match(data)
|
||||
Language["LoomScript"]
|
||||
else
|
||||
Language["LiveScript"]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,6 +10,8 @@ require 'linguist/heuristics'
|
||||
require 'linguist/samples'
|
||||
require 'linguist/file_blob'
|
||||
require 'linguist/blob_helper'
|
||||
require 'linguist/strategy/filename'
|
||||
require 'linguist/shebang'
|
||||
|
||||
module Linguist
|
||||
# Language names that are recognizable by GitHub. Defined languages
|
||||
@@ -91,6 +93,13 @@ module Linguist
|
||||
language
|
||||
end
|
||||
|
||||
STRATEGIES = [
|
||||
Linguist::Strategy::Filename,
|
||||
Linguist::Shebang,
|
||||
Linguist::Heuristics,
|
||||
Linguist::Classifier
|
||||
]
|
||||
|
||||
# Public: Detects the Language of the blob.
|
||||
#
|
||||
# blob - an object that includes the Linguist `BlobHelper` interface;
|
||||
@@ -98,49 +107,22 @@ module Linguist
|
||||
#
|
||||
# Returns Language or nil.
|
||||
def self.detect(blob)
|
||||
name = blob.name.to_s
|
||||
|
||||
# Bail early if the blob is binary or empty.
|
||||
return nil if blob.likely_binary? || blob.binary? || blob.empty?
|
||||
|
||||
# A bit of an elegant hack. If the file is executable but extensionless,
|
||||
# append a "magic" extension so it can be classified with other
|
||||
# languages that have shebang scripts.
|
||||
extension = FileBlob.new(name).extension
|
||||
if extension.empty? && blob.mode && (blob.mode.to_i(8) & 05) == 05
|
||||
name += ".script!"
|
||||
end
|
||||
|
||||
# First try to find languages that match based on filename.
|
||||
possible_languages = find_by_filename(name)
|
||||
|
||||
# If there is more than one possible language with that extension (or no
|
||||
# extension at all, in the case of extensionless scripts), we need to continue
|
||||
# our detection work
|
||||
if possible_languages.length > 1
|
||||
data = blob.data
|
||||
possible_language_names = possible_languages.map(&:name)
|
||||
heuristic_languages = Heuristics.find_by_heuristics(data, possible_language_names)
|
||||
|
||||
if heuristic_languages.size > 1
|
||||
possible_language_names = heuristic_languages.map(&:name)
|
||||
# Call each strategy until one candidate is returned.
|
||||
STRATEGIES.reduce([]) do |languages, strategy|
|
||||
candidates = strategy.call(blob, languages)
|
||||
if candidates.size == 1
|
||||
return candidates.first
|
||||
elsif candidates.size > 1
|
||||
# More than one candidate was found, pass them to the next strategy.
|
||||
candidates
|
||||
else
|
||||
# No candiates were found, pass on languages from the previous strategy.
|
||||
languages
|
||||
end
|
||||
|
||||
# Check if there's a shebang line and use that as authoritative
|
||||
if (result = find_by_shebang(data)) && !result.empty?
|
||||
result.first
|
||||
# No shebang. Still more work to do. Try to find it with our heuristics.
|
||||
elsif heuristic_languages.size == 1
|
||||
heuristic_languages.first
|
||||
# Lastly, fall back to the probabilistic classifier.
|
||||
elsif classified = Classifier.classify(Samples.cache, data, possible_language_names).first
|
||||
# Return the actual Language object based of the string language name (i.e., first element of `#classify`)
|
||||
Language[classified[0]]
|
||||
end
|
||||
else
|
||||
# Simplest and most common case, we can just return the one match based on extension
|
||||
possible_languages.first
|
||||
end
|
||||
end.first
|
||||
end
|
||||
|
||||
# Public: Get all Languages
|
||||
@@ -190,8 +172,13 @@ module Linguist
|
||||
# Returns all matching Languages or [] if none were found.
|
||||
def self.find_by_filename(filename)
|
||||
basename = File.basename(filename)
|
||||
extname = FileBlob.new(filename).extension
|
||||
(@filename_index[basename] + find_by_extension(extname)).compact.uniq
|
||||
|
||||
# find the first extension with language definitions
|
||||
extname = FileBlob.new(filename).extensions.detect do |e|
|
||||
!@extension_index[e].empty?
|
||||
end
|
||||
|
||||
(@filename_index[basename] + @extension_index[extname]).compact.uniq
|
||||
end
|
||||
|
||||
# Public: Look up Languages by file extension.
|
||||
@@ -212,20 +199,26 @@ module Linguist
|
||||
@extension_index[extname]
|
||||
end
|
||||
|
||||
# Public: Look up Languages by shebang line.
|
||||
# DEPRECATED
|
||||
def self.find_by_shebang(data)
|
||||
@interpreter_index[Shebang.interpreter(data)]
|
||||
end
|
||||
|
||||
# Public: Look up Languages by interpreter.
|
||||
#
|
||||
# data - Array of tokens or String data to analyze.
|
||||
# interpreter - String of interpreter name
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# Language.find_by_shebang("#!/bin/bash\ndate;")
|
||||
# Language.find_by_interpreter("bash")
|
||||
# # => [#<Language name="Bash">]
|
||||
#
|
||||
# Returns the matching Language
|
||||
def self.find_by_shebang(data)
|
||||
@interpreter_index[Linguist.interpreter_from_shebang(data)]
|
||||
def self.find_by_interpreter(interpreter)
|
||||
@interpreter_index[interpreter]
|
||||
end
|
||||
|
||||
|
||||
# Public: Look up Language by its name or lexer.
|
||||
#
|
||||
# name - The String name of the Language
|
||||
|
||||
@@ -308,6 +308,8 @@ C:
|
||||
color: "#555"
|
||||
extensions:
|
||||
- .c
|
||||
- .C
|
||||
- .H
|
||||
- .cats
|
||||
- .h
|
||||
- .idc
|
||||
@@ -335,9 +337,6 @@ C++:
|
||||
- cpp
|
||||
extensions:
|
||||
- .cpp
|
||||
- .C
|
||||
- .CPP
|
||||
- .H
|
||||
- .c++
|
||||
- .cc
|
||||
- .cxx
|
||||
@@ -528,6 +527,12 @@ Component Pascal:
|
||||
- delphi
|
||||
- objectpascal
|
||||
|
||||
Cool:
|
||||
type: programming
|
||||
extensions:
|
||||
- .cl
|
||||
tm_scope: source.cool
|
||||
|
||||
Coq:
|
||||
type: programming
|
||||
extensions:
|
||||
@@ -559,6 +564,8 @@ Crystal:
|
||||
- .cr
|
||||
ace_mode: ruby
|
||||
tm_scope: source.ruby
|
||||
interpreters:
|
||||
- crystal
|
||||
|
||||
Cucumber:
|
||||
extensions:
|
||||
@@ -736,6 +743,8 @@ Erlang:
|
||||
- .es
|
||||
- .escript
|
||||
- .hrl
|
||||
interpreters:
|
||||
- escript
|
||||
|
||||
F#:
|
||||
type: programming
|
||||
@@ -815,6 +824,7 @@ Forth:
|
||||
- .for
|
||||
- .forth
|
||||
- .frt
|
||||
- .fs
|
||||
|
||||
Frege:
|
||||
type: programming
|
||||
@@ -868,6 +878,7 @@ GLSL:
|
||||
- .fp
|
||||
- .frag
|
||||
- .frg
|
||||
- .fs
|
||||
- .fshader
|
||||
- .geo
|
||||
- .geom
|
||||
@@ -931,6 +942,8 @@ Gnuplot:
|
||||
- .gnuplot
|
||||
- .plot
|
||||
- .plt
|
||||
interpreters:
|
||||
- gnuplot
|
||||
|
||||
Go:
|
||||
type: programming
|
||||
@@ -961,6 +974,12 @@ Grace:
|
||||
- .grace
|
||||
tm_scope: none
|
||||
|
||||
Gradle:
|
||||
type: data
|
||||
extensions:
|
||||
- .gradle
|
||||
tm_scope: source.groovy.gradle
|
||||
|
||||
Grammatical Framework:
|
||||
type: programming
|
||||
aliases:
|
||||
@@ -1196,13 +1215,15 @@ Ioke:
|
||||
color: "#078193"
|
||||
extensions:
|
||||
- .ik
|
||||
interpreters:
|
||||
- ioke
|
||||
|
||||
Isabelle:
|
||||
type: programming
|
||||
color: "#fdcd00"
|
||||
extensions:
|
||||
- .thy
|
||||
tm_scope: none
|
||||
tm_scope: source.isabelle.theory
|
||||
|
||||
J:
|
||||
type: programming
|
||||
@@ -1288,6 +1309,7 @@ JavaScript:
|
||||
- .bones
|
||||
- .es6
|
||||
- .frag
|
||||
- .gs
|
||||
- .jake
|
||||
- .jsb
|
||||
- .jsfl
|
||||
@@ -1470,6 +1492,12 @@ LookML:
|
||||
- .lookml
|
||||
tm_scope: source.yaml
|
||||
|
||||
LoomScript:
|
||||
type: programming
|
||||
extensions:
|
||||
- .ls
|
||||
tm_scope: source.loomscript
|
||||
|
||||
Lua:
|
||||
type: programming
|
||||
ace_mode: lua
|
||||
@@ -1594,6 +1622,8 @@ Mercury:
|
||||
type: programming
|
||||
color: "#abcdef"
|
||||
ace_mode: prolog
|
||||
interpreters:
|
||||
- mmi
|
||||
extensions:
|
||||
- .m
|
||||
- .moo
|
||||
@@ -1687,11 +1717,12 @@ Nit:
|
||||
|
||||
Nix:
|
||||
type: programming
|
||||
color: "#7070ff"
|
||||
extensions:
|
||||
- .nix
|
||||
aliases:
|
||||
- nixos
|
||||
tm_scope: none
|
||||
tm_scope: source.nix
|
||||
|
||||
Nu:
|
||||
type: programming
|
||||
@@ -1703,6 +1734,8 @@ Nu:
|
||||
filenames:
|
||||
- Nukefile
|
||||
tm_scope: source.scheme
|
||||
interpreters:
|
||||
- nush
|
||||
|
||||
NumPy:
|
||||
group: Python
|
||||
@@ -1832,6 +1865,13 @@ Oxygene:
|
||||
- .oxygene
|
||||
tm_scope: none
|
||||
|
||||
Oz:
|
||||
type: programming
|
||||
color: "#fcaf3e"
|
||||
extensions:
|
||||
- .oz
|
||||
tm_scope: source.oz
|
||||
|
||||
PAWN:
|
||||
type: programming
|
||||
color: "#dbb284"
|
||||
@@ -1849,7 +1889,6 @@ PHP:
|
||||
- .aw
|
||||
- .ctp
|
||||
- .fcgi
|
||||
- .module
|
||||
- .php3
|
||||
- .php4
|
||||
- .php5
|
||||
@@ -1889,6 +1928,8 @@ Parrot Assembly:
|
||||
- pasm
|
||||
extensions:
|
||||
- .pasm
|
||||
interpreters:
|
||||
- parrot
|
||||
tm_scope: none
|
||||
|
||||
Parrot Internal Representation:
|
||||
@@ -1899,6 +1940,8 @@ Parrot Internal Representation:
|
||||
- pir
|
||||
extensions:
|
||||
- .pir
|
||||
interpreters:
|
||||
- parrot
|
||||
|
||||
Pascal:
|
||||
type: programming
|
||||
@@ -1933,14 +1976,19 @@ Perl6:
|
||||
type: programming
|
||||
color: "#0298c3"
|
||||
extensions:
|
||||
- .p6
|
||||
- .6pl
|
||||
- .6pm
|
||||
- .nqp
|
||||
- .p6
|
||||
- .p6l
|
||||
- .p6m
|
||||
- .pl
|
||||
- .pl6
|
||||
- .pm
|
||||
- .pm6
|
||||
- .t
|
||||
interpreters:
|
||||
- perl6
|
||||
tm_scope: none
|
||||
|
||||
PigLatin:
|
||||
@@ -2005,6 +2053,8 @@ Prolog:
|
||||
- .ecl
|
||||
- .pro
|
||||
- .prolog
|
||||
interpreters:
|
||||
- swipl
|
||||
|
||||
Propeller Spin:
|
||||
type: programming
|
||||
@@ -2068,6 +2118,8 @@ Python:
|
||||
- wscript
|
||||
interpreters:
|
||||
- python
|
||||
- python2
|
||||
- python3
|
||||
|
||||
Python traceback:
|
||||
type: data
|
||||
@@ -2088,6 +2140,8 @@ QMake:
|
||||
extensions:
|
||||
- .pro
|
||||
- .pri
|
||||
interpreters:
|
||||
- qmake
|
||||
|
||||
R:
|
||||
type: programming
|
||||
@@ -2107,6 +2161,15 @@ R:
|
||||
interpreters:
|
||||
- Rscript
|
||||
|
||||
RAML:
|
||||
type: data
|
||||
lexer: YAML
|
||||
ace_mode: yaml
|
||||
tm_scope: source.yaml
|
||||
color: "#77d9fb"
|
||||
extensions:
|
||||
- .raml
|
||||
|
||||
RDoc:
|
||||
type: prose
|
||||
ace_mode: rdoc
|
||||
@@ -2242,6 +2305,8 @@ Ruby:
|
||||
- .watchr
|
||||
interpreters:
|
||||
- ruby
|
||||
- macruby
|
||||
- rake
|
||||
filenames:
|
||||
- .pryrc
|
||||
- Appraisals
|
||||
@@ -2318,7 +2383,6 @@ Sass:
|
||||
group: CSS
|
||||
extensions:
|
||||
- .sass
|
||||
- .scss
|
||||
|
||||
Scala:
|
||||
type: programming
|
||||
@@ -2328,6 +2392,8 @@ Scala:
|
||||
- .scala
|
||||
- .sbt
|
||||
- .sc
|
||||
interpreters:
|
||||
- scala
|
||||
|
||||
Scaml:
|
||||
group: HTML
|
||||
@@ -2718,6 +2784,7 @@ XML:
|
||||
- .jelly
|
||||
- .kml
|
||||
- .launch
|
||||
- .mm
|
||||
- .mxml
|
||||
- .nproj
|
||||
- .nuspec
|
||||
|
||||
@@ -6,6 +6,7 @@ end
|
||||
|
||||
require 'linguist/md5'
|
||||
require 'linguist/classifier'
|
||||
require 'linguist/shebang'
|
||||
|
||||
module Linguist
|
||||
# Model for accessing classifier training data.
|
||||
@@ -52,14 +53,16 @@ module Linguist
|
||||
})
|
||||
end
|
||||
else
|
||||
path = File.join(dirname, filename)
|
||||
|
||||
if File.extname(filename) == ""
|
||||
raise "#{File.join(dirname, filename)} is missing an extension, maybe it belongs in filenames/ subdir"
|
||||
raise "#{path} is missing an extension, maybe it belongs in filenames/ subdir"
|
||||
end
|
||||
|
||||
yield({
|
||||
:path => File.join(dirname, filename),
|
||||
:path => path,
|
||||
:language => category,
|
||||
:interpreter => File.exist?(filename) ? Linguist.interpreter_from_shebang(File.read(filename)) : nil,
|
||||
:interpreter => Shebang.interpreter(File.read(path)),
|
||||
:extname => File.extname(filename)
|
||||
})
|
||||
end
|
||||
@@ -112,40 +115,4 @@ module Linguist
|
||||
db
|
||||
end
|
||||
end
|
||||
|
||||
# Used to retrieve the interpreter from the shebang line of a file's
|
||||
# data.
|
||||
def self.interpreter_from_shebang(data)
|
||||
lines = data.lines.to_a
|
||||
|
||||
if lines.any? && (match = lines[0].match(/(.+)\n?/)) && (bang = match[0]) =~ /^#!/
|
||||
bang.sub!(/^#! /, '#!')
|
||||
tokens = bang.split(' ')
|
||||
pieces = tokens.first.split('/')
|
||||
|
||||
if pieces.size > 1
|
||||
script = pieces.last
|
||||
else
|
||||
script = pieces.first.sub('#!', '')
|
||||
end
|
||||
|
||||
script = script == 'env' ? tokens[1] : script
|
||||
|
||||
# "python2.6" -> "python"
|
||||
if script =~ /((?:\d+\.?)+)/
|
||||
script.sub! $1, ''
|
||||
end
|
||||
|
||||
# Check for multiline shebang hacks that call `exec`
|
||||
if script == 'sh' &&
|
||||
lines[0...5].any? { |l| l.match(/exec (\w+).+\$0.+\$@/) }
|
||||
script = $1
|
||||
end
|
||||
|
||||
script
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
44
lib/linguist/shebang.rb
Normal file
44
lib/linguist/shebang.rb
Normal file
@@ -0,0 +1,44 @@
|
||||
module Linguist
|
||||
class Shebang
|
||||
# Public: Use shebang to detect language of the blob.
|
||||
#
|
||||
# blob - An object that quacks like a blob.
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# Shebang.call(FileBlob.new("path/to/file"))
|
||||
#
|
||||
# Returns an Array with one Language if the blob has a shebang with a valid
|
||||
# interpreter, or empty if there is no shebang.
|
||||
def self.call(blob, _ = nil)
|
||||
Language.find_by_interpreter interpreter(blob.data)
|
||||
end
|
||||
|
||||
# Public: Get the interpreter from the shebang
|
||||
#
|
||||
# Returns a String or nil
|
||||
def self.interpreter(data)
|
||||
lines = data.lines
|
||||
return unless match = /^#! ?(.+)$/.match(lines.first)
|
||||
|
||||
tokens = match[1].split(' ')
|
||||
script = tokens.first.split('/').last
|
||||
|
||||
script = tokens[1] if script == 'env'
|
||||
|
||||
# If script has an invalid shebang, we might get here
|
||||
return unless script
|
||||
|
||||
# "python2.6" -> "python2"
|
||||
script.sub! $1, '' if script =~ /(\.\d+)$/
|
||||
|
||||
# Check for multiline shebang hacks that call `exec`
|
||||
if script == 'sh' &&
|
||||
lines.first(5).any? { |l| l.match(/exec (\w+).+\$0.+\$@/) }
|
||||
script = $1
|
||||
end
|
||||
|
||||
File.basename(script)
|
||||
end
|
||||
end
|
||||
end
|
||||
20
lib/linguist/strategy/filename.rb
Normal file
20
lib/linguist/strategy/filename.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
module Linguist
|
||||
module Strategy
|
||||
# Detects language based on filename and/or extension
|
||||
class Filename
|
||||
def self.call(blob, _)
|
||||
name = blob.name.to_s
|
||||
|
||||
# A bit of an elegant hack. If the file is executable but extensionless,
|
||||
# append a "magic" extension so it can be classified with other
|
||||
# languages that have shebang scripts.
|
||||
extensions = FileBlob.new(name).extensions
|
||||
if extensions.empty? && blob.mode && (blob.mode.to_i(8) & 05) == 05
|
||||
name += ".script!"
|
||||
end
|
||||
|
||||
Language.find_by_filename(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -232,9 +232,6 @@
|
||||
# .DS_Store's
|
||||
- .[Dd][Ss]_[Ss]tore$
|
||||
|
||||
# Mercury --use-subdirs
|
||||
- Mercury/
|
||||
|
||||
# R packages
|
||||
- ^vignettes/
|
||||
- ^inst/extdata/
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module Linguist
|
||||
VERSION = "4.0.3"
|
||||
VERSION = "4.2.3"
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user