#!/usr/bin/env ruby require 'yaml' require 'json' def comma_expanson(s) s.scan(/{[^{]+}|[^{]+/).map { |a| a[0] == "{" ? a : a.split(/(? 0 ? (b.is_a?(String) ? a[0..-2] + [a[-1] + b] : a[0..-2] + [a[-1] + b[0]] + b[1..-1]) : [b].flatten end.map { |e| e.gsub('\\,', ',')} end def import_autocommands cmd = <<~EOF vim -es -u DEFAULTS -c 'exe("func! Capture() \\n redir => capture \\n silent autocmd filetypedetect BufRead \\n redir END \\n return capture \\n endfunc") | let a = Capture() | put =a | %print | :q!' EOF filetypes = Hash.new { |h, k| h[k] = [] } autocommands = `#{cmd}` for pattern, cmd in autocommands.scan(/^ ([^ ]+)\s*(.*)\n/) cmd = cmd.strip what = {} if match = cmd.match(/^setf\s+(.*)$/) what[:ft] = match[1] elsif match = cmd.match(/^call s:StarSetf\(\'(.*)\'\)$/) what[:ft] = match[1] elsif match = cmd.match(/^set filetype=(.*)$/) what[:ft] = match[1] end if what != {} for p in comma_expanson(pattern.strip) filetypes[what[:ft].strip] << p end end end for k, v in filetypes filetypes[k].uniq! end filetypes end $with_special_char = /#{[".", ":", "{", "}", "[", "]", ",", "&", "*", "#", "?", "|", "-", "<", ">", "=", "!", "%", "@", "\\"].map { |e| Regexp.escape(e) }.join("|")}/ def print_pattern(p) if p.match($with_special_char) p.gsub("'", "\\'") end return p end def generate_packages_entries(filetypes, comments) entries = [] current_filetypes = YAML.load_stream(File.read('packages.yaml')).flat_map do |p| p["filetypes"].map { |a| a["name"] } end for ft, patterns in filetypes if current_filetypes.include?(ft) next end output = { "name" => ft, "remote" => "vim/vim:runtime", "glob" => "**/" + ft + ".vim", "filetypes" => [{ "name" => ft, "patterns" => [] }] } paths_with_comments = patterns.group_by { |p| (comments[ft] || {})[p] || "" } for comment, paths in paths_with_comments output["filetypes"][0]["patterns"] << { "pattern" => paths.join(","), } if comment.strip.size > 0 output["filetypes"][0]["patterns"].last["description"] = comment end end entries << YAML.dump(output) end return entries end def fix_quotes(a) a = a.gsub(/\\/) { '\\\\' } a.scan(/^.*?"(.+?)"\n/m).each { |p| a[p[0]] = p[0].gsub('"') { '\\"'} } a end def get_comments comments_cmd = <<~'EOF' awk '/^"/ { comment = $0; next } match($0, "(\\S+)\\s+setf (\\S*)$", a) { print "- filetype: \"" a[2] "\"\n description: \"" substr(comment, 3) "\"\n patterns: \"" a[1] "\"" }' 'tmp/vim/vim/runtime/filetype.vim' EOF comments = YAML.load(fix_quotes(`#{comments_cmd}`)) comments.reject! { |c| c["patterns"].strip == "" || c["patterns"].include?('\\') } result = {} comments = comments.flat_map do |c| result[c["filetype"]] ||= {} for p in comma_expanson(c["patterns"]) result[c["filetype"]][p] = c["description"] end end result end def square_expansion(s) return [s] unless s.include?('[') s.scan(/(\[[^\]]+\]|[^\[]+)/).map { |x| x[0] } .map { |x| x[0] == "[" ? x[1..-2].split("") : [x] } .reduce(&:product).map(&:flatten).map(&:join) end def brace_expansion(s) if !s.include?('{') return [s] end r=1 # Dummy value to forward-declare the parse function `r` t=->x{ # Function to parse a bracket block x=x[0].gsub(/^{(.*)}$/){$1} # Remove outer brackets if both are present # x[0] is required because of quirks in the `scan` function x=x.scan(/(({(\g<1>|,)*}|[^,{}]|(?<=,|^)(?=,|$))+)/) # Regex black magic: collect elements of outer bracket x.map{|i|i=i[0];i[?{]?r[i]:i}.flatten # For each element with brackets, run parse function } r=->x{ # Function to parse bracket expansions a{b,c}{d,e} i=x.scan(/({(\g<1>)*}|[^{} ]+)/) # Regex black magic: scan for adjacent sets of brackets i=i.map(&t) # Map all elements against the bracket parser function `t` i.shift.product(*i).map &:join # Combine the adjacent sets with cartesian product and join them together } s.split.map(&r).flatten end def generate_tests(autocommands) existing = JSON.parse(File.read('tmp/vim/vim/src/testdir/test_filetype.vim') .match(/let s:filename_checks = ({.*?\\ })/m)[1] .gsub(' \\', ' ').gsub("'", '"') .gsub('$VIMRUNTIME .', '').gsub(",\n }", "}")) output = [] generated = {} for ft in autocommands.keys().sort() patterns = autocommands[ft] files1 = [] to_delete = [] patterns.reject! { |p| p.match?(/[\|\\\(]/) } patterns = patterns.map do |pattern| pattern = pattern.gsub('[0-9]', '1').gsub('[2-3]', '2').gsub('[1-3]', '1').gsub('?', 'x') if pattern.match?(/\/\*\.[^\*\/]+$/) pattern = pattern.gsub(/\/\*\.([^\*\/]+)$/, '/file.\1') end if pattern.match?(/[\/\.-]\*$/) pattern = pattern[0..-2] + "file" end pattern = pattern.gsub(/\/\*\//) { '/any/' } pattern end patterns = patterns.flat_map { |p| brace_expansion(p) } for pattern in patterns if pattern.match(/^\*[.\,][^\*\/]+$/) files1 << pattern.gsub('*', 'file') to_delete << pattern end if pattern.match(/^[^\*\/]+\.\*$/) files1 << pattern.gsub('*', 'file') to_delete << pattern end end files1.sort! patterns = patterns - to_delete files2 = [] for pattern in patterns if !pattern.match(/\*/) files2 << pattern to_delete << pattern end end files2.sort! patterns = patterns - to_delete patterns = patterns.flat_map do |pattern| if pattern.match?(/^\*\//) [pattern[1..-1], "any" + pattern[1..-1]] else [pattern] end end patterns = patterns.flat_map do |pattern| if pattern.match?(/^\*[-\.]/) ["some" + pattern[1..-1]] elsif pattern.match?(/^\*/) [pattern[1..-1], "some-" + pattern[1..-1]] else [pattern] end end patterns = patterns.flat_map do |pattern| if pattern.match?(/[^\/]+\/\*-[^\\]+$/) [pattern.gsub('/*-', '/file-')] elsif pattern.match?(/[^\/]+\/\*\.[^\\]+$/) [pattern.gsub('/*.', '/file.')] elsif pattern.match?(/[^\/]+\/\*[^\\]+$/) [pattern.gsub('/*', '/'), pattern.gsub('/*', '/some-')] else [pattern] end end patterns = patterns.flat_map do |pattern| if pattern.include?('-*/') [pattern.gsub('-*/', '-file/')] elsif pattern.include?('.*/') [pattern.gsub('.*/', '.file/')] elsif pattern.include?('*/') [pattern.gsub('*/', '/'), pattern.gsub('*/', '-some/')] else [pattern] end end patterns = patterns.flat_map do |pattern| if pattern.match?(/[^\/]+\.\*\.([^\*\/]+)$/) [pattern.gsub('.*.', '.file.')] elsif pattern.match?(/[^\/]+-\*\.([^\*\/]+)$/) [pattern.gsub('-*.', '-file.')] elsif pattern.match?(/[^\/]+\*\.([^\*\/]+)$/) [pattern.gsub('*.', '.'), pattern.gsub('*.', '-file.')] else [pattern] end end patterns = patterns.flat_map do |pattern| if pattern.match?(/\*$/) [pattern[0..-2], pattern[0..-2] + "-file"] else [pattern] end end patterns.sort! files = [*files1, *files2, *patterns].flat_map { |e| square_expansion(e) } generated[ft] = files end for ft, original in existing for file in generated.fetch(ft, []) unless original.include?(file) || ['file.DEF', 'file.MOD', 'file.BUILD', 'BUILD'].include?(file) || ft == "help" original << file end end generated.delete(ft) output << " \\ '#{ft}': #{JSON.generate(original).gsub('"', "'").gsub("','", "', '")}," end for ft, paths in generated idx = output.find_index { |a| a > " \\ '" + ft } output.insert(idx, " \\ '#{ft}': #{JSON.generate(paths).gsub('"', "'").gsub("','", "', '")},") end output << " \\ }" msgs = <<'EOF' \ 'messages': ['/log/auth', '/log/cron', '/log/daemon', '/log/debug', '/log/kern', '/log/lpr', '/log/mail', '/log/messages', '/log/news/news', '/log/syslog', '/log/user', \ '/log/auth.log', '/log/cron.log', '/log/daemon.log', '/log/debug.log', '/log/kern.log', '/log/lpr.log', '/log/mail.log', '/log/messages.log', '/log/news/news.log', '/log/syslog.log', '/log/user.log', \ '/log/auth.err', '/log/cron.err', '/log/daemon.err', '/log/debug.err', '/log/kern.err', '/log/lpr.err', '/log/mail.err', '/log/messages.err', '/log/news/news.err', '/log/syslog.err', '/log/user.err', \ '/log/auth.info', '/log/cron.info', '/log/daemon.info', '/log/debug.info', '/log/kern.info', '/log/lpr.info', '/log/mail.info', '/log/messages.info', '/log/news/news.info', '/log/syslog.info', '/log/user.info', \ '/log/auth.warn', '/log/cron.warn', '/log/daemon.warn', '/log/debug.warn', '/log/kern.warn', '/log/lpr.warn', '/log/mail.warn', '/log/messages.warn', '/log/news/news.warn', '/log/syslog.warn', '/log/user.warn', \ '/log/auth.crit', '/log/cron.crit', '/log/daemon.crit', '/log/debug.crit', '/log/kern.crit', '/log/lpr.crit', '/log/mail.crit', '/log/messages.crit', '/log/news/news.crit', '/log/syslog.crit', '/log/user.crit', \ '/log/auth.notice', '/log/cron.notice', '/log/daemon.notice', '/log/debug.notice', '/log/kern.notice', '/log/lpr.notice', '/log/mail.notice', '/log/messages.notice', '/log/news/news.notice', '/log/syslog.notice', '/log/user.notice'], EOF "let s:filename_checks = {\n" + output.join("\n").gsub("'/doc/help.txt'", "$VIMRUNTIME . '/doc/help.txt'").gsub(/ \\ 'messages':.*?\],\n/m) { msgs } end #comments = get_comments() autocommands = import_autocommands() # entries = generate_packages_entries(autocommands, comments) # print(entries.join("")) result = generate_tests(autocommands) print(result)