From e693b67f5c051f12d049e8cc05ceb700b759f1eb Mon Sep 17 00:00:00 2001 From: Anna Kudriavtsev Date: Sun, 17 Dec 2023 15:09:49 -0600 Subject: [PATCH] refactor: rewrite colorlib, add type checking --- .rubocop.yml | 3 + Gemfile | 2 +- Gemfile.lock | 12 +-- Steepfile | 33 ++++++ gemset.nix | 38 ++++--- lib/qbot/colorlib.rb | 45 ++------ rbs_collection.lock.yaml | 220 ++++++++++++++++++++++++++++++++++++++ rbs_collection.yaml | 19 ++++ sig/lib/qbot/colorlib.rbs | 99 +++++++++++++++++ 9 files changed, 408 insertions(+), 63 deletions(-) create mode 100644 Steepfile create mode 100644 rbs_collection.lock.yaml create mode 100644 rbs_collection.yaml create mode 100644 sig/lib/qbot/colorlib.rbs diff --git a/.rubocop.yml b/.rubocop.yml index d9b08f8..8fa50ba 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -23,5 +23,8 @@ Style/CommandLiteral: Style/AccessModifierDeclarations: EnforcedStyle: inline +Style/DataInheritance: + Enabled: false + Style/ParallelAssignment: Enabled: false diff --git a/Gemfile b/Gemfile index 336106b..1d44399 100644 --- a/Gemfile +++ b/Gemfile @@ -90,4 +90,4 @@ group :development do end # Colors -gem 'matrix' +gem 'numo-narray' diff --git a/Gemfile.lock b/Gemfile.lock index b517add..c79396e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -33,7 +33,7 @@ GIT GEM remote: https://rubygems.org/ specs: - abbrev (0.1.1) + abbrev (0.1.2) activejob (7.1.2) activesupport (= 7.1.2) globalid (>= 0.3.6) @@ -120,7 +120,7 @@ GEM domain_name (~> 0.5) i18n (1.14.1) concurrent-ruby (~> 1.0) - io-console (0.7.0) + io-console (0.7.1) jaro_winkler (1.5.6) jsi (0.7.0) addressable (~> 2.3) @@ -137,7 +137,6 @@ GEM loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) - matrix (0.4.2) mediawiki_api (0.8.1) faraday (~> 1) faraday-cookie_jar @@ -155,6 +154,7 @@ GEM nokogiri (1.15.5) mini_portile2 (~> 2.8.2) racc (~> 1.4) + numo-narray (0.9.2.1) open_uri_redirections (0.2.1) opus-ruby (1.0.1) ffi @@ -234,7 +234,7 @@ GEM rufus-scheduler (3.9.1) fugit (~> 1.1, >= 1.1.6) rutie (0.0.4) - securerandom (0.3.0) + securerandom (0.3.1) solargraph (0.48.0) backport (~> 1.2) benchmark @@ -250,7 +250,7 @@ GEM thor (~> 1.0) tilt (~> 2.0) yard (~> 0.9, >= 0.9.24) - sorbet-runtime (0.5.11155) + sorbet-runtime (0.5.11156) sqlite3 (1.6.9) mini_portile2 (~> 2.8.0) steep (1.6.0) @@ -319,10 +319,10 @@ DEPENDENCIES jsi kramdown kramdown-parser-gfm - matrix mediawiki_api narray nokogiri + numo-narray open_uri_redirections paint parser diff --git a/Steepfile b/Steepfile new file mode 100644 index 0000000..185deb6 --- /dev/null +++ b/Steepfile @@ -0,0 +1,33 @@ +D = Steep::Diagnostic + +target :lib do + signature 'sig' + + check 'lib' + check 'modules' + check 'Gemfile' + # check "app/models/**/*.rb" # Glob + # ignore "lib/templates/*.rb" + + # library "pathname" # Standard libraries + # library "strong_json" # Gems + # library 'numo-narray' + + # configure_code_diagnostics(D::Ruby.default) # `default` diagnostics setting (applies by default) + # configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting + # configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting + # configure_code_diagnostics(D::Ruby.silent) # `silent` diagnostics setting + # configure_code_diagnostics do |hash| # You can setup everything yourself + # hash[D::Ruby::NoMethod] = :information + # end +end + +# target :test do +# signature "sig", "sig-private" +# +# check "test" +# +# # library "pathname" # Standard libraries +# end + +# vim: ft=ruby diff --git a/gemset.nix b/gemset.nix index 294027a..7384d00 100644 --- a/gemset.nix +++ b/gemset.nix @@ -4,10 +4,10 @@ platforms = []; source = { remotes = ["https://rubygems.org"]; - sha256 = "0043p7lc13al7clcp9h7gp1hi1n5zzhjiflm2z9jcjqg8bav091p"; + sha256 = "0hj2qyx7rzpc7awhvqlm597x7qdxwi4kkml4aqnp5jylmsm4w6xd"; type = "gem"; }; - version = "0.1.1"; + version = "0.1.2"; }; activejob = { dependencies = ["activesupport" "globalid"]; @@ -526,10 +526,10 @@ platforms = []; source = { remotes = ["https://rubygems.org"]; - sha256 = "16a2fw7kxzyql92kg5aqyy00l44kj0pbarimvv53y7shfrx8344z"; + sha256 = "1fmwbcapyhla84xhwj3gfws6rb4lw3928ybz6g3lr372dgxakzx5"; type = "gem"; }; - version = "0.7.0"; + version = "0.7.1"; }; jaro_winkler = { groups = ["default" "development"]; @@ -634,18 +634,6 @@ }; version = "2.22.0"; }; - matrix = { - groups = ["default"]; - platforms = []; - source = { - remotes = ["https://rubygems.org"]; - sha256 = "1h2cgkpzkh3dd0flnnwfq6f3nl2b1zff9lvqz8xs853ssv5kq23i"; - target = "ruby"; - type = "gem"; - }; - targets = []; - version = "0.4.2"; - }; mediawiki_api = { dependencies = ["faraday" "faraday-cookie_jar" "faraday_middleware"]; groups = ["default"]; @@ -767,6 +755,16 @@ }; version = "1.15.5"; }; + numo-narray = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1syw9bhkk0bnacadcpdbwvc66j1gi3qqgcvqv3zqh4myxr3npmzf"; + type = "gem"; + }; + version = "0.9.2.1"; + }; open_uri_redirections = { groups = ["default"]; platforms = []; @@ -1219,10 +1217,10 @@ platforms = []; source = { remotes = ["https://rubygems.org"]; - sha256 = "1njx22cpjd6bmm717bp24cr0rkxkdn1n4wv3g19hwgrrc8ffar1p"; + sha256 = "1phv6kh417vkanhssbjr960c0gfqvf8z7d3d9fd2yvd41q64bw4q"; type = "gem"; }; - version = "0.3.0"; + version = "0.3.1"; }; solargraph = { dependencies = ["backport" "benchmark" "diff-lcs" "e2mmap" "jaro_winkler" "kramdown" "kramdown-parser-gfm" "parser" "reverse_markdown" "rubocop" "thor" "tilt" "yard"]; @@ -1240,10 +1238,10 @@ platforms = []; source = { remotes = ["https://rubygems.org"]; - sha256 = "048g79r0j4lmjicp55yb88sysjfx5m0dpf74ags4sz9spvgsi0g6"; + sha256 = "1lkkb5isdrdn5hjq5sygr20zlqrgf3yydf3n5s5dwq7r74glrbx0"; type = "gem"; }; - version = "0.5.11155"; + version = "0.5.11156"; }; sqlite3 = { dependencies = ["mini_portile2"]; diff --git a/lib/qbot/colorlib.rb b/lib/qbot/colorlib.rb index 24738a7..00e1cbc 100644 --- a/lib/qbot/colorlib.rb +++ b/lib/qbot/colorlib.rb @@ -1,49 +1,19 @@ # frozen_string_literal: true +require 'matrix' + module QBot # Library for working with RGB, XYZ, and LAB colorspaces module ColorLib - def self.matrix_dim(mx) - raise('argument is not a matrix') \ - if mx.empty? || mx.first&.empty? - - raise('argument is not a matrix') \ - unless mx.map(&:size).then { |sizes| sizes.all?(sizes.first) } - - mfirst = mx.first || [] - - [mx.size, mfirst.size] - end - - def self.mul_compat?(mx1, mx2) - _, c1 = matrix_dim(mx1) - r2, = matrix_dim(mx2) - - c1 == r2 - end - - def self.dot_product(ary1, ary2) - ary1.zip(ary2).map { |a, b| a * (b || 0.0) }.sum - end - - def self.matmul(mx1, mx2) - raise('matrices have incompatible dimensions') \ - unless mul_compat?(mx1, mx2) - - mx2t = mx2.transpose - - mx1.map { |row| mx2t.zip(row).map { |c, _| dot_product(row, c) } } - end - # rubocop: disable Layout/SpaceInsideArrayLiteralBrackets # rubocop: disable Layout/ExtraSpacing - RGB_XYZ_MATRIX = [ + RGB_XYZ_MATRIX = Matrix[ [ 0.4124564, 0.3575761, 0.1804375 ], [ 0.2126729, 0.7151522, 0.0721750 ], [ 0.0193339, 0.1191920, 0.9503041 ] ].freeze - XYZ_RGB_MATRIX = [ + XYZ_RGB_MATRIX = Matrix[ [ 3.2404542, -1.5371385, -0.4985314 ], [ -0.9692660, 1.8760108, 0.0415560 ], [ 0.0556434, -0.2040259, 1.0572252 ] @@ -54,8 +24,10 @@ def self.matmul(mx1, mx2) ## # A tristimulus value in the CIE 1931 space. class XYZTristimulus < Data.define(:x, :y, :z) + # @dynamic x, y, z, initialize + def to_srgb_linear - r, g, b = ColorLib.matmul(XYZ_RGB_MATRIX, [[x], [y], [z]]).flatten + r, g, b = (XYZ_RGB_MATRIX * Matrix[[x], [y], [z]]).to_a.flatten SRGBLinearColor.new(r:, g:, b:) end @@ -98,6 +70,7 @@ def to_a = to_ary ## # An RGB value in the sRGB colorspace. class SRGBColor < Data.define(:r, :g, :b) + # @dynamic r, g, b, initialize def self.from_hex(hex) r, g, b = hex.chars.last(6).each_slice(2) .map { |c| c.join.to_i(16) / 255.0 } @@ -139,7 +112,7 @@ def to_a = to_ary # A gamma-expanded ("linear light") RGB value in the sRGB colorspace. class SRGBLinearColor < Data.define(:r, :g, :b) def to_xyz - x, y, z = ColorLib.matmul(RGB_XYZ_MATRIX, [[r], [g], [b]]).flatten + x, y, z = (RGB_XYZ_MATRIX * Matrix[[r], [g], [b]]).to_a.flatten XYZTristimulus.new(x:, y:, z:) end diff --git a/rbs_collection.lock.yaml b/rbs_collection.lock.yaml new file mode 100644 index 0000000..22657e1 --- /dev/null +++ b/rbs_collection.lock.yaml @@ -0,0 +1,220 @@ +--- +path: ".gem_rbs_collection" +gems: +- name: activejob + version: '6.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: activemodel + version: '7.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: activerecord + version: '7.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: activesupport + version: '7.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: addressable + version: '2.8' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: ast + version: '2.4' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: base64 + version: '0' + source: + type: stdlib +- name: bigdecimal + version: '0' + source: + type: stdlib +- name: concurrent-ruby + version: '1.1' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: connection_pool + version: '2.4' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: date + version: '0' + source: + type: stdlib +- name: delayed_job + version: '4.1' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: delayed_job_active_record + version: '4.1' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: erb + version: '0' + source: + type: stdlib +- name: faraday + version: '2.5' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: fileutils + version: '0' + source: + type: stdlib +- name: forwardable + version: '0' + source: + type: stdlib +- name: globalid + version: '1.1' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: i18n + version: '1.10' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: io-console + version: '0' + source: + type: stdlib +- name: logger + version: '0' + source: + type: stdlib +- name: mime-types + version: '3.5' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: minitest + version: '0' + source: + type: stdlib +- name: monitor + version: '0' + source: + type: stdlib +- name: mutex_m + version: '0' + source: + type: stdlib +- name: net-http + version: '0' + source: + type: stdlib +- name: net-protocol + version: '0' + source: + type: stdlib +- name: nokogiri + version: '1.11' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: parser + version: '3.2' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: rake + version: '13.0' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: rest-client + version: '2.1' + source: + type: git + name: ruby/gem_rbs_collection + revision: 20e6e0f0685139dbd29df50e03367e222aa5d1b8 + remote: https://github.com/ruby/gem_rbs_collection.git + repo_dir: gems +- name: securerandom + version: '0' + source: + type: stdlib +- name: singleton + version: '0' + source: + type: stdlib +- name: time + version: '0' + source: + type: stdlib +- name: timeout + version: '0' + source: + type: stdlib +- name: uri + version: '0' + source: + type: stdlib +gemfile_lock_path: "../../../../../nix/store/fafy0l0xqvv4i9xig3frxh0s4zj24xrn-gemfile-and-lockfile/Gemfile.lock" diff --git a/rbs_collection.yaml b/rbs_collection.yaml new file mode 100644 index 0000000..66e30ec --- /dev/null +++ b/rbs_collection.yaml @@ -0,0 +1,19 @@ +# Download sources +sources: + - type: git + name: ruby/gem_rbs_collection + remote: https://github.com/ruby/gem_rbs_collection.git + revision: main + repo_dir: gems + +# You can specify local directories as sources also. +# - type: local +# path: path/to/your/local/repository + +# A directory to install the downloaded RBSs +path: .gem_rbs_collection + +# gems: +# # If you want to avoid installing rbs files for gems, you can specify them here. +# - name: GEM_NAME +# ignore: true diff --git a/sig/lib/qbot/colorlib.rbs b/sig/lib/qbot/colorlib.rbs new file mode 100644 index 0000000..845f335 --- /dev/null +++ b/sig/lib/qbot/colorlib.rbs @@ -0,0 +1,99 @@ +interface _Array + def to_ary : () -> Array[untyped] +end + +class Matrix[T < Numeric] + def self.[] : [T < Numeric] (*Array[T]) -> Matrix[T] + + def * : (instance) -> instance + + def to_a : () -> Array[Array[T]] +end + +module QBot + module ColorLib + RGB_XYZ_MATRIX : Matrix[Float] + XYZ_RGB_MATRIX : Matrix[Float] + + class XYZTristimulus + attr_reader x : Float + attr_reader y : Float + attr_reader z : Float + + def initialize : (Float x, Float y, Float z) -> void + | (x: Float, y: Float, z: Float) -> void + + def to_srgb_linear : () -> SRGBLinearColor + def to_srgb : () -> SRGBColor + + private def scale_f : (Float val) -> Float + def to_cielab : (?illuminant: XYZTristimulus) -> CIELABColor + + def to_ary : () -> Array[Float] + def to_a : () -> Array[Float] + end + + D65 : XYZTristimulus + D50 : XYZTristimulus + + class SRGBColor + attr_reader r : Float + attr_reader g : Float + attr_reader b : Float + + def initialize : (Float r, Float g, Float b) -> void + | (r: Float, g: Float, b: Float) -> void + + def self.from_hex : (String hex) -> instance + + def to_hex : () -> String + + private def gamma_expand_one : (Float val) -> Float + def gamma_expand : () -> SRGBLinearColor + + def to_xyz : () -> XYZTristimulus + + def to_ary : () -> Array[Float] + def to_a : () -> Array[Float] + end + + class SRGBLinearColor + attr_reader r : Float + attr_reader g : Float + attr_reader b : Float + + def initialize : (Float r, Float g, Float b) -> void + | (r: Float, g: Float, b: Float) -> void + + def to_xyz : () -> XYZTristimulus + + private def gamma_compress_one : (Float val) -> Float + def gamma_compress : () -> SRGBColor + + def to_ary : () -> Array[Float] + def to_a : () -> Array[Float] + end + + class CIELABColor + attr_reader l : Float + attr_reader a : Float + attr_reader b : Float + + def initialize : (Float l, Float a, Float b) -> void + | (l: Float, a: Float, b: Float) -> void + + def self.from_hex : (String hex) -> CIELABColor + def to_hex : () -> String + + def to_xyz : (?illuminant: XYZTristimulus) -> XYZTristimulus + + def cie76 : (CIELABColor other) -> Float + def ciede2000 : (CIELABColor other) -> Float + + def to_ary : () -> Array[Float] + def to_a : () -> Array[Float] + end + end +end + +# vim: ft=rbs sw=2 ts=2