From 5d32e4ad6dfbd2b56938131078219351dd89f875 Mon Sep 17 00:00:00 2001 From: Floris de Bijl Date: Mon, 11 Mar 2024 16:50:55 +0100 Subject: [PATCH 1/5] Bump diff-lcs version --- rich-text.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/rich-text.gemspec b/rich-text.gemspec index 8f957c9..ef2ce0d 100644 --- a/rich-text.gemspec +++ b/rich-text.gemspec @@ -21,6 +21,7 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency "diff-lcs", "~> 1.2.5" + spec.add_dependency "diff-lcs", "~> 1.5.1" spec.add_dependency "activesupport", ">= 3.0.0" spec.add_dependency "nokogiri", ">= 1.0.0" From 2792a5c1acf796e016f1f3c9c360c4674d040b43 Mon Sep 17 00:00:00 2001 From: Floris de Bijl Date: Mon, 11 Mar 2024 16:53:30 +0100 Subject: [PATCH 2/5] Auto-correct rubocop offenses --- Rakefile | 12 +- bin/console | 6 +- bin/rake | 14 +- lib/rich-text.rb | 23 ++- lib/rich-text/attributes.rb | 7 +- lib/rich-text/delta.rb | 62 +++---- lib/rich-text/diff.rb | 10 +- lib/rich-text/html.rb | 35 ++-- lib/rich-text/iterator.rb | 21 ++- lib/rich-text/op.rb | 21 +-- lib/rich-text/version.rb | 2 +- rich-text.gemspec | 36 ++-- test/test_helper.rb | 2 +- test/unit/attributes_test.rb | 2 +- test/unit/delta_test.rb | 70 ++++---- test/unit/html_test.rb | 312 +++++++++++++++++++---------------- test/unit/iterator_test.rb | 10 +- test/unit/op_test.rb | 10 +- 18 files changed, 333 insertions(+), 322 deletions(-) diff --git a/Rakefile b/Rakefile index cdd2b41..5697345 100644 --- a/Rakefile +++ b/Rakefile @@ -1,13 +1,13 @@ -require "bundler/gem_tasks" -require "rake/testtask" -require "yard" +require 'bundler/gem_tasks' +require 'rake/testtask' +require 'yard' Rake::TestTask.new(:test) do |t| - t.libs << "test" - t.libs << "lib" + t.libs << 'test' + t.libs << 'lib' t.test_files = FileList['test/**/*_test.rb'] end YARD::Rake::YardocTask.new(:doc) -task :default => :test +task default: :test diff --git a/bin/console b/bin/console index 51b2752..994a555 100755 --- a/bin/console +++ b/bin/console @@ -1,7 +1,7 @@ #!/usr/bin/env ruby -require "bundler/setup" -require "rich-text" +require 'bundler/setup' +require 'rich-text' # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. @@ -10,5 +10,5 @@ require "rich-text" # require "pry" # Pry.start -require "irb" +require 'irb' IRB.start diff --git a/bin/rake b/bin/rake index ea0e293..5aa8950 100755 --- a/bin/rake +++ b/bin/rake @@ -8,11 +8,11 @@ # this file is here to facilitate running it. # -require "pathname" -ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", - Pathname.new(__FILE__).realpath) +require 'pathname' +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', + Pathname.new(__FILE__).realpath) -bundle_binstub = File.expand_path("../bundle", __FILE__) +bundle_binstub = File.expand_path('bundle', __dir__) if File.file?(bundle_binstub) if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/ @@ -23,7 +23,7 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this end end -require "rubygems" -require "bundler/setup" +require 'rubygems' +require 'bundler/setup' -load Gem.bin_path("rake", "rake") +load Gem.bin_path('rake', 'rake') diff --git a/lib/rich-text.rb b/lib/rich-text.rb index f48c495..44bafb1 100644 --- a/lib/rich-text.rb +++ b/lib/rich-text.rb @@ -14,22 +14,21 @@ def self.configure require 'rich-text/html' RichText.configure do |c| - c.html_inline_formats = { - bold: { tag: 'strong' }, - br: { tag: 'br' }, - hr: { tag: 'hr', block_format: false }, - italic: { tag: 'em' }, - link: { tag: 'a', apply: ->(el, op, ctx){ el[:href] = op.attributes[:link] } } + bold: { tag: 'strong' }, + br: { tag: 'br' }, + hr: { tag: 'hr', block_format: false }, + italic: { tag: 'em' }, + link: { tag: 'a', apply: ->(el, op, _ctx) { el[:href] = op.attributes[:link] } } } c.html_block_formats = { - firstheader: { tag: 'h1' }, - secondheader: { tag: 'h2' }, - thirdheader: { tag: 'h3' }, - bullet: { tag: 'li', parent: 'ul' }, - list: { tag: 'li', parent: 'ol' }, - id: { apply: ->(el, op, ctx){ el[:id] = op.attributes[:id] } } + firstheader: { tag: 'h1' }, + secondheader: { tag: 'h2' }, + thirdheader: { tag: 'h3' }, + bullet: { tag: 'li', parent: 'ul' }, + list: { tag: 'li', parent: 'ol' }, + id: { apply: ->(el, op, _ctx) { el[:id] = op.attributes[:id] } } } c.html_default_block_format = 'p' diff --git a/lib/rich-text/attributes.rb b/lib/rich-text/attributes.rb index efcbb67..f3cc624 100644 --- a/lib/rich-text/attributes.rb +++ b/lib/rich-text/attributes.rb @@ -5,14 +5,16 @@ class << self def compose(a, b, keep_nil) return b if a.nil? return a if b.nil? - result = b.merge(a) { |k,vb,va| vb } - result.delete_if { |k,v| v.nil? } unless keep_nil + + result = b.merge(a) { |_k, vb, _va| vb } + result.delete_if { |_k, v| v.nil? } unless keep_nil result end def diff(a, b) return b if a.nil? return a if b.nil? + (a.keys | b.keys).each_with_object({}) do |key, memo| memo[key] = b[key] if a[key] != b[key] end @@ -20,6 +22,7 @@ def diff(a, b) def transform(a, b, priority) return b if a.nil? || a.empty? || b.nil? || b.empty? || !priority + (b.keys - a.keys).each_with_object({}) do |key, memo| memo[key] = b[key] end diff --git a/lib/rich-text/delta.rb b/lib/rich-text/delta.rb index e5a68d1..0724370 100644 --- a/lib/rich-text/delta.rb +++ b/lib/rich-text/delta.rb @@ -30,8 +30,6 @@ def initialize(data = []) else ArgumentError.new("Please provide either String, Array or Hash with an 'ops' key containing an Array") end - - @ops end # Appends an insert operation. A no-op if the provided value is the empty string. @@ -43,6 +41,7 @@ def initialize(data = []) # delta.insert({ image: 'http://i.imgur.com/FUCb95Y.gif' }) def insert(value, attributes = {}) return self if value.is_a?(String) && value.length == 0 + push(Op.new(:insert, value, attributes)) end @@ -53,6 +52,7 @@ def insert(value, attributes = {}) # delta.delete(5) def delete(value) return self if value <= 0 + push(Op.new(:delete, value)) end @@ -64,6 +64,7 @@ def delete(value) # delta.retain(4).retain(5, { color: '#0c6' }) def retain(value, attributes = {}) return self if value <= 0 + push(Op.new(:retain, value, attributes)) end @@ -85,7 +86,7 @@ def push(op) if last_op.delete? && op.insert? index -= 1 last_op = @ops[index - 1] - if !last_op + unless last_op @ops.unshift(op) return self end @@ -108,18 +109,16 @@ def push(op) @ops[index, 0] = op end - return self + self end - alias :<< :push + alias << push # Modifies self by removing the last op if it was a retain without attributes. # @return [Delta] `self` for chainability def chop! last_op = @ops.last - if last_op && last_op.retain? && !last_op.attributes? - @ops.pop - end - return self + @ops.pop if last_op && last_op.retain? && !last_op.attributes? + self end # Returns true if all operations are inserts, i.e. a fully-composed document @@ -127,12 +126,13 @@ def chop! def insert_only? @ops.all?(&:insert?) end - alias :document? :insert_only? + alias document? insert_only? # Returns true if the last operation is a string insert that ends with a `\n` character. # @return [Boolean] def trailing_newline? return false unless @ops.last && @ops.last.insert?(String) + @ops.last.value.end_with?("\n") end @@ -140,8 +140,8 @@ def trailing_newline? # @param other [Delta] # @return [Boolean] # @todo Not implemented yet - def include?(other) - raise NotImplementedError.new("TODO") + def include?(_other) + raise NotImplementedError, 'TODO' end # Yields ops of at most `size` length to the block, or returns an enumerator which will do the same @@ -151,9 +151,10 @@ def include?(other) # @example # delta = RichText::Delta.new.insert('abc') # delta.each_slice(2).to_a # => [#, #] - def each_slice(size = 1) + def each_slice(size = 1, &block) return enum_for(:each_slice, size) unless block_given? - Iterator.new(@ops).each(size) { |op| yield op } + + Iterator.new(@ops).each(size, &block) self end @@ -167,6 +168,7 @@ def each_slice(size = 1) # delta.each_char.to_a # => [["a", { bold: true }], ["b", {}], [{ image: "http://i.imgur.com/YtQPTnw.gif" }, {}]] def each_char return enum_for(:each_char) unless block_given? + each_slice(1) { |op| yield op.value, op.attributes } self end @@ -186,7 +188,7 @@ def each_line while iter.next? op = iter.next - if !op.insert?(String) + unless op.insert?(String) line.push(op) next end @@ -199,9 +201,7 @@ def each_line offset = idx + 1 end - if offset < op.value.length - line.push op.slice(offset) - end + line.push op.slice(offset) if offset < op.value.length end yield line if line.length > 0 @@ -210,9 +210,10 @@ def each_line # Yields each operation in the delta, as-is. # @yield [op] an {Op} object # @return [Enumerator, Delta] if no block given, returns an {Enumerator}, else returns `self` for chainability - def each_op + def each_op(&block) return enum_for(:each_op) unless block_given? - @ops.each { |op| yield op } + + @ops.each(&block) self end @@ -254,9 +255,9 @@ def slice(start = 0, len = length) end idx += op.length end - return delta + delta end - alias :[] :slice + alias [] slice # Returns a Delta that is equivalent to first applying the operations of `self`, then applying the operations of `other` on top of that. # @param other [Delta] @@ -293,7 +294,7 @@ def compose(other) end delta.chop! end - alias :| :compose + alias | compose # Modifies `self` by the concatenating this and another document Delta's operations. # Correctly handles the case of merging the last operation of `self` with the first operation of `other`, if possible. @@ -360,7 +361,7 @@ def diff(other) delta.chop! end - alias :- :diff + alias - diff # Transform other Delta against own operations, such that [transformation property 1 (TP1)](https://en.wikipedia.org/wiki/Operational_transformation#Convergence_properties) holds: # @@ -384,6 +385,7 @@ def diff(other) # a.transform(b, false) # => #"#fff", :bold=>true}]> def transform(other, priority) return transform_position(other, priority) if other.is_a?(Integer) + iter = Iterator.new(@ops) other_iter = Iterator.new(other.ops) delta = Delta.new @@ -409,7 +411,7 @@ def transform(other, priority) end delta.chop! end - alias :^ :transform + alias ^ transform # Transform an index against the current delta. Useful for shifting cursor & selection positions in response to remote changes. # @param index [Integer] an offset position that may be shifted by inserts and deletes happening beforehand @@ -435,12 +437,12 @@ def transform_position(index, priority) end offset += op.length end - return index + index end # @return [Hash] the Hash representation of this object, by converting each contained op into a Hash def to_h - { :ops => @ops.map(&:to_h) } + { ops: @ops.map(&:to_h) } end # @return [String] the JSON representation of this object, by delegating to {#to_h} @@ -477,8 +479,8 @@ def to_html(options = {}) # '#true}, insert={:image=>"http://i.imgur.com/vwGN6.gif"}]>' def inspect str = "#<#{self.class.name} [" - str << @ops.map { |o| o.inspect(false) }.join(", ") - str << "]>" + str << @ops.map { |o| o.inspect(false) }.join(', ') + str << ']>' end # A Delta is equal to another if all the ops are equal. @@ -487,6 +489,6 @@ def inspect def ==(other) other.is_a?(RichText::Delta) && @ops == other.ops end - alias_method :eql?, :== + alias eql? == end end diff --git a/lib/rich-text/diff.rb b/lib/rich-text/diff.rb index cfef175..2c7a453 100644 --- a/lib/rich-text/diff.rb +++ b/lib/rich-text/diff.rb @@ -5,10 +5,10 @@ module RichText class Diff attr_reader :chunks - def initialize(left, right) + def initialize(left, right, &block) @chunks = [] ::Diff::LCS.traverse_sequences(left.to_plaintext, right.to_plaintext, self) - @chunks.each { |c| yield c } if block_given? + @chunks.each(&block) if block_given? end def push(type) @@ -19,15 +19,15 @@ def push(type) end end - def match(args) + def match(_args) push :retain end - def discard_a(args) + def discard_a(_args) push :delete end - def discard_b(args) + def discard_b(_args) push :insert end end diff --git a/lib/rich-text/html.rb b/lib/rich-text/html.rb index ac7c938..df77724 100644 --- a/lib/rich-text/html.rb +++ b/lib/rich-text/html.rb @@ -6,13 +6,13 @@ module RichText class HTML ConfigError = Class.new(StandardError) - def self.render(delta, options={}) + def self.render(delta, options = {}) new(options).render(delta).inner_html end attr_reader :doc - def initialize(options={}, config=RichText.config) + def initialize(options = {}, config = RichText.config) @default_block_format = options[:default_block_format] || config.html_default_block_format @inline_formats = config.html_inline_formats.merge(options[:inline_formats] || {}) @block_formats = config.html_block_formats.merge(options[:block_formats] || {}) @@ -24,7 +24,7 @@ def initialize(options={}, config=RichText.config) end def render(delta) - raise TypeError.new("cannot convert retain or delete ops to html") unless delta.insert_only? + raise TypeError, 'cannot convert retain or delete ops to html' unless delta.insert_only? render_lines = [] @@ -46,7 +46,7 @@ def render(delta) @root end - private + private def inline_tag?(op) op.attributes.keys.find { |k| @inline_formats[k.to_sym]&.key?(:tag) } @@ -92,7 +92,7 @@ def render_line(ops) end # renders a block for a collection of elements based on a final operation - def render_block(op, elements, default_block_format=@default_block_format) + def render_block(op, elements, default_block_format = @default_block_format) elements = elements.compact return unless elements.any? @@ -103,10 +103,10 @@ def render_block(op, elements, default_block_format=@default_block_format) # direct insertions (like "hr" tags) omit block format entirely # install these elements directly into the root flow - if !default_block_format + unless default_block_format # remove tag formats from from insertions without a block, # this assures that only custom (non-tag) formatters run on the element - block_attrs = Hash[block_attrs.reject { |k, v| @block_formats[k.to_sym].key?(:tag) }] + block_attrs = Hash[block_attrs.reject { |k, _v| @block_formats[k.to_sym].key?(:tag) }] return elements.each do |el| el = apply_formats(@block_formats, el, op, attributes: block_attrs) @root.add_child(el) @@ -115,7 +115,7 @@ def render_block(op, elements, default_block_format=@default_block_format) # assure that the block has a tag formatting attribute # use or build a format for the default format, when necessary - unless block_attrs.detect { |k, v| block_formats[k.to_sym].key?(:tag) } + unless block_attrs.detect { |k, _v| block_formats[k.to_sym].key?(:tag) } # if the default isn't an official format, built a one-off definition for it unless block_formats.key?(default_block_format.to_sym) block_formats = block_formats.merge(default_block_format.to_sym => { tag: default_block_format.to_s }) @@ -129,13 +129,13 @@ def render_block(op, elements, default_block_format=@default_block_format) @root.add_child(el) end - def apply_formats(formats, content, op, attributes:nil) + def apply_formats(formats, content, op, attributes: nil) attributes ||= op.attributes # order of operations for rendering formats # tag formats are applied first (sorted by priority) # other attribute formats follow (sorted by priority) - ordered_formats = attributes.keys.map {|k| formats[k.to_sym] }.compact.sort do |a, b| + ordered_formats = attributes.keys.map { |k| formats[k.to_sym] }.compact.sort do |a, b| a = [a.key?(:tag) ? 0 : 1, a[:priority] || Float::INFINITY] b = [b.key?(:tag) ? 0 : 1, b[:priority] || Float::INFINITY] a <=> b @@ -148,13 +148,9 @@ def apply_formats(formats, content, op, attributes:nil) end def apply_format(format, content, op) - if format[:tag] - content = create_node(format, content, op: op) - end + content = create_node(format, content, op: op) if format[:tag] - if format[:apply] && format[:apply].respond_to?(:call) - format[:apply].call(content, op, @context) - end + format[:apply].call(content, op, @context) if format[:apply] && format[:apply].respond_to?(:call) if format[:parent] # build wrapper into a hierarchy of parents @@ -174,7 +170,7 @@ def apply_format(format, content, op) content end - def create_node(format={}, content=nil, tag:nil, op:nil) + def create_node(format = {}, content = nil, tag: nil, op: nil) tag ||= format[:tag] tag = 'span' if tag.respond_to?(:call) el = Nokogiri::XML::Node.new(tag, @doc) @@ -187,12 +183,9 @@ def create_node(format={}, content=nil, tag:nil, op:nil) content.each { |n| el.add_child(n) } end - if format[:tag].respond_to?(:call) && op - el = format[:tag].call(el, op, @context) - end + el = format[:tag].call(el, op, @context) if format[:tag].respond_to?(:call) && op el end - end end diff --git a/lib/rich-text/iterator.rb b/lib/rich-text/iterator.rb index e492499..e4f9a47 100644 --- a/lib/rich-text/iterator.rb +++ b/lib/rich-text/iterator.rb @@ -8,6 +8,7 @@ def initialize(ops) def each(size = 1) return enum_for(:each, size) unless block_given? + yield self.next(size) while next? end @@ -26,19 +27,17 @@ def next? def next(length = Float::INFINITY) next_op = @ops[@index] offset = @offset - if next_op - if length >= next_op.length - offset - length = next_op.length - offset - @index += 1 - @offset = 0 - else - @offset += length - end - - next_op.slice(offset, length) + return Op.new(:retain, Float::INFINITY) unless next_op + + if length >= next_op.length - offset + length = next_op.length - offset + @index += 1 + @offset = 0 else - return Op.new(:retain, Float::INFINITY) + @offset += length end + + next_op.slice(offset, length) end def reset diff --git a/lib/rich-text/op.rb b/lib/rich-text/op.rb index ab54c78..7992ff0 100644 --- a/lib/rich-text/op.rb +++ b/lib/rich-text/op.rb @@ -3,7 +3,7 @@ module RichText # Operations are the immutable units of rich-text deltas and documents. As such, we have a class that wraps these values and provides convenient methods for querying type and contents, and for subdividing as needed by {Delta#slice}. class Op - TYPES = [:insert, :retain, :delete].freeze + TYPES = %i[insert retain delete].freeze # @return [Symbol] one of {TYPES} attr_reader :type @@ -26,21 +26,19 @@ def self.parse(data) data = data.to_h.with_indifferent_access type_keys = (data.keys & TYPES.map(&:to_s)) if type_keys.length != 1 - raise ArgumentError.new("must be a Hash containing exactly one of the following keys: #{TYPES.inspect}") + raise ArgumentError, "must be a Hash containing exactly one of the following keys: #{TYPES.inspect}" end type = type_keys.first.to_sym value = data[type] - if [:retain, :delete].include?(type) && !value.is_a?(Integer) - raise ArgumentError.new("value must be an Integer when type is #{type.inspect}") + if %i[retain delete].include?(type) && !value.is_a?(Integer) + raise ArgumentError, "value must be an Integer when type is #{type.inspect}" end attributes = data[:attributes] - if attributes && !attributes.is_a?(Hash) - raise ArgumentError.new("attributes must be a Hash") - end + raise ArgumentError, 'attributes must be a Hash' if attributes && !attributes.is_a?(Hash) - self.new(type, value, attributes) + new(type, value, attributes) end # Creates a new Op object, based on a type, value, and attributes. No sanity checking is performed on the arguments; please use {Op.parse} for dealing with untrusted user input. @@ -109,9 +107,8 @@ def slice(start = 0, len = length) if insert?(String) Op.new(:insert, value.slice(start, len), attributes) elsif insert? - unless start == 0 && len == 1 - raise ArgumentError.new("cannot subdivide a non-string insert") - end + raise ArgumentError, 'cannot subdivide a non-string insert' unless start == 0 && len == 1 + dup else Op.new(type, [value - start, len].min, attributes) @@ -148,6 +145,6 @@ def inspect(wrap = true) def ==(other) other.is_a?(Op) && type == other.type && value == other.value && attributes == other.attributes end - alias :eql? :== + alias eql? == end end diff --git a/lib/rich-text/version.rb b/lib/rich-text/version.rb index 27a7cc2..94e9bca 100644 --- a/lib/rich-text/version.rb +++ b/lib/rich-text/version.rb @@ -1,3 +1,3 @@ module RichText - VERSION = "0.4.0" + VERSION = '0.4.0' end diff --git a/rich-text.gemspec b/rich-text.gemspec index ef2ce0d..75e0616 100644 --- a/rich-text.gemspec +++ b/rich-text.gemspec @@ -1,32 +1,32 @@ -# coding: utf-8 -lib = File.expand_path('../lib', __FILE__) +lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'rich-text/version' Gem::Specification.new do |spec| - spec.name = "rich-text" + spec.name = 'rich-text' spec.version = RichText::VERSION - spec.authors = ["Blake Thomson"] - spec.email = ["thomsbg@gmail.com"] + spec.authors = ['Blake Thomson'] + spec.email = ['thomsbg@gmail.com'] - spec.summary = %q{A ruby wrapper and utilities for rich text JSON documents.} - spec.homepage = "https://github.com/voxmedia/rich-text-ruby" - spec.license = "MIT" + spec.summary = 'A ruby wrapper and utilities for rich text JSON documents.' + spec.homepage = 'https://github.com/voxmedia/rich-text-ruby' + spec.license = 'MIT' spec.files = `git ls-files -z`.split("\x0").reject do |f| f.match(%r{^(test|spec|features)/}) end - spec.bindir = "exe" + spec.bindir = 'exe' spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] + spec.require_paths = ['lib'] - spec.add_dependency "diff-lcs", "~> 1.2.5" - spec.add_dependency "diff-lcs", "~> 1.5.1" - spec.add_dependency "activesupport", ">= 3.0.0" - spec.add_dependency "nokogiri", ">= 1.0.0" + spec.required_ruby_version = '>= 2.7.0' - spec.add_development_dependency "bundler" - spec.add_development_dependency "rake", "~> 10.0" - spec.add_development_dependency "minitest", "~> 5.0" - spec.add_development_dependency "yard" + spec.add_dependency 'activesupport', '>= 3.0.0' + spec.add_dependency 'diff-lcs', '~> 1.5.1' + spec.add_dependency 'nokogiri', '>= 1.0.0' + + spec.add_development_dependency 'bundler' + spec.add_development_dependency 'minitest', '~> 5.0' + spec.add_development_dependency 'rake', '~> 10.0' + spec.add_development_dependency 'yard' end diff --git a/test/test_helper.rb b/test/test_helper.rb index 0b8dfab..010f3a6 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,4 +1,4 @@ -$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) +$LOAD_PATH.unshift File.expand_path('../lib', __dir__) require 'rich-text' require 'minitest/spec' require 'minitest/autorun' diff --git a/test/unit/attributes_test.rb b/test/unit/attributes_test.rb index b238965..861a57f 100644 --- a/test/unit/attributes_test.rb +++ b/test/unit/attributes_test.rb @@ -2,7 +2,7 @@ describe RichText::Attributes do subject { RichText::Attributes } - let(:a) { { a: 1, b: 1, d: nil, x: 3 } } + let(:a) { { a: 1, b: 1, d: nil, x: 3 } } let(:b) { { a: 2, c: 2, x: 3 } } describe 'compose' do diff --git a/test/unit/delta_test.rb b/test/unit/delta_test.rb index 28633bf..eff35ee 100644 --- a/test/unit/delta_test.rb +++ b/test/unit/delta_test.rb @@ -145,53 +145,51 @@ describe 'to_h' do it 'returns a hash with an :ops key' do assert_equal({ - :ops => [ - { :insert => 'abc' }, - { :retain => 3, :attributes => { :x => 1 } }, - { :delete => 3 } - ] - }, subject.insert('abc').retain(3, x: 1).delete(3).to_h) + ops: [ + { insert: 'abc' }, + { retain: 3, attributes: { x: 1 } }, + { delete: 3 } + ] + }, subject.insert('abc').retain(3, x: 1).delete(3).to_h) end end describe 'to_plaintext' do it 'renders a string of plaintext' do subject = RichText::Delta.new([ - { insert: 'a man, ' }, - { insert: 'a plan', attributes: { italic: true } }, - { insert: ', ' }, - { insert: 'panama', attributes: { bold: true } }, - { insert: "\n" }, - { insert: "visit!\n" }, - ]) + { insert: 'a man, ' }, + { insert: 'a plan', attributes: { italic: true } }, + { insert: ', ' }, + { insert: 'panama', attributes: { bold: true } }, + { insert: "\n" }, + { insert: "visit!\n" } + ]) assert_equal("a man, a plan, panama\nvisit!", subject.to_plaintext) end it 'renders plaintext without objects and extra newlines' do subject = RichText::Delta.new([ - { insert: "kittens\n" }, - { insert: { image: { src: 'https://placekitten.com/200/150' } } }, - { insert: "\n" }, - { insert: { oembed: { url: 'https://youtu.be/KaOC9danxNo' } } }, - { insert: "\n" }, - { insert: "in space\n" } - ]) + { insert: "kittens\n" }, + { insert: { image: { src: 'https://placekitten.com/200/150' } } }, + { insert: "\n" }, + { insert: { oembed: { url: 'https://youtu.be/KaOC9danxNo' } } }, + { insert: "\n" }, + { insert: "in space\n" } + ]) assert_equal("kittens\n\n\nin space", subject.to_plaintext) end it 'renders plaintext with block handler for objects' do subject = RichText::Delta.new([ - { insert: "kittens\n" }, - { insert: { image: { src: 'https://placekitten.com/200/150' } } }, - { insert: "\n" }, - { insert: { oembed: { url: 'https://youtu.be/KaOC9danxNo' } } }, - { insert: "\n" }, - { insert: "in space\n" } - ]) + { insert: "kittens\n" }, + { insert: { image: { src: 'https://placekitten.com/200/150' } } }, + { insert: "\n" }, + { insert: { oembed: { url: 'https://youtu.be/KaOC9danxNo' } } }, + { insert: "\n" }, + { insert: "in space\n" } + ]) result = subject.to_plaintext do |op| - if op.value.key?(:image) - op.value[:image][:src] - end + op.value[:image][:src] if op.value.key?(:image) end assert_equal("kittens\nhttps://placekitten.com/200/150\n\nin space", result) end @@ -221,11 +219,11 @@ let(:d) { RichText::Delta.new.delete(1) } it 'insert + insert' do - assert_equal RichText::Delta.new.insert("ba"), a.compose(b) + assert_equal RichText::Delta.new.insert('ba'), a.compose(b) end it 'insert + retain' do - assert_equal RichText::Delta.new.insert("a", {:x=>1}), a.compose(x) + assert_equal RichText::Delta.new.insert('a', { x: 1 }), a.compose(x) end it 'insert + delete' do @@ -233,11 +231,11 @@ end it 'delete + insert' do - assert_equal RichText::Delta.new.insert("a").delete(1), d.compose(a) + assert_equal RichText::Delta.new.insert('a').delete(1), d.compose(a) end it 'delete + retain' do - assert_equal RichText::Delta.new.delete(1).retain(1, {:x=>1}), d.compose(x) + assert_equal RichText::Delta.new.delete(1).retain(1, { x: 1 }), d.compose(x) end it 'delete + delete' do @@ -245,11 +243,11 @@ end it 'retain + insert' do - assert_equal RichText::Delta.new.insert("a").retain(1, {:x=>1}), x.compose(a) + assert_equal RichText::Delta.new.insert('a').retain(1, { x: 1 }), x.compose(a) end it 'retain + retain' do - assert_equal RichText::Delta.new.retain(1, {:y=>1, :x=>1}), x.compose(y) + assert_equal RichText::Delta.new.retain(1, { y: 1, x: 1 }), x.compose(y) end it 'retain + delete' do diff --git a/test/unit/html_test.rb b/test/unit/html_test.rb index f4d9a7e..a2e7692 100644 --- a/test/unit/html_test.rb +++ b/test/unit/html_test.rb @@ -4,19 +4,19 @@ before do RichText.configure do |c| c.html_inline_formats = { - bold: { tag: 'strong' }, - br: { tag: 'br' }, - italic: { tag: 'em' }, - link: { tag: 'a', apply: ->(el, op, ctx){ el[:href] = op.attributes[:link] } } + bold: { tag: 'strong' }, + br: { tag: 'br' }, + italic: { tag: 'em' }, + link: { tag: 'a', apply: ->(el, op, _ctx) { el[:href] = op.attributes[:link] } } }.freeze c.html_block_formats = { - firstheader: { tag: 'h1' }, - secondheader: { tag: 'h2' }, - thirdheader: { tag: 'h3' }, - bullet: { tag: 'li', parent: 'ul' }, - list: { tag: 'li', parent: 'ol' }, - id: { apply: ->(el, op, ctx){ el[:id] = op.attributes[:id] } } + firstheader: { tag: 'h1' }, + secondheader: { tag: 'h2' }, + thirdheader: { tag: 'h3' }, + bullet: { tag: 'li', parent: 'ul' }, + list: { tag: 'li', parent: 'ol' }, + id: { apply: ->(el, op, _ctx) { el[:id] = op.attributes[:id] } } }.freeze c.html_default_block_format = 'p' @@ -35,148 +35,150 @@ it 'renders basic text with inline formats' do d = RichText::Delta.new([ - { insert: 'a man ' }, - { insert: 'a plan', attributes: { italic: true } }, - { insert: ' ' }, - { insert: 'panama', attributes: { bold: true } }, - { insert: "\n" }, - ]) + { insert: 'a man ' }, + { insert: 'a plan', attributes: { italic: true } }, + { insert: ' ' }, + { insert: 'panama', attributes: { bold: true } }, + { insert: "\n" } + ]) assert_equal '

a man a plan panama

', render_compact_html(d) end it 'renders inline formats with applied attributes' do d = RichText::Delta.new([ - { insert: 'link', attributes: { link: 'https://visitpanama.com' } }, - { insert: "\n" }, - ]) + { insert: 'link', attributes: { link: 'https://visitpanama.com' } }, + { insert: "\n" } + ]) assert_equal '

link

', render_compact_html(d) end it 'renders multiple level of inline attribution' do d = RichText::Delta.new([ - { insert: 'a man ' }, - { insert: 'a plan', attributes: { bold: true, italic: true, link: 'https://visitpanama.com' } }, - { insert: "\n" }, - ]) - assert_equal '

a man a plan

', render_compact_html(d) + { insert: 'a man ' }, + { insert: 'a plan', + attributes: { bold: true, italic: true, link: 'https://visitpanama.com' } }, + { insert: "\n" } + ]) + assert_equal '

a man a plan

', + render_compact_html(d) end it 'allows inline formatting options to override defaults' do d = RichText::Delta.new([ - { insert: 'a man ' }, - { insert: 'a plan', attributes: { bold: true } }, - { insert: "\n" }, - ]) + { insert: 'a man ' }, + { insert: 'a plan', attributes: { bold: true } }, + { insert: "\n" } + ]) assert_equal '

a man a plan

', render_compact_html(d, inline_formats: { - bold: { tag: 'b' } - }) + bold: { tag: 'b' } + }) end it 'renders blocks with parent elements' do d = RichText::Delta.new([ - { insert: 'a man' }, - { insert: "\n", attributes: { bullet: true } }, - { insert: 'a plan' }, - { insert: "\n", attributes: { bullet: true } }, - { insert: 'panama' }, - { insert: "\n", attributes: { bullet: true } } - ]) + { insert: 'a man' }, + { insert: "\n", attributes: { bullet: true } }, + { insert: 'a plan' }, + { insert: "\n", attributes: { bullet: true } }, + { insert: 'panama' }, + { insert: "\n", attributes: { bullet: true } } + ]) assert_equal '
  • a man
  • a plan
  • panama
', render_compact_html(d) end it 'renders properly merged parent sets' do d = RichText::Delta.new([ - { insert: 'helium' }, - { insert: "\n", attributes: { list: true } }, - { insert: 'neon' }, - { insert: "\n", attributes: { list: true } }, - { insert: 'argon' }, - { insert: "\n", attributes: { bullet: true } }, - { insert: 'krypton' }, - { insert: "\n", attributes: { bullet: true } } - ]) + { insert: 'helium' }, + { insert: "\n", attributes: { list: true } }, + { insert: 'neon' }, + { insert: "\n", attributes: { list: true } }, + { insert: 'argon' }, + { insert: "\n", attributes: { bullet: true } }, + { insert: 'krypton' }, + { insert: "\n", attributes: { bullet: true } } + ]) assert_equal '
  1. helium
  2. neon
  • argon
  • krypton
', render_compact_html(d) end it 'renders block formats' do d = RichText::Delta.new([ - { insert: 'a' }, - { insert: "\n", attributes: { firstheader: true } }, - { insert: 'b' }, - { insert: "\n", attributes: { secondheader: true } }, - { insert: 'c' }, - { insert: "\n", attributes: { thirdheader: true } } - ]) + { insert: 'a' }, + { insert: "\n", attributes: { firstheader: true } }, + { insert: 'b' }, + { insert: "\n", attributes: { secondheader: true } }, + { insert: 'c' }, + { insert: "\n", attributes: { thirdheader: true } } + ]) assert_equal '

a

b

c

', render_compact_html(d) end it 'renders inline breaks' do d = RichText::Delta.new([ - { insert: 'a man' }, - { insert: "\n", attributes: { br: true } }, - { insert: 'a plan' }, - { insert: "\n", attributes: { br: true } }, - { insert: 'panama' }, - { insert: "\n" } - ]) + { insert: 'a man' }, + { insert: "\n", attributes: { br: true } }, + { insert: 'a plan' }, + { insert: "\n", attributes: { br: true } }, + { insert: 'panama' }, + { insert: "\n" } + ]) assert_equal '

a man
a plan
panama

', render_compact_html(d) end it 'renders inline breaks at the end of the delta' do d = RichText::Delta.new([ - { insert: 'a man' }, - { insert: "\n", attributes: { br: true } }, - { insert: 'a plan' }, - { insert: "\n", attributes: { br: true } }, - { insert: 'panama' }, - { insert: "\n", attributes: { br: true } } - ]) + { insert: 'a man' }, + { insert: "\n", attributes: { br: true } }, + { insert: 'a plan' }, + { insert: "\n", attributes: { br: true } }, + { insert: 'panama' }, + { insert: "\n", attributes: { br: true } } + ]) assert_equal '

a man
a plan
panama

', render_compact_html(d) end it 'renders attributes' do d = RichText::Delta.new([ - { insert: 'mali principii' }, - { insert: "\n", attributes: { id: 'mali' } } - ]) + { insert: 'mali principii' }, + { insert: "\n", attributes: { id: 'mali' } } + ]) assert_equal '

mali principii

', render_compact_html(d) end it 'ignores invalid attributes' do d = RichText::Delta.new([ - { insert: 'malus finis', attributes: { invalid: true } }, - { insert: "\n", attributes: { invalid: true } } - ]) + { insert: 'malus finis', attributes: { invalid: true } }, + { insert: "\n", attributes: { invalid: true } } + ]) assert_equal '

malus finis

', render_compact_html(d) end it 'accepts an alternate block default referencing a defined format' do d = RichText::Delta.new([ - { insert: 'malus finis', attributes: { invalid: true } }, - { insert: "\n", attributes: { invalid: true } } - ]) + { insert: 'malus finis', attributes: { invalid: true } }, + { insert: "\n", attributes: { invalid: true } } + ]) assert_equal '

malus finis

', render_compact_html(d, default_block_format: :firstheader) end it 'accepts an alternate block default using a one-off tag name' do d = RichText::Delta.new([ - { insert: 'malus finis', attributes: { invalid: true } }, - { insert: "\n", attributes: { invalid: true } } - ]) + { insert: 'malus finis', attributes: { invalid: true } }, + { insert: "\n", attributes: { invalid: true } } + ]) assert_equal '
malus finis
', render_compact_html(d, default_block_format: 'div') end it 'renders custom object insertions using apply function' do d = RichText::Delta.new([ - { insert: { image: { src: "https://placekitten.com/200/150" } } }, - { insert: "\n" } - ]) + { insert: { image: { src: 'https://placekitten.com/200/150' } } }, + { insert: "\n" } + ]) f = { image: { tag: 'img', - apply: ->(el, op, ctx){ el[:src] = op.value.dig(:image, :src) } + apply: ->(el, op, _ctx) { el[:src] = op.value.dig(:image, :src) } } } @@ -185,49 +187,51 @@ it 'renders object insertions with special block formats, referencing a known format' do d = RichText::Delta.new([ - { insert: { image: { src: "https://placekitten.com/200/150" } } }, - { insert: "\n", attributes: { id: 'bingo' } } - ]) + { insert: { image: { src: 'https://placekitten.com/200/150' } } }, + { insert: "\n", attributes: { id: 'bingo' } } + ]) f = { image: { tag: 'img', block_format: :firstheader, - apply: ->(el, op, ctx){ el[:src] = op.value.dig(:image, :src) } + apply: ->(el, op, _ctx) { el[:src] = op.value.dig(:image, :src) } } } - assert_equal '

', render_compact_html(d, inline_formats: f) + assert_equal '

', + render_compact_html(d, inline_formats: f) end it 'renders custom object insertions with special block formats, using a custom tag' do d = RichText::Delta.new([ - { insert: { image: { src: "https://placekitten.com/200/150" } } }, - { insert: "\n", attributes: { id: 'bingo' } } - ]) + { insert: { image: { src: 'https://placekitten.com/200/150' } } }, + { insert: "\n", attributes: { id: 'bingo' } } + ]) f = { image: { tag: 'img', block_format: 'figure', - apply: ->(el, op, ctx){ el[:src] = op.value.dig(:image, :src) } + apply: ->(el, op, _ctx) { el[:src] = op.value.dig(:image, :src) } } } - assert_equal '
', render_compact_html(d, inline_formats: f) + assert_equal '
', + render_compact_html(d, inline_formats: f) end it 'renders custom object insertions with no block format' do d = RichText::Delta.new([ - { insert: { image: { src: "https://placekitten.com/200/150" } } }, - { insert: "\n", attributes: { id: 'bingo' } } - ]) + { insert: { image: { src: 'https://placekitten.com/200/150' } } }, + { insert: "\n", attributes: { id: 'bingo' } } + ]) f = { image: { tag: 'img', block_format: false, - apply: ->(el, op, ctx){ el[:src] = op.value.dig(:image, :src) } + apply: ->(el, op, _ctx) { el[:src] = op.value.dig(:image, :src) } } } @@ -236,64 +240,66 @@ it 'renders custom object insertions with a tag build function' do d = RichText::Delta.new([ - { insert: { image: { src: "https://placekitten.com/200/150", caption: 'cuteness' } } }, - { insert: "\n", attributes: { id: 'bingo' } } - ]) + { insert: { image: { src: 'https://placekitten.com/200/150', caption: 'cuteness' } } }, + { insert: "\n", attributes: { id: 'bingo' } } + ]) f = { image: { omit_block: true, - tag: ->(el, op, ctx) { + tag: lambda { |el, op, _ctx| el.name = 'figure' - el.add_child(%()) - el.add_child(%(
#{ op.value.dig(:image, :caption) }
)) + el.add_child(%()) + el.add_child(%(
#{op.value.dig(:image, :caption)}
)) el } } } - assert_equal '
cuteness
', render_compact_html(d, inline_formats: f) + assert_equal '
cuteness
', + render_compact_html(d, inline_formats: f) end it 'renders custom object insertions into story flow' do d = RichText::Delta.new([ - { insert: 'before' }, - { insert: "\n" }, - { insert: { image: { src: 'https://placekitten.com/200/150', caption: 'cuteness' } } }, - { insert: "\n", attributes: { id: 'bingo' } }, - { insert: 'after' }, - { insert: "\n" }, - ]) + { insert: 'before' }, + { insert: "\n" }, + { insert: { image: { src: 'https://placekitten.com/200/150', caption: 'cuteness' } } }, + { insert: "\n", attributes: { id: 'bingo' } }, + { insert: 'after' }, + { insert: "\n" } + ]) f = { image: { omit_block: true, - tag: ->(el, op, ctx) { + tag: lambda { |el, op, _ctx| el.name = 'figure' - el.add_child(%()) - el.add_child(%(
#{ op.value.dig(:image, :caption) }
)) + el.add_child(%()) + el.add_child(%(
#{op.value.dig(:image, :caption)}
)) el } } } - assert_equal '

before

cuteness

after

', render_compact_html(d, inline_formats: f) + assert_equal '

before

cuteness

after

', + render_compact_html(d, inline_formats: f) end it 'renders nothing for build functions that return no element' do d = RichText::Delta.new([ - { insert: 'before' }, - { insert: "\n" }, - { insert: { image: { src: 'https://placekitten.com/200/150', caption: 'cuteness' } } }, - { insert: "\n", attributes: { id: 'bingo' } }, - { insert: 'after' }, - { insert: "\n" }, - ]) + { insert: 'before' }, + { insert: "\n" }, + { insert: { image: { src: 'https://placekitten.com/200/150', caption: 'cuteness' } } }, + { insert: "\n", attributes: { id: 'bingo' } }, + { insert: 'after' }, + { insert: "\n" } + ]) f = { image: { omit_block: true, - tag: ->(el, op, ctx) { nil } + tag: ->(_el, _op, _ctx) { nil } } } @@ -302,9 +308,9 @@ it 'passes a render context argument to build and apply functions' do d = RichText::Delta.new([ - { insert: { image: { src: 'https://placekitten.com/200/150' } } }, - { insert: "\n" }, - ]) + { insert: { image: { src: 'https://placekitten.com/200/150' } } }, + { insert: "\n" } + ]) render_context = {} build_context = nil @@ -312,17 +318,23 @@ f = { image: { - tag: ->(el, op, ctx) { build_context = ctx; el }, - apply: ->(el, op, ctx) { apply_context = ctx } + tag: lambda { |el, _op, ctx| + build_context = ctx + el + }, + apply: ->(_el, _op, ctx) { apply_context = ctx } } } render_compact_html(d, context: render_context, inline_formats: { - image: { - tag: ->(el, op, ctx) { build_context = ctx; el }, - apply: ->(el, op, ctx) { apply_context = ctx } - } - }) + image: { + tag: lambda { |el, _op, ctx| + build_context = ctx + el + }, + apply: ->(_el, _op, ctx) { apply_context = ctx } + } + }) assert_equal render_context.object_id, build_context.object_id assert_equal render_context.object_id, apply_context.object_id @@ -330,27 +342,35 @@ it 'applies tags then attributes, each ordered by priority' do d = RichText::Delta.new([ - { insert: "hitme\n", attributes: { attr2: true, tag2: true, attr1: true, tag1: true } } - ]) + { insert: "hitme\n", attributes: { attr2: true, tag2: true, attr1: true, tag1: true } } + ]) assert_equal '

hitme

', render_compact_html(d, inline_formats: { - tag1: { tag: 'first', priority: 1 }, - tag2: { tag: 'second', priority: 2 }, - attr1: { apply: ->(el, op, ctx){ el[:id] = 'first' }, priority: 1 }, - attr2: { apply: ->(el, op, ctx){ el[:id] = 'second' }, priority: 2 }, - }) + tag1: { + tag: 'first', priority: 1 + }, + tag2: { + tag: 'second', priority: 2 + }, + attr1: { apply: lambda { |el, _op, _ctx| + el[:id] = 'first' + }, priority: 1 }, + attr2: { apply: lambda { |el, _op, _ctx| + el[:id] = 'second' + }, priority: 2 } + }) end it 'gracefully handles missing newline ends' do d = RichText::Delta.new([ - { insert: 'mali principii' }, - { insert: "\n" }, - { insert: 'malus finis', attributes: { invalid: true } } - ]) + { insert: 'mali principii' }, + { insert: "\n" }, + { insert: 'malus finis', attributes: { invalid: true } } + ]) assert_equal '

mali principii

malus finis

', render_compact_html(d) end - def render_compact_html(delta, options={}) + def render_compact_html(delta, options = {}) RichText::HTML.new(options).render(delta).inner_html(save_with: 0) end -end \ No newline at end of file +end diff --git a/test/unit/iterator_test.rb b/test/unit/iterator_test.rb index 600a23a..aa6af3f 100644 --- a/test/unit/iterator_test.rb +++ b/test/unit/iterator_test.rb @@ -3,11 +3,11 @@ describe RichText::Iterator do subject do RichText::Iterator.new([ - RichText::Op.new(:insert, 'abc'), - RichText::Op.new(:retain, 3, { test: true }), - RichText::Op.new(:delete, 3), - RichText::Op.new(:insert, 'def') - ]) + RichText::Op.new(:insert, 'abc'), + RichText::Op.new(:retain, 3, { test: true }), + RichText::Op.new(:delete, 3), + RichText::Op.new(:insert, 'def') + ]) end describe 'peek' do diff --git a/test/unit/op_test.rb b/test/unit/op_test.rb index 59454e0..9e5395c 100644 --- a/test/unit/op_test.rb +++ b/test/unit/op_test.rb @@ -136,14 +136,14 @@ describe 'to_h' do it 'omits attributes when not present' do - assert_equal ({ :insert => 'abc' }), RichText::Op.new(:insert, 'abc').to_h + assert_equal ({ insert: 'abc' }), RichText::Op.new(:insert, 'abc').to_h end it 'includes attributes when present' do assert_equal({ - :insert => 'abc', - :attributes => { :foo => true } - }, RichText::Op.new(:insert, 'abc', { foo: true }).to_h) + insert: 'abc', + attributes: { foo: true } + }, RichText::Op.new(:insert, 'abc', { foo: true }).to_h) end end @@ -157,7 +157,7 @@ it 'omits class name when flag is passed' do assert_equal( - "retain=4 {:x=>1}", + 'retain=4 {:x=>1}', RichText::Op.new(:retain, 4, { x: 1 }).inspect(false) ) end From 553803391761bbc47ae4c4880488e95dd5e8d4c7 Mon Sep 17 00:00:00 2001 From: Fdebijl Date: Mon, 11 Mar 2024 18:01:49 +0100 Subject: [PATCH 3/5] Update to modern FbF standards --- .editorconfig | 9 ++ .github/workflows/tests.yml | 19 ++--- .rubocop.yml | 166 ++++++++++++++++++++++++++++++++++++ .ruby-version | 1 + Gemfile | 1 - bin/bundle | 109 +++++++++++++++++++++++ bin/console | 6 +- bin/htmldiff | 27 ++++++ bin/ldiff | 27 ++++++ bin/nokogiri | 27 ++++++ bin/racc | 27 ++++++ bin/rake | 14 ++- bin/yard | 27 ++++++ bin/yardoc | 27 ++++++ bin/yri | 27 ++++++ lib/rich-text/version.rb | 2 +- rich-text.gemspec | 12 +-- test/unit/delta_test.rb | 3 - 18 files changed, 499 insertions(+), 32 deletions(-) create mode 100644 .editorconfig create mode 100644 .rubocop.yml create mode 100644 .ruby-version create mode 100755 bin/bundle create mode 100755 bin/htmldiff create mode 100755 bin/ldiff create mode 100755 bin/nokogiri create mode 100755 bin/racc create mode 100755 bin/yard create mode 100755 bin/yardoc create mode 100755 bin/yri diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3c0eb46 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 +charset=utf-8 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a33a04a..4c9d32b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,20 +1,19 @@ name: tests + on: - push + jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - ruby: ['2.x'] steps: - - name: check out source - uses: actions/checkout@v2 - - name: set up ruby - uses: actions/setup-ruby@v1 + - name: Checkout source + uses: actions/checkout@v4 + - name: Setup Ruby + uses: ruby/setup-ruby@v1 with: - ruby-version: ${{ matrix.ruby }} - - name: build and test + bundler-cache: true + - name: Build and test run: | gem install bundler bundle install --jobs 4 --retry 3 @@ -23,7 +22,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: | mkdir ~/.gem echo ":github: Bearer ${{ secrets.GITHUB_TOKEN }}" >> ~/.gem/credentials diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..2993003 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,166 @@ +AllCops: + NewCops: enable + TargetRubyVersion: 3.3.0 + +Bundler/OrderedGems: + Enabled: false + +Layout/ArgumentAlignment: + Enabled: false + EnforcedStyle: with_fixed_indentation + +Layout/BlockEndNewline: + Enabled: false + +Layout/DefEndAlignment: + EnforcedStyleAlignWith: 'start_of_line' + +Layout/ElseAlignment: + Enabled: false + +Layout/EndAlignment: + EnforcedStyleAlignWith: 'start_of_line' + +Layout/FirstArgumentIndentation: + EnforcedStyle: 'consistent' + +Layout/FirstArrayElementIndentation: + EnforcedStyle: 'consistent' + +Layout/FirstHashElementIndentation: + EnforcedStyle: 'consistent' + +Layout/HashAlignment: + Exclude: + - 'lib/tasks/auto_annotate_models.rake' + +Layout/IndentationWidth: + Enabled: false + +Layout/LineLength: + Enabled: false + +Layout/MultilineBlockLayout: + Enabled: false + +Layout/MultilineMethodCallBraceLayout: + Enabled: false + +Layout/MultilineMethodCallIndentation: + Enabled: false + +Layout/ParameterAlignment: + EnforcedStyle: 'with_first_parameter' + +Layout/RescueEnsureAlignment: + Enabled: false + +Lint/AmbiguousBlockAssociation: + Enabled: false + +Lint/ShadowingOuterLocalVariable: + Enabled: false + +Lint/SuppressedException: + Exclude: + - 'db/**/*' + +Lint/UnusedMethodArgument: + AllowUnusedKeywordArguments: true + +Metrics/AbcSize: + Max: 66 + Exclude: + - '**/db/**/*' + +Metrics/BlockLength: + Enabled: false + +Metrics/ClassLength: + Enabled: false + +Metrics/CyclomaticComplexity: + Max: 15 + +Metrics/MethodLength: + Enabled: false + +Metrics/ModuleLength: + Enabled: false + +Metrics/ParameterLists: + Max: 6 + +Metrics/PerceivedComplexity: + Max: 15 + +Naming/VariableNumber: + CheckSymbols: false + +Naming/BlockForwarding: + Enabled: false + +Style/Alias: + EnforcedStyle: 'prefer_alias_method' + +Style/NumericLiterals: + AllowedPatterns: + - \d{4}_\d{2}_\d{2}_\d{6} + +Style/Documentation: + Enabled: false + +Style/EmptyMethod: + Enabled: false + +Style/FrozenStringLiteralComment: + Enabled: false + +Style/HashAsLastArrayItem: + EnforcedStyle: no_braces + +Style/IfInsideElse: + Enabled: false + +Style/IfUnlessModifier: + Enabled: false + +Style/Lambda: + EnforcedStyle: 'literal' + +Style/MixinUsage: + Exclude: + - 'bin/*' + +Style/MultilineBlockChain: + Enabled: false + +Style/MutableConstant: + Enabled: false + +Style/OpenStructUse: + Enabled: false + +Style/PercentLiteralDelimiters: + Enabled: false + +Style/PerlBackrefs: + Enabled: false + +Style/RegexpLiteral: + AllowInnerSlashes: true + +Style/RescueModifier: + Enabled: false + +Style/RescueStandardError: + Enabled: false + +Style/SymbolArray: + EnforcedStyle: 'brackets' + +Style/WordArray: + Enabled: false + +Style/HashSyntax: + Enabled: false diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..15a2799 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.3.0 diff --git a/Gemfile b/Gemfile index 1795cc9..fa75df1 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,3 @@ source 'https://rubygems.org' -# Specify your gem's dependencies in rich-text.gemspec gemspec diff --git a/bin/bundle b/bin/bundle new file mode 100755 index 0000000..50da5fd --- /dev/null +++ b/bin/bundle @@ -0,0 +1,109 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN) + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || + cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + bundler_gem_version.approximate_recommendation + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/bin/console b/bin/console index 994a555..51b2752 100755 --- a/bin/console +++ b/bin/console @@ -1,7 +1,7 @@ #!/usr/bin/env ruby -require 'bundler/setup' -require 'rich-text' +require "bundler/setup" +require "rich-text" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. @@ -10,5 +10,5 @@ require 'rich-text' # require "pry" # Pry.start -require 'irb' +require "irb" IRB.start diff --git a/bin/htmldiff b/bin/htmldiff new file mode 100755 index 0000000..0aeaec8 --- /dev/null +++ b/bin/htmldiff @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'htmldiff' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("diff-lcs", "htmldiff") diff --git a/bin/ldiff b/bin/ldiff new file mode 100755 index 0000000..8173ede --- /dev/null +++ b/bin/ldiff @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'ldiff' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("diff-lcs", "ldiff") diff --git a/bin/nokogiri b/bin/nokogiri new file mode 100755 index 0000000..c00ec26 --- /dev/null +++ b/bin/nokogiri @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'nokogiri' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("nokogiri", "nokogiri") diff --git a/bin/racc b/bin/racc new file mode 100755 index 0000000..8190015 --- /dev/null +++ b/bin/racc @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'racc' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("racc", "racc") diff --git a/bin/rake b/bin/rake index 5aa8950..4eb7d7b 100755 --- a/bin/rake +++ b/bin/rake @@ -8,14 +8,12 @@ # this file is here to facilitate running it. # -require 'pathname' -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', - Pathname.new(__FILE__).realpath) +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) -bundle_binstub = File.expand_path('bundle', __dir__) +bundle_binstub = File.expand_path("bundle", __dir__) if File.file?(bundle_binstub) - if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/ + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") load(bundle_binstub) else abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. @@ -23,7 +21,7 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this end end -require 'rubygems' -require 'bundler/setup' +require "rubygems" +require "bundler/setup" -load Gem.bin_path('rake', 'rake') +load Gem.bin_path("rake", "rake") diff --git a/bin/yard b/bin/yard new file mode 100755 index 0000000..ea9daf5 --- /dev/null +++ b/bin/yard @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'yard' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("yard", "yard") diff --git a/bin/yardoc b/bin/yardoc new file mode 100755 index 0000000..e1324dc --- /dev/null +++ b/bin/yardoc @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'yardoc' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("yard", "yardoc") diff --git a/bin/yri b/bin/yri new file mode 100755 index 0000000..f968fde --- /dev/null +++ b/bin/yri @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'yri' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("yard", "yri") diff --git a/lib/rich-text/version.rb b/lib/rich-text/version.rb index 94e9bca..8e1cecd 100644 --- a/lib/rich-text/version.rb +++ b/lib/rich-text/version.rb @@ -1,3 +1,3 @@ module RichText - VERSION = '0.4.0' + VERSION = '0.4.0'.freeze end diff --git a/rich-text.gemspec b/rich-text.gemspec index 75e0616..f13a4f1 100644 --- a/rich-text.gemspec +++ b/rich-text.gemspec @@ -19,14 +19,14 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.required_ruby_version = '>= 2.7.0' + spec.required_ruby_version = '>= 3.3.0' - spec.add_dependency 'activesupport', '>= 3.0.0' - spec.add_dependency 'diff-lcs', '~> 1.5.1' - spec.add_dependency 'nokogiri', '>= 1.0.0' + spec.add_dependency 'activesupport', '>= 7.1.3' + spec.add_dependency 'diff-lcs', '>= 1.5.1' + spec.add_dependency 'nokogiri', '>= 1.16.2' spec.add_development_dependency 'bundler' - spec.add_development_dependency 'minitest', '~> 5.0' - spec.add_development_dependency 'rake', '~> 10.0' + spec.add_development_dependency 'minitest' + spec.add_development_dependency 'rake' spec.add_development_dependency 'yard' end diff --git a/test/unit/delta_test.rb b/test/unit/delta_test.rb index eff35ee..f1bdcac 100644 --- a/test/unit/delta_test.rb +++ b/test/unit/delta_test.rb @@ -208,9 +208,6 @@ # end # end - describe '=~' do - end - describe 'compose' do let(:a) { RichText::Delta.new.insert('a') } let(:b) { RichText::Delta.new.insert('b') } From 8488e07af5f95d6deea5b5545fd143aaf04ffe31 Mon Sep 17 00:00:00 2001 From: Fdebijl Date: Mon, 11 Mar 2024 18:03:49 +0100 Subject: [PATCH 4/5] Fix version bounds --- rich-text.gemspec | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rich-text.gemspec b/rich-text.gemspec index f13a4f1..531d503 100644 --- a/rich-text.gemspec +++ b/rich-text.gemspec @@ -21,12 +21,12 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.3.0' - spec.add_dependency 'activesupport', '>= 7.1.3' - spec.add_dependency 'diff-lcs', '>= 1.5.1' - spec.add_dependency 'nokogiri', '>= 1.16.2' + spec.add_dependency 'activesupport', '~> 7.1' + spec.add_dependency 'diff-lcs', '~> 1.5' + spec.add_dependency 'nokogiri', '~> 1.16' - spec.add_development_dependency 'bundler' - spec.add_development_dependency 'minitest' - spec.add_development_dependency 'rake' - spec.add_development_dependency 'yard' + spec.add_development_dependency 'bundler', '~> 2.5' + spec.add_development_dependency 'minitest', '~> 5.22' + spec.add_development_dependency 'rake', '~> 13.1' + spec.add_development_dependency 'yard', '~> 0.9' end From 596baefcdd21cd58488c0337249ee3740fa5181e Mon Sep 17 00:00:00 2001 From: Fdebijl Date: Tue, 12 Mar 2024 11:37:11 +0100 Subject: [PATCH 5/5] Bump version --- lib/rich-text/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rich-text/version.rb b/lib/rich-text/version.rb index 8e1cecd..418a6f2 100644 --- a/lib/rich-text/version.rb +++ b/lib/rich-text/version.rb @@ -1,3 +1,3 @@ module RichText - VERSION = '0.4.0'.freeze + VERSION = '0.5.0'.freeze end