188 lines
4.9 KiB
Crystal
188 lines
4.9 KiB
Crystal
require "http/client"
|
|
require "uri"
|
|
require "json"
|
|
|
|
# rosettacode api url #
|
|
URL = "https://rosettacode.org/w/api.php"
|
|
|
|
# categories under these categories are programming languages #
|
|
LANG_CATS = ["Programming Languages"]
|
|
.map {|name| "Category:" + name }
|
|
|
|
# pages in these categories are tasks #
|
|
TASKS_CATS = ["Programming Tasks", "Draft Programming Tasks"]
|
|
.map {|name| "Category:" + name }
|
|
|
|
def get_list (list, prefix, params)
|
|
q = { "action" => "query", "format" => "json", "list" => list,
|
|
prefix + "limit" => "500" }
|
|
.merge(params)
|
|
uri = URI.parse URL
|
|
result = [] of JSON::Any
|
|
loop do
|
|
uri.query = URI::Params.encode q
|
|
response = HTTP::Client.get uri
|
|
json = JSON.parse(response.body)
|
|
result.concat json["query"][list].as_a
|
|
cont = json["continue"]? && json["continue"][prefix + "continue"].to_s
|
|
break unless cont
|
|
q[prefix + "continue"] = cont
|
|
end
|
|
result
|
|
end
|
|
|
|
def get_cat_members (cats, page_type)
|
|
cats.flat_map {|cat|
|
|
get_list("categorymembers", "cm",
|
|
{"cmtitle" => cat, "cmprop" => "ids|title", "cmtype" => page_type})
|
|
}.map {|jo| { jo["pageid"].to_s.to_i, jo["title"].to_s } }
|
|
end
|
|
|
|
module Enumerable(T)
|
|
def rank_by (&)
|
|
result = [] of {Int32, T}
|
|
if present?
|
|
rank = 1
|
|
previous = yield first
|
|
result << {rank, first}
|
|
skip(1).each_with_index do |item, idx|
|
|
key = yield item
|
|
rank = idx+2 unless previous == key
|
|
result << {rank, item}
|
|
previous = key
|
|
end
|
|
end
|
|
result
|
|
end
|
|
end
|
|
|
|
# command line options
|
|
load = save = ""
|
|
verbose = false
|
|
need_help = false
|
|
report = "pop"
|
|
language = ""
|
|
|
|
while ARGV.present?
|
|
case opt = ARGV.shift
|
|
when "-l" then load = ARGV.shift
|
|
when "-s" then save = ARGV.shift
|
|
when "-r" then report = ARGV.shift
|
|
when "-v" then verbose = true
|
|
when "-h" then need_help = true
|
|
else
|
|
if report == "ut" && language == ""
|
|
language = opt
|
|
else
|
|
STDERR.puts "Invalid option #{opt}. Use -h for a list of options."
|
|
exit
|
|
end
|
|
end
|
|
end
|
|
if load != "" && save != ""
|
|
STDERR.puts "Only one of -s or -l is allowed."
|
|
exit
|
|
end
|
|
unless report.in? ["pop", "ex", "ut"]
|
|
STDERR.puts "Invalid report: #{report}"
|
|
exit
|
|
end
|
|
if need_help
|
|
STDERR.puts <<-EOH
|
|
Usage: #{PROGRAM_NAME} [options]
|
|
-h show this help
|
|
-v verbose
|
|
-s FILE save downloaded data in FILE
|
|
-l FILE don't download anything, use data in FILE
|
|
-r REPORT report to output:
|
|
pop: Rank languages by popularity (default)
|
|
ex: Count examples
|
|
ut LANG: Find unimplemented tasks in LANG
|
|
EOH
|
|
exit
|
|
end
|
|
|
|
macro log (s)
|
|
if verbose
|
|
STDERR.print "\r\e[K" + {{s}}
|
|
STDERR.flush
|
|
end
|
|
end
|
|
|
|
if load == ""
|
|
log "Fetching programming languages..."
|
|
all_langs = get_cat_members(LANG_CATS, "subcat")
|
|
log "Fetched #{all_langs.size} programming languages.\n"
|
|
|
|
log "Fetching programming tasks..."
|
|
all_tasks = get_cat_members(TASKS_CATS, "page").to_h
|
|
log "Fetched #{all_tasks.size} programming tasks.\n"
|
|
|
|
fetch_for = "all languages"
|
|
# when reporting unimplemented tasks, if we're not saving, don't fetch all languages' tasks
|
|
if save == "" && language != ""
|
|
all_langs.reject! {|_, lang| lang.lchop("Category:").upcase != language.upcase }
|
|
fetch_for = language
|
|
end
|
|
|
|
i = 0
|
|
tasks_by_lang = all_langs.map {|pageid, cat|
|
|
cat_name = cat.lchop("Category:")
|
|
log "[#{i += 1}/#{all_langs.size}] Fetching tasks for #{cat_name}..."
|
|
|
|
lang_tasks = get_cat_members([cat], "page").select {|task_id, _| all_tasks[task_id]? }
|
|
.map {|_, name| name }
|
|
|
|
{ cat_name, lang_tasks }
|
|
}
|
|
tasks_by_lang << { "", all_tasks.values }
|
|
log "Fetched tasks for #{fetch_for}.\n"
|
|
|
|
if save != ""
|
|
File.open(save, "w") do |f|
|
|
tasks_by_lang.to_json(f)
|
|
log "Data saved to #{save}.\n"
|
|
end
|
|
end
|
|
else
|
|
log "Loading data from #{load}..."
|
|
tasks_by_lang = File.open(load) do |f|
|
|
Array({String, Array(String)}).from_json(f)
|
|
end
|
|
log "Loaded #{tasks_by_lang.size} entries from #{load}.\n"
|
|
end
|
|
|
|
if report == "pop"
|
|
tasks_by_lang.reject {|lang, tasks| lang == "" || tasks.size == 0 }
|
|
.sort_by {|lang, tasks| {-tasks.size, lang} }
|
|
.rank_by {|lang, tasks| tasks.size }
|
|
.each do |rank, (lang, tasks)|
|
|
printf("%3d. %4d %s\n", rank, tasks.size, lang)
|
|
end
|
|
elsif report == "ex"
|
|
tally = Hash(String, Int32).new(0)
|
|
tasks_by_lang.each do |lang, tasks|
|
|
tasks.each do |task|
|
|
tally[task] += 1
|
|
end
|
|
end
|
|
tally.delete ""
|
|
tally.to_a.sort_by {|task, n| {-n, task} }
|
|
.rank_by {|task, n| n }
|
|
.each do |rank, (task, n)|
|
|
printf("%4d. %3d %s\n", rank, n, task)
|
|
end
|
|
elsif report == "ut"
|
|
ulang = language.upcase
|
|
langtasks = tasks_by_lang.find { |(lang, _)| lang.upcase == ulang }
|
|
if langtasks
|
|
all_tasks = tasks_by_lang.find! { |(lang, _)| lang == "" }.last.to_set
|
|
_, tasks = langtasks
|
|
(all_tasks - tasks).to_a.sort!.each do |task|
|
|
puts task
|
|
end
|
|
else
|
|
STDERR.puts "Language #{language} not found."
|
|
exit 1
|
|
end
|
|
end
|