diff --git a/lib/linguist/language.rb b/lib/linguist/language.rb index 365beb06..7fbf8a96 100644 --- a/lib/linguist/language.rb +++ b/lib/linguist/language.rb @@ -11,6 +11,7 @@ require 'linguist/samples' require 'linguist/file_blob' require 'linguist/blob_helper' require 'linguist/strategy/filename' +require 'linguist/strategy/modeline' require 'linguist/shebang' module Linguist @@ -94,6 +95,7 @@ module Linguist end STRATEGIES = [ + Linguist::Strategy::Modeline, Linguist::Strategy::Filename, Linguist::Shebang, Linguist::Heuristics, diff --git a/lib/linguist/strategy/modeline.rb b/lib/linguist/strategy/modeline.rb new file mode 100644 index 00000000..6a07fe3f --- /dev/null +++ b/lib/linguist/strategy/modeline.rb @@ -0,0 +1,30 @@ +module Linguist + module Strategy + class Modeline + EmacsModeline = /-\*-\s*(?:mode:)?\s*(\w+);?\s*-\*-/ + VimModeline = /\/\*\s*vim:\s*set\s*(?:ft|filetype)=(\w+):\s*\*\// + + # Public: Detects language based on Vim and Emacs modelines + # + # blob - An object that quacks like a blob. + # + # Examples + # + # Modeline.call(FileBlob.new("path/to/file")) + # + # Returns an Array with one Language if the blob has a Vim or Emacs modeline + # that matches a Language name or alias. Returns an empty array if no match. + def self.call(blob, _ = nil) + Array(Language.find_by_alias(modeline(blob.data))) + end + + # Public: Get the modeline from the first n-lines of the file + # + # Returns a String or nil + def self.modeline(data) + match = data.match(EmacsModeline) || data.match(VimModeline) + match[1] if match + end + end + end +end diff --git a/test/fixtures/Data/Modelines/example_smalltalk.md b/test/fixtures/Data/Modelines/example_smalltalk.md new file mode 100644 index 00000000..6a927e40 --- /dev/null +++ b/test/fixtures/Data/Modelines/example_smalltalk.md @@ -0,0 +1 @@ +; -*-Smalltalk-*- diff --git a/test/fixtures/Data/Modelines/iamphp.inc b/test/fixtures/Data/Modelines/iamphp.inc new file mode 100644 index 00000000..14370309 --- /dev/null +++ b/test/fixtures/Data/Modelines/iamphp.inc @@ -0,0 +1 @@ +; -*- mode: php;-*- diff --git a/test/fixtures/Data/Modelines/not_perl.pl b/test/fixtures/Data/Modelines/not_perl.pl new file mode 100644 index 00000000..df8e9fc5 --- /dev/null +++ b/test/fixtures/Data/Modelines/not_perl.pl @@ -0,0 +1,3 @@ +/* vim: set filetype=prolog: */ + +# I am Prolog diff --git a/test/fixtures/Data/Modelines/ruby b/test/fixtures/Data/Modelines/ruby new file mode 100644 index 00000000..9dee00eb --- /dev/null +++ b/test/fixtures/Data/Modelines/ruby @@ -0,0 +1,3 @@ +/* vim: set filetype=ruby: */ + +# I am Ruby diff --git a/test/fixtures/Data/Modelines/seeplusplus b/test/fixtures/Data/Modelines/seeplusplus new file mode 100644 index 00000000..a5cdd9d3 --- /dev/null +++ b/test/fixtures/Data/Modelines/seeplusplus @@ -0,0 +1,3 @@ +/* vim: set ft=cpp: */ + +I would like to be C++ please. diff --git a/test/helper.rb b/test/helper.rb index ebfeefc7..a6a03672 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -2,3 +2,21 @@ require "bundler/setup" require "minitest/autorun" require "mocha/setup" require "linguist" + +def fixtures_path + File.expand_path("../fixtures", __FILE__) +end + +def fixture_blob(name) + name = File.join(fixtures_path, name) unless name =~ /^\// + Linguist::FileBlob.new(name, fixtures_path) +end + +def samples_path + File.expand_path("../../samples", __FILE__) +end + +def sample_blob(name) + name = File.join(samples_path, name) unless name =~ /^\// + Linguist::FileBlob.new(name, samples_path) +end diff --git a/test/test_blob.rb b/test/test_blob.rb index 372ff13f..ceb54bb3 100644 --- a/test/test_blob.rb +++ b/test/test_blob.rb @@ -14,24 +14,6 @@ class TestBlob < Minitest::Test Encoding.default_external = @original_external end - def samples_path - File.expand_path("../../samples", __FILE__) - end - - def fixtures_path - File.expand_path("../fixtures", __FILE__) - end - - def sample_blob(name) - name = File.join(samples_path, name) unless name =~ /^\// - FileBlob.new(name, samples_path) - end - - def fixture_blob(name) - name = File.join(fixtures_path, name) unless name =~ /^\// - FileBlob.new(name, fixtures_path) - end - def script_blob(name) blob = sample_blob(name) blob.instance_variable_set(:@name, 'script') diff --git a/test/test_classifier.rb b/test/test_classifier.rb index 1d10d512..2ae2f45e 100644 --- a/test/test_classifier.rb +++ b/test/test_classifier.rb @@ -3,10 +3,6 @@ require_relative "./helper" class TestClassifier < Minitest::Test include Linguist - def samples_path - File.expand_path("../../samples", __FILE__) - end - def fixture(name) File.read(File.join(samples_path, name)) end diff --git a/test/test_generated.rb b/test/test_generated.rb index 1c3f8d90..b714e19e 100644 --- a/test/test_generated.rb +++ b/test/test_generated.rb @@ -3,10 +3,6 @@ require_relative "./helper" class TestGenerated < Minitest::Test include Linguist - def samples_path - File.expand_path("../../samples", __FILE__) - end - class DataLoadedError < StandardError; end def generated_without_loading_data(name) diff --git a/test/test_heuristics.rb b/test/test_heuristics.rb index b88d0fda..3ea4af78 100644 --- a/test/test_heuristics.rb +++ b/test/test_heuristics.rb @@ -3,10 +3,6 @@ require_relative "./helper" class TestHeuristcs < Minitest::Test include Linguist - def samples_path - File.expand_path("../../samples", __FILE__) - end - def fixture(name) File.read(File.join(samples_path, name)) end diff --git a/test/test_modelines.rb b/test/test_modelines.rb new file mode 100644 index 00000000..6c68cc87 --- /dev/null +++ b/test/test_modelines.rb @@ -0,0 +1,25 @@ +require_relative "./helper" + +class TestModelines < Minitest::Test + include Linguist + + def assert_modeline(language, blob) + assert_equal language, Linguist::Strategy::Modeline.call(blob).first + end + + def test_modeline_strategy + assert_modeline Language["Ruby"], fixture_blob("Data/Modelines/ruby") + assert_modeline Language["C++"], fixture_blob("Data/Modelines/seeplusplus") + assert_modeline Language["Prolog"], fixture_blob("Data/Modelines/not_perl.pl") + assert_modeline Language["Smalltalk"], fixture_blob("Data/Modelines/example_smalltalk.md") + assert_modeline Language["PHP"], fixture_blob("Data/Modelines/iamphp.inc") + end + + def test_modeline_languages + assert_equal Language["Ruby"], fixture_blob("Data/Modelines/ruby").language + assert_equal Language["C++"], fixture_blob("Data/Modelines/seeplusplus").language + assert_equal Language["Prolog"], fixture_blob("Data/Modelines/not_perl.pl").language + assert_equal Language["Smalltalk"], fixture_blob("Data/Modelines/example_smalltalk.md").language + assert_equal Language["PHP"], fixture_blob("Data/Modelines/iamphp.inc").language + end +end diff --git a/test/test_tokenizer.rb b/test/test_tokenizer.rb index 339d5485..780db019 100644 --- a/test/test_tokenizer.rb +++ b/test/test_tokenizer.rb @@ -3,10 +3,6 @@ require_relative "./helper" class TestTokenizer < Minitest::Test include Linguist - def samples_path - File.expand_path("../../samples", __FILE__) - end - def tokenize(data) data = File.read(File.join(samples_path, data.to_s)) if data.is_a?(Symbol) Tokenizer.tokenize(data)