$ cat a.rbs
class Foo
def bar: () -> void
def self.baz: () -> (Integer | String)
def qux: (untyped) -> untyped
end
$ cat b.rbs
module Bar
def bar: () -> void
end
module Baz
def baz: (Integer) -> Integer?
end
class Foo
include Bar
extend Baz
end
$ rbs_diff --with_defined_in --no_collection --format markdown --type_name Foo --before_path a.rbs --after_path b.rbs
| before | after |
| --- | --- |
| `(::Foo) def bar: () -> void` | `(::Bar) def bar: () -> void` |
| `(::Foo) def qux: (untyped) -> untyped` | `-` |
| `(::Foo) def self.baz: () -> (::Integer | ::String)` | `(::Baz) def self.baz: (::Integer) -> ::Integer?` |
$ rbs_diff -f diff -t Foo -b a.rbs -a b.rbs
- def qux: (untyped) -> untyped
+ -
- def self.baz: () -> (::Integer | ::String)
+ def self.baz: (::Integer) -> ::Integer?
Last active
August 4, 2023 07:06
-
-
Save ksss/15e7d6afacc139567ccfa3b2c92d187b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env ruby | |
# $ rbs_diff --help | |
# Usage: rbs_diff [options] | |
# -f, --format FORMAT | |
# -t, --type_name NAME | |
# -b, --before_path PATH | |
# -a, --after_path PATH | |
# --no_collection | |
# --with_defined_in | |
require 'rbs' | |
require 'rbs/cli' | |
require 'optparse' | |
module RBSDiff | |
Option = Struct.new( | |
:format, | |
:type_name, | |
:before_path, | |
:after_path, | |
:no_collection, | |
:with_defined_in, | |
keyword_init: true | |
) do | |
def initialize | |
super | |
# defaults | |
self.no_collection = false | |
self.with_defined_in = false | |
self.before_path = [] | |
self.after_path = [] | |
end | |
def validate! | |
unless ["diff", "markdown"].include?(format) | |
error! "format should be in [diff, markdown]" | |
end | |
end | |
def error!(msg) | |
$stderr.puts("error: #{msg}") | |
exit 1 | |
end | |
end | |
class Cli | |
def initialize(argv) | |
@option = Option.new | |
OptionParser.new do |o| | |
o.on("-f", "--format FORMAT") do |arg| | |
@option.format = arg | |
end | |
o.on("-t", "--type_name NAME") do |arg| | |
@option.type_name = TypeName(arg).absolute! | |
end | |
o.on("-b", "--before_path [PATH]") do |arg| | |
@option.before_path << arg | |
end | |
o.on("-a", "--after_path [PATH]") do |arg| | |
@option.after_path << arg | |
end | |
o.on("--no_collection") do | |
@option.no_collection = true | |
end | |
o.on("--with_defined_in") do | |
@option.with_defined_in = true | |
end | |
end.parse!(argv) | |
@option.validate! | |
end | |
def run | |
Runner.new(@option).run | |
end | |
end | |
class Runner | |
def initialize(option) | |
@option = option | |
end | |
def run | |
__send__("run_#{@option.format}") | |
end | |
def run_diff | |
each_diff do |before, after| | |
puts "- #{before}" | |
puts "+ #{after}" | |
puts | |
end | |
end | |
def run_markdown | |
puts "| before | after |" | |
puts "| --- | --- |" | |
each_diff do |before, after| | |
puts "| `#{before}` | `#{after}` |" | |
end | |
end | |
def each_diff(&block) | |
before_instance_methods, before_singleton_methods = build_methods(@option.before_path) | |
after_instance_methods, after_singleton_methods = build_methods(@option.after_path) | |
each_diff_by(:instance, before_instance_methods, after_instance_methods, &block) | |
each_diff_by(:singleton, before_singleton_methods, after_singleton_methods, &block) | |
end | |
private | |
def each_diff_by(kind, before_methods, after_methods) | |
all_keys = before_methods.keys.to_set + after_methods.keys.to_set | |
all_keys.each do |key| | |
before = definition_method_to_s(key, kind, before_methods[key]) or next | |
after = definition_method_to_s(key, kind, after_methods[key]) or next | |
next if before == after | |
yield [before, after] | |
end | |
end | |
def build_methods(path) | |
env = build_env(path) | |
builder = build_builder(env) | |
instance_methods = begin | |
builder.build_instance(@option.type_name).methods | |
rescue | |
{} | |
end | |
singleton_methods = begin | |
builder.build_singleton(@option.type_name).methods | |
rescue | |
{} | |
end | |
[ | |
instance_methods, | |
singleton_methods | |
] | |
end | |
def build_env(path) | |
rbs_options = RBS::CLI::LibraryOptions.new | |
if @option.no_collection | |
rbs_options.config_path = nil | |
end | |
loader = rbs_options.loader | |
path&.each do |dir| | |
loader.add(path: Pathname(dir)) | |
end | |
RBS::Environment.from_loader(loader) | |
end | |
def build_builder(env) | |
RBS::DefinitionBuilder.new(env: env.resolve_type_names) | |
end | |
def definition_method_to_s(key, kind, definition_method) | |
if definition_method | |
prefix = kind == :instance ? "" : "self." | |
defined_in = @option.with_defined_in ? "(#{definition_method.defined_in}) " : "" | |
if definition_method.alias_of | |
"#{defined_in}alias #{prefix}#{key} #{prefix}#{definition_method.alias_of.defs.first.member.name}" | |
else | |
"#{defined_in}def #{prefix}#{key}: #{definition_method.method_types.join(" | ")}" | |
end | |
else | |
"-" | |
end | |
end | |
end | |
end | |
RBSDiff::Cli.new(ARGV).run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment