mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-10-29 09:40:21 +00:00
* Remove trailing spaces * Setup Bundler in some scripts * Update grammar index * Make prune-grammars script to be callable in a script directory * Prune unused xquery grammar repo source.xq by language-jsoniq is actual tm_scope for XQuery. * Remove xquery submodule git submodule deinit vendor/grammars/xquery/ git rm vendor/grammars/xquery/ * Fix invocation of script/list-grammars This fixes #3339. * Make add-grammars script to be callable in a script directory * Generate samples.json before running list-grammars list-grammars requires linguist.
319 lines
7.1 KiB
Ruby
Executable File
319 lines
7.1 KiB
Ruby
Executable File
#!/usr/bin/env ruby
|
|
|
|
require 'bundler/setup'
|
|
require 'json'
|
|
require 'net/http'
|
|
require 'optparse'
|
|
require 'plist'
|
|
require 'set'
|
|
require 'thread'
|
|
require 'tmpdir'
|
|
require 'uri'
|
|
require 'yaml'
|
|
|
|
ROOT = File.expand_path("../..", __FILE__)
|
|
GRAMMARS_PATH = File.join(ROOT, "grammars")
|
|
SOURCES_FILE = File.join(ROOT, "grammars.yml")
|
|
CSONC = File.join(ROOT, "node_modules", ".bin", "csonc")
|
|
|
|
$options = {
|
|
:add => false,
|
|
:install => true,
|
|
:output => SOURCES_FILE,
|
|
:remote => true,
|
|
}
|
|
|
|
class SingleFile
|
|
def initialize(path)
|
|
@path = path
|
|
end
|
|
|
|
def url
|
|
@path
|
|
end
|
|
|
|
def fetch(tmp_dir)
|
|
[@path]
|
|
end
|
|
end
|
|
|
|
class DirectoryPackage
|
|
def self.fetch(dir)
|
|
Dir["#{dir}/**/*"].select do |path|
|
|
case File.extname(path.downcase)
|
|
when '.plist'
|
|
path.split('/')[-2] == 'Syntaxes'
|
|
when '.tmlanguage', '.yaml-tmlanguage'
|
|
true
|
|
when '.cson', '.json'
|
|
path.split('/')[-2] == 'grammars'
|
|
else
|
|
false
|
|
end
|
|
end
|
|
end
|
|
|
|
def initialize(directory)
|
|
@directory = directory
|
|
end
|
|
|
|
def url
|
|
@directory
|
|
end
|
|
|
|
def fetch(tmp_dir)
|
|
self.class.fetch(File.join(ROOT, @directory))
|
|
end
|
|
end
|
|
|
|
class TarballPackage
|
|
def self.fetch(tmp_dir, url)
|
|
`curl --silent --location --max-time 30 --output "#{tmp_dir}/archive" "#{url}"`
|
|
raise "Failed to fetch GH package: #{url} #{$?.to_s}" unless $?.success?
|
|
|
|
output = File.join(tmp_dir, 'extracted')
|
|
Dir.mkdir(output)
|
|
`tar -C "#{output}" -xf "#{tmp_dir}/archive"`
|
|
raise "Failed to uncompress tarball: #{tmp_dir}/archive (from #{url}) #{$?.to_s}" unless $?.success?
|
|
|
|
DirectoryPackage.fetch(output)
|
|
end
|
|
|
|
attr_reader :url
|
|
|
|
def initialize(url)
|
|
@url = url
|
|
end
|
|
|
|
def fetch(tmp_dir)
|
|
self.class.fetch(tmp_dir, url)
|
|
end
|
|
end
|
|
|
|
class SingleGrammar
|
|
attr_reader :url
|
|
|
|
def initialize(url)
|
|
@url = url
|
|
end
|
|
|
|
def fetch(tmp_dir)
|
|
filename = File.join(tmp_dir, File.basename(url))
|
|
`curl --silent --location --max-time 10 --output "#{filename}" "#{url}"`
|
|
raise "Failed to fetch grammar: #{url}: #{$?.to_s}" unless $?.success?
|
|
[filename]
|
|
end
|
|
end
|
|
|
|
class SVNPackage
|
|
attr_reader :url
|
|
|
|
def initialize(url)
|
|
@url = url
|
|
end
|
|
|
|
def fetch(tmp_dir)
|
|
`svn export -q "#{url}/Syntaxes" "#{tmp_dir}/Syntaxes"`
|
|
raise "Failed to export SVN repository: #{url}: #{$?.to_s}" unless $?.success?
|
|
Dir["#{tmp_dir}/Syntaxes/*.{plist,tmLanguage,tmlanguage,YAML-tmLanguage}"]
|
|
end
|
|
end
|
|
|
|
class GitHubPackage
|
|
def self.parse_url(url)
|
|
url, ref = url.split("@", 2)
|
|
path = URI.parse(url).path.split('/')
|
|
[path[1], path[2].chomp('.git'), ref || "master"]
|
|
end
|
|
|
|
attr_reader :user
|
|
attr_reader :repo
|
|
attr_reader :ref
|
|
|
|
def initialize(url)
|
|
@user, @repo, @ref = self.class.parse_url(url)
|
|
end
|
|
|
|
def url
|
|
suffix = "@#{ref}" unless ref == "master"
|
|
"https://github.com/#{user}/#{repo}#{suffix}"
|
|
end
|
|
|
|
def fetch(tmp_dir)
|
|
url = "https://github.com/#{user}/#{repo}/archive/#{ref}.tar.gz"
|
|
TarballPackage.fetch(tmp_dir, url)
|
|
end
|
|
end
|
|
|
|
def load_grammar(path)
|
|
case File.extname(path.downcase)
|
|
when '.plist', '.tmlanguage'
|
|
Plist::parse_xml(path)
|
|
when '.yaml-tmlanguage'
|
|
content = File.read(path)
|
|
# Attempt to parse YAML file even if it has a YAML 1.2 header
|
|
if content.lines[0] =~ /^%YAML[ :]1\.2/
|
|
content = content.lines[1..-1].join
|
|
end
|
|
begin
|
|
YAML.load(content)
|
|
rescue Psych::SyntaxError => e
|
|
$stderr.puts "Failed to parse YAML grammar '#{path}'"
|
|
end
|
|
when '.cson'
|
|
cson = `"#{CSONC}" "#{path}"`
|
|
raise "Failed to convert CSON grammar '#{path}': #{$?.to_s}" unless $?.success?
|
|
JSON.parse(cson)
|
|
when '.json'
|
|
JSON.parse(File.read(path))
|
|
else
|
|
raise "Invalid document type #{path}"
|
|
end
|
|
end
|
|
|
|
def load_grammars(tmp_dir, source, all_scopes)
|
|
is_url = source.start_with?("http:", "https:")
|
|
return [] if is_url && !$options[:remote]
|
|
|
|
p = if !is_url
|
|
if File.directory?(source)
|
|
DirectoryPackage.new(source)
|
|
else
|
|
SingleFile.new(source)
|
|
end
|
|
elsif source.end_with?('.tmLanguage', '.plist', '.YAML-tmLanguage')
|
|
SingleGrammar.new(source)
|
|
elsif source.start_with?('https://github.com')
|
|
GitHubPackage.new(source)
|
|
elsif source.start_with?('http://svn.textmate.org')
|
|
SVNPackage.new(source)
|
|
elsif source.end_with?('.tar.gz')
|
|
TarballPackage.new(source)
|
|
else
|
|
nil
|
|
end
|
|
|
|
raise "Unsupported source: #{source}" unless p
|
|
|
|
p.fetch(tmp_dir).map do |path|
|
|
grammar = load_grammar(path)
|
|
scope = grammar['scopeName'] || grammar['scope']
|
|
|
|
if all_scopes.key?(scope)
|
|
unless all_scopes[scope] == p.url
|
|
$stderr.puts "WARN: Duplicated scope #{scope}\n" +
|
|
" Current package: #{p.url}\n" +
|
|
" Previous package: #{all_scopes[scope]}"
|
|
end
|
|
next
|
|
end
|
|
all_scopes[scope] = p.url
|
|
grammar
|
|
end.compact
|
|
end
|
|
|
|
def install_grammars(grammars, path)
|
|
installed = []
|
|
|
|
grammars.each do |grammar|
|
|
scope = grammar['scopeName'] || grammar['scope']
|
|
File.write(File.join(GRAMMARS_PATH, "#{scope}.json"), JSON.pretty_generate(grammar))
|
|
installed << scope
|
|
end
|
|
|
|
$stderr.puts("OK #{path} (#{installed.join(', ')})")
|
|
end
|
|
|
|
def run_thread(queue, all_scopes)
|
|
Dir.mktmpdir do |tmpdir|
|
|
loop do
|
|
source, index = begin
|
|
queue.pop(true)
|
|
rescue ThreadError
|
|
# The queue is empty.
|
|
break
|
|
end
|
|
|
|
dir = "#{tmpdir}/#{index}"
|
|
Dir.mkdir(dir)
|
|
|
|
grammars = load_grammars(dir, source, all_scopes)
|
|
install_grammars(grammars, source) if $options[:install]
|
|
end
|
|
end
|
|
end
|
|
|
|
def generate_yaml(all_scopes, base)
|
|
yaml = all_scopes.each_with_object(base) do |(key,value),out|
|
|
out[value] ||= []
|
|
out[value] << key
|
|
end
|
|
|
|
yaml = Hash[yaml.sort]
|
|
yaml.each { |k, v| v.sort! }
|
|
yaml
|
|
end
|
|
|
|
def main(sources)
|
|
begin
|
|
Dir.mkdir(GRAMMARS_PATH)
|
|
rescue Errno::EEXIST
|
|
end
|
|
|
|
`npm install`
|
|
|
|
all_scopes = {}
|
|
|
|
if source = $options[:add]
|
|
Dir.mktmpdir do |tmpdir|
|
|
grammars = load_grammars(tmpdir, source, all_scopes)
|
|
install_grammars(grammars, source) if $options[:install]
|
|
end
|
|
generate_yaml(all_scopes, sources)
|
|
else
|
|
queue = Queue.new
|
|
|
|
sources.each do |url, scopes|
|
|
queue.push([url, queue.length])
|
|
end
|
|
|
|
threads = 8.times.map do
|
|
Thread.new { run_thread(queue, all_scopes) }
|
|
end
|
|
threads.each(&:join)
|
|
generate_yaml(all_scopes, {})
|
|
end
|
|
end
|
|
|
|
OptionParser.new do |opts|
|
|
opts.banner = "Usage: #{$0} [options]"
|
|
|
|
opts.on("--add GRAMMAR", "Add a new grammar. GRAMMAR may be a file path or URL.") do |a|
|
|
$options[:add] = a
|
|
end
|
|
|
|
opts.on("--[no-]install", "Install grammars into grammars/ directory.") do |i|
|
|
$options[:install] = i
|
|
end
|
|
|
|
opts.on("--output FILE", "Write output to FILE. Use - for stdout.") do |o|
|
|
$options[:output] = o == "-" ? $stdout : o
|
|
end
|
|
|
|
opts.on("--[no-]remote", "Download remote grammars.") do |r|
|
|
$options[:remote] = r
|
|
end
|
|
end.parse!
|
|
|
|
sources = File.open(SOURCES_FILE) do |file|
|
|
YAML.load(file)
|
|
end
|
|
|
|
yaml = main(sources)
|
|
|
|
if $options[:output].is_a?(IO)
|
|
$options[:output].write(YAML.dump(yaml))
|
|
else
|
|
File.write($options[:output], YAML.dump(yaml))
|
|
end
|