From 54ae7e7b4d2702797476da05cd740f28f568e412 Mon Sep 17 00:00:00 2001 From: Paul Chaignon Date: Tue, 17 Apr 2018 10:02:57 +0200 Subject: [PATCH] Strategies take result from previous strategy into account (#4099) Each strategy takes as candidates the language outputted by the previous strategy if any. This was already the case for the Classifier and Heuristic strategies as these couldn't generate new candidate languages (as opposed to the Modeline, Filename, Shebang, and Extension strategies). In practice, this signifies that if, for example, the Shebang strategy finds two possible languages for a given file (as is currently possible with the perl interpreter), the next strategy, the Extension strategy, will use this information and further reduce the set of possible language. Currently, without this commit, the Extension strategy would discard the results from the previous strategy and start anew, possibly returning a different language from those returned by the Shebang strategy. --- lib/linguist/shebang.rb | 11 ++++--- lib/linguist/strategy/extension.rb | 17 ++++++++-- lib/linguist/strategy/filename.rb | 17 ++++++++-- test/fixtures/Perl/01-methods.pl | 51 ++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 test/fixtures/Perl/01-methods.pl diff --git a/lib/linguist/shebang.rb b/lib/linguist/shebang.rb index 290dca70..7d1c0e8c 100644 --- a/lib/linguist/shebang.rb +++ b/lib/linguist/shebang.rb @@ -3,17 +3,20 @@ module Linguist # Public: Use shebang to detect language of the blob. # # blob - An object that quacks like a blob. + # candidates - A list of candidate languages. # # 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) + # Returns an array of languages from the candidate list for which the + # blob's shebang is valid. Returns an empty list if there is no shebang. + # If the candidate list is empty, any language is a valid candidate. + def self.call(blob, candidates) return [] if blob.symlink? - Language.find_by_interpreter interpreter(blob.data) + languages = Language.find_by_interpreter interpreter(blob.data) + candidates.any? ? candidates & languages : languages end # Public: Get the interpreter from the shebang diff --git a/lib/linguist/strategy/extension.rb b/lib/linguist/strategy/extension.rb index 161dc34c..b5dd6e9f 100644 --- a/lib/linguist/strategy/extension.rb +++ b/lib/linguist/strategy/extension.rb @@ -2,8 +2,21 @@ module Linguist module Strategy # Detects language based on extension class Extension - def self.call(blob, _) - Language.find_by_extension(blob.name.to_s) + # Public: Use the file extension to detect the blob's language. + # + # blob - An object that quacks like a blob. + # candidates - A list of candidate languages. + # + # Examples + # + # Extension.call(FileBlob.new("path/to/file")) + # + # Returns an array of languages associated with a blob's file extension. + # Selected languages must be in the candidate list, except if it's empty, + # in which case any language is a valid candidate. + def self.call(blob, candidates) + languages = Language.find_by_extension(blob.name.to_s) + candidates.any? ? candidates & languages : languages end end end diff --git a/lib/linguist/strategy/filename.rb b/lib/linguist/strategy/filename.rb index f3656515..39b9ff1b 100644 --- a/lib/linguist/strategy/filename.rb +++ b/lib/linguist/strategy/filename.rb @@ -2,9 +2,22 @@ module Linguist module Strategy # Detects language based on filename class Filename - def self.call(blob, _) + # Public: Use the filename to detect the blob's language. + # + # blob - An object that quacks like a blob. + # candidates - A list of candidate languages. + # + # Examples + # + # Filename.call(FileBlob.new("path/to/file")) + # + # Returns an array of languages with a associated blob's filename. + # Selected languages must be in the candidate list, except if it's empty, + # in which case any language is a valid candidate. + def self.call(blob, candidates) name = blob.name.to_s - Language.find_by_filename(name) + languages = Language.find_by_filename(name) + candidates.any? ? candidates & languages : languages end end end diff --git a/test/fixtures/Perl/01-methods.pl b/test/fixtures/Perl/01-methods.pl new file mode 100644 index 00000000..251bd4b0 --- /dev/null +++ b/test/fixtures/Perl/01-methods.pl @@ -0,0 +1,51 @@ +#!perl +use Test::More; +use Test::Exception; + +use_ok 'Music::ScaleNote'; + +my $msn = Music::ScaleNote->new( + scale_note => 'C', + scale_name => 'pminor', +# verbose => 1, +); +isa_ok $msn, 'Music::ScaleNote'; + +my $x; + +throws_ok { $x = $msn->get_offset() } + qr/note_name, note_format or offset not provided/, 'invalid get_offset'; + +my $format = 'midinum'; +$x = $msn->get_offset( + note_name => 60, + note_format => $format, + offset => 1, +); +is $x->format($format), 63, 'get_offset'; + +$format = 'ISO'; +$x = $msn->get_offset( + note_name => 'D#4', + note_format => $format, + offset => -1, +); +is $x->format($format), 'C4', 'get_offset'; + +throws_ok { + $x = $msn->get_offset( + note_name => 'C0', + note_format => $format, + offset => -1, + ) +} qr/Octave: -1 out of bounds/, 'out of bounds'; + +throws_ok { + $x = $msn->get_offset( + note_name => 'A#127', + note_format => $format, + offset => 1, + ) +} qr/Octave: 128 out of bounds/, 'out of bounds'; + +done_testing();