Rakefile
# frozen_string_literal: true
require "bundler/gem_tasks"
require_relative "lib/discorb/utils/colored_puts"
task default: %i[]
# @private
def current_version
require_relative "lib/discorb/common"
tag = `git tag --points-at HEAD`.force_encoding("utf-8").strip
tag.empty? ? "main" : Discorb::VERSION
end
desc "Run spec with parallel_rspec"
task :spec do
sh "parallel_rspec spec/*.spec.rb spec/**/*.spec.rb"
end
desc "Build emoji_table.rb"
task :emoji_table do
require_relative "lib/discorb"
iputs "Building emoji_table.rb"
res = {}
Discorb::EmojiTable::DISCORD_TO_UNICODE.each do |discord, unicode|
res[unicode] ||= []
res[unicode] << discord
end
res_text = +""
res.each do |unicode, discord|
res_text << %(#{unicode.unpack("C*").pack("C*").inspect} => %w[#{discord.join(" ")}],\n)
end
table_script = File.read("lib/discorb/emoji_table.rb")
table_script.gsub!(
/(?<=UNICODE_TO_DISCORD = {\n)[\s\S]+(?=}\.freeze)/,
res_text
)
File.open("lib/discorb/emoji_table.rb", "w") { |f| f.print(table_script) }
`rufo lib/discorb/emoji_table.rb`
sputs "Successfully made emoji_table.rb"
end
desc "Format files"
task :format do
Dir
.glob("**/*.rb")
.each do |file|
next if file.start_with?("vendor")
iputs "Formatting #{file}"
`rufo ./#{file}`
content = ""
File.open(file, "rb") { |f| content = f.read }
content.gsub!("\r\n", "\n")
File.open(file, "wb") { |f| f.print(content) }
end
end
desc "Generate document and replace"
namespace :document do
version = current_version
desc "Just generate document"
task :yard do
sh "yard -o doc/#{version} --locale #{ENV.fetch("rake_locale", nil) or "en"}"
end
desc "Replace files"
namespace :replace do
require "fileutils"
desc "Replace CSS"
task :css do
iputs "Replacing css"
Dir
.glob("template-replace/files/**/*.*")
.map { |f| f.delete_prefix("template-replace/files") }
.each do |file|
FileUtils.cp(
"template-replace/files#{file}",
"doc/#{version}/#{file}"
)
end
sputs "Successfully replaced css"
end
desc "Replace HTML"
task :html do
require_relative "template-replace/scripts/sidebar"
require_relative "template-replace/scripts/version"
require_relative "template-replace/scripts/index"
require_relative "template-replace/scripts/yard_replace"
require_relative "template-replace/scripts/favicon"
require_relative "template-replace/scripts/arrow"
iputs "Resetting changes"
Dir.glob("doc/#{version}/**/*.html") do |f|
if (m = f.match(/[0-9]+\.[0-9]+\.[0-9]+(-[a-z]+)?/)) && m[0] != version
next
end
content = File.read(f)
content.gsub!(/<!--od-->[\s\S]*<!--eod-->/, "")
File.write(f, content)
end
iputs "Adding version tab"
%w[file_list class_list method_list].each do |f|
replace_sidebar("doc/#{version}/#{f}.html")
end
iputs "Building version tab"
build_version_sidebar("doc/#{version}", version)
iputs "Replacing _index.html"
replace_index("doc/#{version}", version)
iputs "Replacing YARD credits"
yard_replace("doc/#{version}", version)
iputs "Adding favicon"
add_favicon("doc/#{version}")
iputs "Replacing arrow"
replace_arrow("doc/#{version}")
iputs "Successfully replaced htmls"
end
desc "Replace EOL"
task :eol do
iputs "Replacing CRLF with LF"
Dir.glob("doc/**/*.*") do |file|
next unless File.file?(file)
next unless %w[html css js].include? file.split(".").last
content = ""
File.open(file, "rb") { |f| content = f.read }
content.gsub!("\r\n", "\n")
File.open(file, "wb") { |f| f.print(content) }
end
sputs "Successfully replaced CRLF with LF"
end
desc "change locale of current document"
task :locale do
next if ENV["rake_locale"].nil?
require_relative "template-replace/scripts/locale_#{ENV.fetch("rake_locale", nil)}.rb"
replace_locale("doc/main")
end
end
task replace: %i[replace:css replace:html replace:eol]
desc "Build all versions"
task :build_all do
require "fileutils"
iputs "Building all versions"
begin
FileUtils.rm_rf("doc")
rescue StandardError
nil
end
FileUtils.cp_r("./template-replace/.", "./tmp-template-replace")
Rake::Task["document:yard"].execute
Rake::Task["document:replace:html"].execute
Rake::Task["document:replace:css"].execute
Rake::Task["document:replace:eol"].execute
Rake::Task["document:replace:locale"].execute
tags =
`git tag`.force_encoding("utf-8")
.split("\n")
.sort_by { |t| t[1..].split(".").map(&:to_i) }
tags.each do |tag|
sh "git checkout #{tag} -f"
iputs "Building #{tag}"
FileUtils.cp_r("./tmp-template-replace/.", "./template-replace")
version = tag.delete_prefix("v")
Rake::Task["document:yard"].execute
Rake::Task["document:replace:html"].execute
Rake::Task["document:replace:css"].execute
Rake::Task["document:replace:eol"].execute
Rake::Task["document:replace:locale"].execute
FileUtils.cp_r("./doc/.", "./tmp-doc")
FileUtils.rm_rf("doc")
end
sh "git switch main -f"
FileUtils.cp_r("./tmp-doc/.", "./doc")
FileUtils.cp_r("./doc/#{tags.last.delete_prefix("v")}/.", "./doc")
sputs "Successfully built all versions"
rescue StandardError => e
sh "git switch main -f"
raise e
end
desc "Push to discorb-lib/discorb-lib.github.io"
task :push do
iputs "Pushing documents"
Dir.chdir("doc") do
sh "git init"
sh "git remote add origin git@github.com:discorb-lib/discorb-lib.github.io"
sh "git add ."
sh "git commit -m \"Update: Update document\""
sh "git push -f"
end
sputs "Successfully pushed documents"
end
namespace :locale do
desc "Generate Japanese document"
task :ja do
require "crowdin-api"
require "zip"
crowdin =
Crowdin::Client.new do |config|
config.api_token = ENV.fetch("CROWDIN_PERSONAL_TOKEN", nil)
config.project_id = ENV["CROWDIN_PROJECT_ID"].to_i
end
build = crowdin.build_project_translation["data"]["id"]
crowdin.download_project_translations("./tmp.zip", build)
Zip::File.open("tmp.zip") do |zip|
zip.each { |entry| zip.extract(entry, entry.name) { true } }
end
ENV["rake_locale"] = "ja"
Rake::Task["document:yard"].execute
Rake::Task["document:replace"].execute
end
desc "Generate English document"
task :en do
Rake::Task["document"].execute("locale:en")
end
end
end
desc "Generate rbs file"
namespace :rbs do
desc "Generate event signature"
task :event do
require "syntax_tree/rbs"
client_rbs = File.read("sig/discorb/client.rbs")
extension_rbs = File.read("sig/discorb/extension.rbs")
event_document = File.read("./docs/events.md")
voice_event_document = File.read("./docs/voice_events.md")
event_reference = event_document.split("## Event reference")[1]
event_reference += voice_event_document.split("# Voice Events")[1]
event_reference.gsub!(/^### (.*)$/, "")
events = []
event_reference.split("#### `")[1..].each do |event|
header, content = event.split("`\n", 2)
name = header.split("(")[0]
description = content.split("| Parameter", 2)[0].strip
parameters =
if content.include?("| Parameter")
content.scan(/\| `(.*?)` +\| (.*?) +\| (.*?) +\|/)
else
[]
end
events << {
name:,
description:,
parameters:
parameters.map { |p| { name: p[0], type: p[1], description: p[2] } }
}
end
event_sig = +""
event_lock_sig = +""
extension_sig = +""
events.each do |event|
args = []
event[:parameters].each do |parameter|
args << {
name: parameter[:name],
type:
if parameter[:type].start_with?("?")
parameter[:type][1..]
else
parameter[:type]
end.tr("{}`", "")
.tr("<>", "[]")
.gsub(", ", " | ")
.then do |t|
if event[:name] == "event_receive"
case t
when "Hash"
next "Discorb::json"
end
end
t
end
}
end
sig = args.map { |a| "#{a[:type]} #{a[:name]}" }.join(", ")
tuple_sig = args.map { |a| a[:type] }.join(", ")
tuple_sig = "[#{tuple_sig}]" if args.length > 1
tuple_sig = "void" if args.empty?
event_sig << <<~RBS
| (:#{event[:name]} event_name, ?id: Symbol?, **untyped metadata) { (#{sig}) -> void } -> Discorb::EventHandler
RBS
event_lock_sig << <<~RBS
| (:#{event[:name]} event, ?Integer? timeout) { (#{sig}) -> boolish } -> Async::Task[#{tuple_sig}]
RBS
extension_sig << <<~RBS
| (:#{event[:name]} event_name, ?id: Symbol?, **untyped metadata) { (#{sig}) -> void } -> void
RBS
end
event_sig << <<~RBS
| (Symbol event_name, ?id: Symbol?, **untyped metadata) { (*untyped) -> void } -> Discorb::EventHandler
RBS
event_lock_sig << <<~RBS
| (Symbol event, ?Integer? timeout) { (*untyped) -> boolish } -> Async::Task[untyped]
RBS
extension_sig << <<~RBS
| (Symbol event_name, ?id: Symbol?, **untyped metadata) { (*untyped) -> void } -> void
RBS
event_sig.sub!("| ", " ").rstrip!
event_lock_sig.sub!("| ", " ").rstrip!
extension_sig.sub!("| ", " ").rstrip!
res = client_rbs.gsub!(/(?<=def on:\n)(?:[\s\S]*?)(?=\n\n)/, event_sig)
raise "Failed to generate Client#on" unless res
res = client_rbs.gsub!(/(?<=def once:\n)(?:[\s\S]*?)(?=\n\n)/, event_sig)
raise "Failed to generate Client#once" unless res
res =
client_rbs.gsub!(
/(?<=def event_lock:\n)(?:[\s\S]*?)(?=\n\n)/,
event_lock_sig
)
raise "Failed to generate Client#event_lock" unless res
res =
extension_rbs.gsub!(
/(?<=def event:\n)(?:[\s\S]*?)(?=\n\n)/,
extension_sig
)
raise "Failed to generate Extension.event" unless res
res =
extension_rbs.gsub!(
/(?<=def once_event:\n)(?:[\s\S]*?)(?=\n\n)/,
extension_sig
)
raise "Failed to generate Extension.once_event" unless res
File.write(
"sig/discorb/client.rbs",
SyntaxTree::RBS.format(client_rbs),
mode: "wb"
)
File.write(
"sig/discorb/extension.rbs",
SyntaxTree::RBS.format(extension_rbs),
mode: "wb"
)
end
desc "Generate rbs file using sord"
task :sord do
require "open3"
type_errors = {
"SORD_ERROR_SymbolSymbolSymbolInteger" =>
"{ r: Integer, g: Integer, b: Integer}",
"SORD_ERROR_DiscorbRoleDiscorbMemberDiscorbPermissionOverwrite" =>
"Hash[Discorb::Role | Discorb::Member, Discorb::PermissionOverwrite]",
"SORD_ERROR_DiscorbRoleDiscorbMemberPermissionOverwrite" =>
"Hash[Discorb::Role | Discorb::Member, Discorb::PermissionOverwrite]",
"SORD_ERROR_f | SORD_ERROR_F | SORD_ERROR_d | SORD_ERROR_D | SORD_ERROR_t | SORD_ERROR_T | SORD_ERROR_R" =>
'"f" | "F" | "d" | "D" | "t" | "T" | "R"',
"SORD_ERROR_dark | SORD_ERROR_light" => '"dark" | "light"',
"SORD_ERROR_SymbolStringSymbolboolSymbolObject" =>
"String | Integer | Float"
}
regenerate = ARGV.include?("--regenerate") || ARGV.include?("-r")
sh(
"sord gen sig/discorb.rbs --keep-original-comments " \
"--no-sord-comments#{regenerate ? " --regenerate" : " --no-regenerate"}"
)
base = File.read("sig/discorb.rbs")
base.gsub!(/\n +def _set_data: \(.+\) -> untyped\n\n/, "\n")
# base.gsub!(/( )?( *)# @private.+?(?:\n\n(?=\1\2#)|(?=\n\2end))/sm, "")
base.gsub!(/untyped ([a-z_]*id)/, "_ToS \\1")
# #region rbs dictionary
base.gsub!(/ class Dictionary.+?end\n/ms, <<-RBS)
class Dictionary[K, V]
#
# Initialize a new Dictionary.
#
# @param [Hash] hash A hash of items to add to the dictionary.
# @param [Integer] limit The maximum number of items in the dictionary.
# @param [false, Proc] sort Whether to sort the items in the dictionary.
def initialize: (?::Hash[untyped, untyped] hash, ?limit: Integer?, ?sort: (bool | Proc)) -> void
#
# Registers a new item in the dictionary.
#
# @param [#to_s] id The ID of the item.
# @param [Object] body The item to register.
#
# @return [self] The dictionary.
def register: (_ToS id, Object body) -> self
#
# Merges another dictionary into this one.
#
# @param [Discorb::Dictionary] other The dictionary to merge.
def merge: (Discorb::Dictionary other) -> untyped
#
# Removes an item from the dictionary.
#
# @param [#to_s] id The ID of the item to remove.
def remove: (_ToS id) -> untyped
#
# Get an item from the dictionary.
#
# @param [#to_s] id The ID of the item.
# @return [Object] The item.
# @return [nil] if the item was not found.
#
# @overload get(index)
# @param [Integer] index The index of the item.
#
# @return [Object] The item.
# @return [nil] if the item is not found.
def get: (K id) -> V?
#
# Returns the values of the dictionary.
#
# @return [Array] The values of the dictionary.
def values: () -> ::Array[V]
#
# Checks if the dictionary has an ID.
#
# @param [#to_s] id The ID to check.
#
# @return [Boolean] `true` if the dictionary has the ID, `false` otherwise.
def has?: (_ToS id) -> bool
#
# Send a message to the array of values.
def method_missing: (untyped name) -> untyped
def respond_to_missing?: (untyped name, untyped args, untyped kwargs) -> bool
def inspect: () -> String
# @return [Integer] The maximum number of items in the dictionary.
attr_accessor limit: Integer
end
RBS
# #endregion
type_errors.each { |error, type| base.gsub!(error, type) }
base.gsub!("end\n\n\nend", "end\n")
base.gsub!(/ +$/m, "")
File.write("sig/discorb.rbs", base)
end
desc "Lint rbs with stree"
task :lint do
sh "stree check --plugins=rbs sig/**/*.rbs"
end
desc "Autofix rbs with stree"
task "lint:fix" do
sh "stree write --plugins=rbs sig/**/*.rbs"
end
end
task document: %i[document:yard document:replace]
desc "Lint code with rubocop"
task :lint do
sh "rubocop lib spec Rakefile"
end
desc "Autofix code with rubocop"
task "lint:fix" do
sh "rubocop lib spec Rakefile -A"
end