如何检测片段的编程语言?

How can I detect the programming language of a snippet?

我有一个包含一些文本的字符串。文本可能是也可能不是代码。使用 Github 的语言学家,只有在我给它一个候选列表的情况下,我才能检测到可能的编程语言。

# test_linguist_1.rb
#!/usr/bin/env ruby

require 'linguist'

s = "int main(){}"
candidates = [Linguist::Language["Python"], Linguist::Language["C"], Linguist::Language["Ruby"]]
b = Linguist::Blob.new('', s)
langs = Linguist::Classifier.call(b, candidates)
puts langs.inspect

执行:

$ ./test_linguist_1.rb 
[#<Linguist::Language name=C>, #<Linguist::Language name=Python>, #<Linguist::Language name=Ruby>]

注意我给了它一个候选人名单。 如何避免必须定义候选人名单?

我尝试了以下方法:

# test_linguist_2.rb
#!/usr/bin/env ruby

require 'linguist'

s = "int main(){}"
candidates = Linguist::Language.all
# I also tried only Popular
# candidates = Linguist.Language.popular
b = Linguist::Blob.new('', s)
langs = Linguist::Classifier.call(b, candidates)
puts langs.inspect    

执行:

$ ./test_linguist_2.rb 
/home/marvelez/.rvm/gems/ruby-2.2.1/gems/github-linguist-4.8.9/lib/linguist/classifier.rb:131:in `token_probability': undefined method `[]' for nil:NilClass (NoMethodError)
from /home/marvelez/.rvm/gems/ruby-2.2.1/gems/github-linguist-4.8.9/lib/linguist/classifier.rb:120:in `block in tokens_probability'
from /home/marvelez/.rvm/gems/ruby-2.2.1/gems/github-linguist-4.8.9/lib/linguist/classifier.rb:119:in `each'
from /home/marvelez/.rvm/gems/ruby-2.2.1/gems/github-linguist-4.8.9/lib/linguist/classifier.rb:119:in `inject'
from /home/marvelez/.rvm/gems/ruby-2.2.1/gems/github-linguist-4.8.9/lib/linguist/classifier.rb:119:in `tokens_probability'
from /home/marvelez/.rvm/gems/ruby-2.2.1/gems/github-linguist-4.8.9/lib/linguist/classifier.rb:105:in `block in classify'
from /home/marvelez/.rvm/gems/ruby-2.2.1/gems/github-linguist-4.8.9/lib/linguist/classifier.rb:104:in `each'
from /home/marvelez/.rvm/gems/ruby-2.2.1/gems/github-linguist-4.8.9/lib/linguist/classifier.rb:104:in `classify'
from /home/marvelez/.rvm/gems/ruby-2.2.1/gems/github-linguist-4.8.9/lib/linguist/classifier.rb:78:in `classify'
from /home/marvelez/.rvm/gems/ruby-2.2.1/gems/github-linguist-4.8.9/lib/linguist/classifier.rb:20:in `call'
from ./test_linguist.rb:21:in `block in <main>'
from ./test_linguist.rb:14:in `each'
from ./test_linguist.rb:14:in `<main>'

附加:

  1. 这是使用 Github Linguist 的最佳方式吗? FileBlob 是 Blob 的替代品,但这需要将我的字符串写入文件。这是有问题的,原因有两个:1) 它很慢,2) 选择的文件扩展名会指导语言学家,我们不知道正确的文件扩展名。
  2. 是否有更好的工具可以做到这一点? Github 语言学家可能在文件上工作得很好,但在字符串上却不行。

看了一下Linguist的源码,好像是用了很多策略来判断语言,它依次调用每一个策略。 Classifier 是最后一个被调用的策略,到那时它(希望)从先前的策略中选择语言 "candidates"(正如您自己发现的那样)。因此,我认为对于您与我们共享的特定示例,您必须传递某种文件名,即使文件实际上并不存在,也必须传递候选语言列表。如果两者都不适合您,那么这可能不是您问题的可行解决方案。

$ ruby -r linguist -e 'p Linguist::Blob.new("foo.c", "int main(){}").language'
#<Linguist::Language name=C>

它 returns nil 没有文件名,#<Linguist::Language name=C++> 有 "foo.cc" 和相同的代码示例。

好消息是您选择了一个非常糟糕的样本进行测试。 :-) 其他策略着眼于 modelines 和 shebangs,因此更复杂的样本更有可能成功。看看这些:

$ ruby -r linguist -e 'p Linguist::Blob.new("", "#!/usr/bin/env perl
print q{Hello, world!};
").language'
#<Linguist::Language name=Perl>
$ ruby -r linguist -e 'p Linguist::Blob.new("", "# vim: ft=ruby
puts %q{Hello, world!}
").language'
#<Linguist::Language name=Ruby>

但是,如果没有 shebang 或模式行,我们仍然不走运。事实证明,有一个训练数据集在安装时计算并序列化到磁盘,并在语言检测期间自动加载。不幸的是,我认为库中存在一个错误,如果在到达此步骤时没有任何候选人,则该错误会阻止使用此训练数据集。修复错误让我可以这样做:

$ ruby -Ilib -r linguist -e 'p Linguist::Blob.new("", "int main(){}").language'
#<Linguist::Language name=XC>

(我不知道 XC 是什么,但是在字符串中添加一些其他标记,例如 #include <stdio.h>int argc, char* argv[] 会给出 C。我相信您的大部分样本都会有更多肉来分析。)

这是一个非常简单的修复,我已经提交了 PR。如果您同时愿意,可以使用我的 Gem 分支。否则,我们需要考虑直接使用 Linguist::Classify,正如您已经开始探索的那样,但这有可能变得混乱。

要使用我的叉子,add/modify 你的 Gem 文件要这样阅读:

gem 'github-linguist',
  require: 'linguist',
  git: 'https://github.com/mwpastore/linguist.git',
  branch: 'fix-no-candidates'

当 PR 被合并并且 Gem 的新版本与修复一起发布时,我会尝试回来更新这个答案。如果我必须执行任何强制推送以满足存储库指南 and/or 让维护者满意,您可能必须执行 bundler update 来反映更改。如果您有任何问题,请告诉我。

再快速浏览一下 Linguist 来源,Linguist::Language.all 似乎就是您要查找的内容。

编辑:我自己尝试了 Linguist::Language.all。失败是由于另一个错误:某些语言似乎有错误的数据。例如,这也失败了:

candidates = [Linguist::Language['ADA']]

这显然是因为 lib/linguist/samples.json 中不存在 tokens.ADA。它不是唯一这样的语言。

为避免错误,您可以过滤语言:

non_buggy_languages = Linguist::Samples.cache['tokens'].keys
candidates = non_buggy_languages.map { |l| Linguist::Language[l] }