module Linguist # A collection of simple heuristics that can be used to better analyze languages. class Heuristics # Public: Use heuristics to detect language of the blob. # # blob - An object that quacks like a blob. # possible_languages - Array of Language objects # # 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 @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 /^[^#]+:-/.match(data) # 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 # Common heuristics ObjectiveCRegex = /^[ \t]*@(interface|class|protocol|property|end|synchronised|selector|implementation)\b/ disambiguate "BitBake", "BlitzBasic" do |data| if /^\s*; /.match(data) || data.include?("End Function") Language["BlitzBasic"] elsif /^\s*(# |include|require)\b/.match(data) Language["BitBake"] end end disambiguate "C#", "Smalltalk" do |data| if /![\w\s]+methodsFor: /.match(data) Language["Smalltalk"] elsif /^\s*namespace\s*[\w\.]+\s*{/.match(data) || /^\s*\/\//.match(data) Language["C#"] end end disambiguate "Objective-C", "C++", "C" do |data| if ObjectiveCRegex.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* ")) Language["GAP"] # Heads up - we don't usually write heuristics like this (with no regex match) else Language["Scilab"] end end disambiguate "Common Lisp", "OpenCL", "Cool" do |data| if /^\s*\((defun|in-package|defpackage) /i.match(data) Language["Common Lisp"] elsif /^class/x.match(data) Language["Cool"] elsif /\/\* |\/\/ |^\}/.match(data) Language["OpenCL"] end end disambiguate "Hack", "PHP" do |data| if data.include?(" |case\s+(\S+\s)+of/.match(data) Language["Standard ML"] end end disambiguate "XML", "Modula-2", "Linux Kernel Module", "AMPL" do |data| if data.include?(')/.match(data) Language["Lex"] elsif /^\.[a-z][a-z](\s|$)/i.match(data) Language["Groff"] elsif /^\((de|class|rel|code|data|must)\s/.match(data) Language["PicoLisp"] end end disambiguate "Groff", "Nemerle" do |data| if /^[.']/.match(data) Language["Groff"] elsif /^(module|namespace|using)\s/.match(data) Language["Nemerle"] end end disambiguate "GAS", "Groff" do |data| if /^[.'][a-z][a-z](\s|$)/i.match(data) Language["Groff"] elsif /((^|\s)move?[. ])|\.(include|globa?l)\s/.match(data) Language["GAS"] end end disambiguate "xBase", "Charity" do |data| if /^\s*#\s*(if|ifdef|ifndef|define|command|xcommand|translate|xtranslate|include|pragma|undef)\b/i.match(data) Language["xBase"] end end end end