├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── lib ├── sub_diff.rb └── sub_diff │ ├── adapter.rb │ ├── buildable.rb │ ├── builder.rb │ ├── collection.rb │ ├── core_ext │ └── string.rb │ ├── diff.rb │ ├── differ.rb │ ├── gsub.rb │ ├── sub.rb │ └── version.rb ├── spec ├── spec_helper.rb ├── sub_diff │ ├── collection_spec.rb │ └── diff_spec.rb └── sub_diff_spec.rb └── sub_diff.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | rdoc -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | addons: 2 | code_climate: 3 | repo_token: 6f133fcc28caad1e1099287a1ac4ee0ac4fb428cdd9c5cb9424be1110ef8704a 4 | 5 | install: bundle install --jobs 3 --retry 3 6 | 7 | rvm: 8 | - 2.0.0 9 | - ruby-head 10 | 11 | script: bundle exec rspec 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://www.rubygems.org' 2 | 3 | gemspec 4 | 5 | gem 'codeclimate-test-reporter' 6 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | sub_diff (1.1.1) 5 | 6 | GEM 7 | remote: https://www.rubygems.org/ 8 | specs: 9 | codeclimate-test-reporter (0.4.7) 10 | simplecov (>= 0.7.1, < 1.0.0) 11 | diff-lcs (1.2.5) 12 | docile (1.1.5) 13 | multi_json (1.11.0) 14 | rspec (3.2.0) 15 | rspec-core (~> 3.2.0) 16 | rspec-expectations (~> 3.2.0) 17 | rspec-mocks (~> 3.2.0) 18 | rspec-core (3.2.2) 19 | rspec-support (~> 3.2.0) 20 | rspec-expectations (3.2.0) 21 | diff-lcs (>= 1.2.0, < 2.0) 22 | rspec-support (~> 3.2.0) 23 | rspec-mocks (3.2.1) 24 | diff-lcs (>= 1.2.0, < 2.0) 25 | rspec-support (~> 3.2.0) 26 | rspec-support (3.2.2) 27 | simplecov (0.9.2) 28 | docile (~> 1.1.0) 29 | multi_json (~> 1.0) 30 | simplecov-html (~> 0.9.0) 31 | simplecov-html (0.9.0) 32 | 33 | PLATFORMS 34 | ruby 35 | 36 | DEPENDENCIES 37 | codeclimate-test-reporter 38 | rspec 39 | sub_diff! 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2015 Sean Huber - github@shuber.io 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [![Sean Huber](https://cloud.githubusercontent.com/assets/2419/6550752/832d9a64-c5ea-11e4-9717-6f9aa6e023b5.png)](https://github.com/shuber) sub_diff 2 | 3 | [![Build Status](https://secure.travis-ci.org/shuber/sub_diff.svg)](http://travis-ci.org/shuber/sub_diff) [![Code Climate](https://codeclimate.com/github/shuber/sub_diff/badges/gpa.svg)](https://codeclimate.com/github/shuber/sub_diff) [![Coverage](https://codeclimate.com/github/shuber/sub_diff/badges/coverage.svg)](https://codeclimate.com/github/shuber/sub_diff) [![Gem Version](https://badge.fury.io/rb/sub_diff.svg)](http://badge.fury.io/rb/sub_diff) 4 | 5 | Inspect the changes of your [`String#sub`] and [`String#gsub`] replacements. 6 | 7 | [`String#sub`]: http://ruby-doc.org//core-2.2.0/String.html#method-i-sub 8 | [`String#gsub`]: http://ruby-doc.org//core-2.2.0/String.html#method-i-gsub 9 | 10 | ## Installation 11 | 12 | ``` 13 | gem install sub_diff 14 | ``` 15 | 16 | 17 | ## Requirements 18 | 19 | Ruby 2.0+ 20 | 21 | For older Ruby versions (1.8+), use the [1.0.7](https://github.com/shuber/sub_diff/tree/1.0.7) tag. 22 | 23 | 24 | ## Usage 25 | 26 | This gem introduces a couple new methods to [`String`](http://ruby-doc.org/core-2.2.0/String.html) objects. 27 | 28 | * [`String#sub_diff`](http://ruby-doc.org/core-2.2.0/String.html#method-i-sub) 29 | * [`String#gsub_diff`](http://ruby-doc.org/core-2.2.0/String.html#method-i-gsub) 30 | 31 | These methods accept the same arguments as their `sub` and `gsub` counterparts. 32 | 33 | ```ruby 34 | replaced = 'this is a test'.gsub_diff(/(\S*is)/, 'replaced(\1)') #=> # 35 | ``` 36 | 37 | The difference is that it returns a `SubDiff::Collection` instead. This object behaves like a `String`. 38 | 39 | ```ruby 40 | puts replaced #=> "replaced(this) replaced(is) a test" 41 | ``` 42 | 43 | But it also allows us to check if the replacement actually *changed* anything. 44 | 45 | ```ruby 46 | replaced.changed? #=> true 47 | ``` 48 | 49 | For a closer look at the changes, we can iterate thru each `Diff` in the replacment. 50 | 51 | ```ruby 52 | replaced.each do |diff| 53 | puts diff.inspect 54 | end 55 | 56 | #=> "replaced(this)" 57 | #=> " " 58 | #=> "replaced(is)" 59 | #=> " a test" 60 | ``` 61 | 62 | Each `Diff` object behaves just like a string, but also includes a few additional methods. 63 | 64 | ```ruby 65 | replaced.each do |diff| 66 | puts " value: #{diff.value.inspect}" 67 | puts "value_was: #{diff.value_was.inspect}" 68 | puts " changed?: #{diff.changed?}" 69 | end 70 | 71 | #=> value: "replaced(this)" 72 | #=> value_was: "this" 73 | #=> changed?: true 74 | 75 | #=> value: " " 76 | #=> value_was: " " 77 | #=> changed?: false 78 | 79 | #=> value: "replaced(is)" 80 | #=> value_was: "is" 81 | #=> changed?: true 82 | 83 | #=> value: " a test" 84 | #=> value_was: " a test" 85 | #=> changed?: false 86 | ``` 87 | 88 | 89 | ## API 90 | 91 | [YARD Documentation](http://www.rubydoc.info/github/shuber/sub_diff) 92 | 93 | * `String#sub_diff` 94 | * `String#gsub_diff` 95 | * `SubDiff::Diff#changed?` 96 | * `SubDiff::Diff#value` 97 | * `SubDiff::Diff#value_was` 98 | * `SubDiff::Collection#changed?` 99 | * `SubDiff::Collection#clear` 100 | * `SubDiff::Collection#diffs` 101 | * `SubDiff::Collection#each` 102 | * `SubDiff::Collection#reset` 103 | * `SubDiff::Collection#size` 104 | 105 | ## Testing 106 | 107 | ``` 108 | bundle exec rspec 109 | ``` 110 | 111 | 112 | ## Contributing 113 | 114 | * Fork the project. 115 | * Make your feature addition or bug fix. 116 | * Add tests for it. This is important so I don't break it in a future version unintentionally. 117 | * Commit, do not mess with the version or history. 118 | * Send me a pull request. Bonus points for topic branches. 119 | 120 | 121 | ## License 122 | 123 | [MIT](https://github.com/shuber/sub_diff/blob/master/LICENSE) - Copyright © 2011-2015 Sean Huber 124 | -------------------------------------------------------------------------------- /lib/sub_diff.rb: -------------------------------------------------------------------------------- 1 | require 'delegate' 2 | require 'forwardable' 3 | require 'sub_diff/buildable' 4 | require 'sub_diff/adapter' 5 | require 'sub_diff/builder' 6 | require 'sub_diff/collection' 7 | require 'sub_diff/diff' 8 | require 'sub_diff/differ' 9 | require 'sub_diff/sub' 10 | require 'sub_diff/gsub' 11 | require 'sub_diff/version' 12 | require 'sub_diff/core_ext/string' 13 | 14 | String.send(:include, SubDiff::CoreExt::String) 15 | -------------------------------------------------------------------------------- /lib/sub_diff/adapter.rb: -------------------------------------------------------------------------------- 1 | module SubDiff 2 | # Constructs an instance of {Sub} or {Gsub} to be 3 | # used as a receiver for delegated calls to `diff`. 4 | # 5 | # Used internally by {Builder}. 6 | # 7 | # @api private 8 | class Adapter 9 | include Buildable 10 | 11 | def_delegators :adapter, :diff 12 | 13 | private 14 | 15 | def adapter 16 | adapter_class.new(builder) 17 | end 18 | 19 | def adapter_class 20 | Module.nesting.last.const_get(adapter_name) 21 | end 22 | 23 | def adapter_name 24 | diff_method.to_s.capitalize 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/sub_diff/buildable.rb: -------------------------------------------------------------------------------- 1 | module SubDiff 2 | # This module allows classes to accept a {Builder} object as 3 | # an initializer argument and defines an `attr_reader` for it. 4 | # 5 | # It also delegates commonly used methods to the {Builder} instance. 6 | # 7 | # Used internally by {Adapter}, {Differ}, and {Sub}. 8 | # 9 | # @api private 10 | module Buildable 11 | attr_reader :builder 12 | 13 | def initialize(builder) 14 | @builder = builder 15 | end 16 | 17 | def self.included(base) 18 | base.extend(Forwardable) 19 | base.def_delegators(:builder, :diff_method, :differ, :string) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/sub_diff/builder.rb: -------------------------------------------------------------------------------- 1 | module SubDiff 2 | # Performs a {Sub} or {Gsub} replacement and returns 3 | # the resulting {Collection} of {Diff} objects. 4 | # 5 | # Used internally by {CoreExt::String#sub_diff} and {CoreExt::String#gsub_diff}. 6 | # 7 | # @api private 8 | class Builder 9 | attr_reader :string, :diff_method 10 | 11 | def initialize(string, diff_method) 12 | @string = string 13 | @diff_method = diff_method 14 | end 15 | 16 | def diff(*args, &block) 17 | build_diff_collection do 18 | adapter.diff(*args, &block) 19 | end 20 | end 21 | 22 | def push(*args) 23 | if args.compact.any? 24 | diff = Diff.new(*args) 25 | collection.push(diff) 26 | end 27 | end 28 | alias_method :<<, :push 29 | 30 | private 31 | 32 | def build_diff_collection(&block) 33 | collection.reset(&block).dup 34 | end 35 | 36 | def collection 37 | @collection ||= Collection.new(string) 38 | end 39 | 40 | def adapter 41 | @adapter ||= Adapter.new(self) 42 | end 43 | 44 | def differ 45 | @differ ||= Differ.new(self) 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/sub_diff/collection.rb: -------------------------------------------------------------------------------- 1 | module SubDiff 2 | # Stores a collection of {Diff} objects for all matches from 3 | # a {String#sub_diff} or {String#gsub_diff} replacement. 4 | # 5 | # It behaves like a {String} that represents the entire 6 | # replacement result from {String#sub} or {String#gsub}. 7 | # 8 | # It also behaves like an {Enumerable} by delegating to 9 | # {Collection#diffs} - an {Array} containing each {Diff}. 10 | # 11 | # @api public 12 | class Collection < SimpleDelegator 13 | extend Forwardable 14 | include Enumerable 15 | 16 | def_delegators :diffs, :each, :size 17 | 18 | attr_reader :string, :diffs 19 | 20 | def initialize(string) 21 | @string = string 22 | @diffs = [] 23 | super(string) 24 | end 25 | 26 | def changed? 27 | diffs.any?(&:changed?) 28 | end 29 | 30 | def clear 31 | diffs.clear 32 | __setobj__('') 33 | self 34 | end 35 | 36 | def push(diff) 37 | unless diff.empty? 38 | diffs << diff 39 | __setobj__(diffs.join) 40 | end 41 | end 42 | 43 | def reset 44 | clear 45 | __setobj__(string) 46 | yield if block_given? 47 | self 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/sub_diff/core_ext/string.rb: -------------------------------------------------------------------------------- 1 | module SubDiff 2 | module CoreExt 3 | module String 4 | # Behaves just like {String#sub} but wraps the returned replacement 5 | # string in an enumerable {Collection} of {Diff} objects. 6 | # 7 | # See http://ruby-doc.org/core-2.2.0/String.html#method-i-sub 8 | def sub_diff(*args, &block) 9 | Builder.new(self, :sub).diff(*args, &block) 10 | end 11 | 12 | # Behaves just like {String#gsub} but wraps the returned replacement 13 | # string in an enumerable {Collection} of {Diff} objects. 14 | # 15 | # See http://ruby-doc.org/core-2.2.0/String.html#method-i-gsub 16 | def gsub_diff(*args, &block) 17 | Builder.new(self, :gsub).diff(*args, &block) 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/sub_diff/diff.rb: -------------------------------------------------------------------------------- 1 | module SubDiff 2 | # Stores a single match (and optional replacement) 3 | # from a {String#sub} or {String#gsub} replacement. 4 | # 5 | # It behaves just like a {String} and represents the 6 | # newly replaced string if a replacement was made, or 7 | # the matched string itself if no changes occurred. 8 | # 9 | # It also has additional methods that provide access 10 | # to the old string, the newly replaced string, and a 11 | # boolean to determine if a replacement was actually made. 12 | # 13 | # @api public 14 | class Diff < SimpleDelegator 15 | attr_reader :value_was 16 | 17 | alias_method :value, :__getobj__ 18 | 19 | def initialize(value, value_was = nil) 20 | @value_was = value_was || value 21 | super(value) 22 | end 23 | 24 | def changed? 25 | value != value_was 26 | end 27 | 28 | def empty? 29 | value.empty? && !changed? 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/sub_diff/differ.rb: -------------------------------------------------------------------------------- 1 | module SubDiff 2 | # Performs a {String#sub} or {String#gsub} replacement 3 | # while yielding each match "diff payload" to a block. 4 | # 5 | # The payload contains: 6 | # 7 | # match - the string matching the search. 8 | # prefix - the string preceding the match. 9 | # suffix - the string trailing the match. 10 | # replacement - the string replacing the match. 11 | # 12 | # This class uses some special global variables: $` and $'. 13 | # See http://ruby-doc.org/core-2.2.0/doc/globals_rdoc.html 14 | # 15 | # Used internally by {Sub}. 16 | # 17 | # @api private 18 | class Differ 19 | include Buildable 20 | 21 | def match(search, *args, replacement) 22 | string.send(diff_method, search) do |match| 23 | diff = { match: match, prefix: $`, suffix: $' } 24 | diff[:replacement] = match.sub(search, *args, &replacement) 25 | yield(diff) 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/sub_diff/gsub.rb: -------------------------------------------------------------------------------- 1 | module SubDiff 2 | # Processes matches for {String#gsub} replacements 3 | # by pushing diffs into a {Builder} instance. 4 | # 5 | # Used internally by {Adapter}. 6 | # 7 | # @api private 8 | class Gsub < Sub 9 | private 10 | 11 | def append_diff_to_builder(diff, _search) 12 | super 13 | last_prefix << prefix(diff) << diff[:match] 14 | end 15 | 16 | def last_prefix 17 | @last_prefix ||= '' 18 | end 19 | 20 | def prefix(_diff) 21 | super.sub(last_prefix, '') 22 | end 23 | 24 | def suffix(_diff, search) 25 | suffix = super 26 | regex = Regexp.new(search) 27 | suffix unless suffix.match(regex) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/sub_diff/sub.rb: -------------------------------------------------------------------------------- 1 | module SubDiff 2 | # Processes matches for {String#gsub} replacements 3 | # by pushing diffs into a {Builder} instance. 4 | # 5 | # Used internally by {Adapter}. 6 | # 7 | # @api private 8 | class Sub 9 | include Buildable 10 | 11 | def diff(search, *args, &block) 12 | differ.match(search, *args, block) do |diff| 13 | append_diff_to_builder(diff, search) 14 | end 15 | end 16 | 17 | private 18 | 19 | def append_diff_to_builder(diff, search) 20 | builder << prefix(diff) 21 | builder.push(diff[:replacement], diff[:match]) 22 | builder << suffix(diff, search) 23 | end 24 | 25 | def prefix(diff) 26 | diff[:prefix] 27 | end 28 | 29 | def suffix(diff, _search) 30 | diff[:suffix] 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/sub_diff/version.rb: -------------------------------------------------------------------------------- 1 | module SubDiff 2 | VERSION = '1.1.1' 3 | end 4 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | if ENV['CODECLIMATE_REPO_TOKEN'] 2 | require 'codeclimate-test-reporter' 3 | CodeClimate::TestReporter.start 4 | else 5 | require 'simplecov' 6 | SimpleCov.start { add_filter('/vendor/bundle/') } 7 | end 8 | 9 | require File.expand_path('../../lib/sub_diff', __FILE__) 10 | 11 | RSpec.configure do |config| 12 | config.filter_run :focus 13 | config.raise_errors_for_deprecations! 14 | config.run_all_when_everything_filtered = true 15 | end 16 | -------------------------------------------------------------------------------- /spec/sub_diff/collection_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe SubDiff::Collection do 2 | subject { described_class.new(diffable) } 3 | 4 | let(:diffable) { 'this is a simple test' } 5 | 6 | describe '#changed?' do 7 | it 'should return true if any diffs have changed' do 8 | diff = double('diff', changed?: true, empty?: false) 9 | subject.push(diff) 10 | expect(subject).to be_changed 11 | end 12 | 13 | it 'should return false if no diffs have changed' do 14 | diff = double('diff', changed?: false, empty?: false) 15 | subject.push(diff) 16 | expect(subject).not_to be_changed 17 | end 18 | 19 | it 'should return false if there are no diffs' do 20 | expect(subject).not_to be_changed 21 | end 22 | end 23 | 24 | describe '#clear' do 25 | before { subject.push('example') } 26 | 27 | let(:block) { proc { subject.clear } } 28 | 29 | it 'should clear diffs' do 30 | expect(block).to change(subject, :size) 31 | expect(subject.diffs).to be_empty 32 | end 33 | 34 | it 'should clear string' do 35 | expect(block).to change(subject, :to_s) 36 | expect(subject.to_s).to eq('') 37 | end 38 | 39 | it 'should return the instance itself' do 40 | expect(block.call.object_id).to eq(subject.object_id) 41 | end 42 | end 43 | 44 | describe '#each' do 45 | it { is_expected.to be_an(Enumerable) } 46 | end 47 | 48 | describe '#push' do 49 | it 'should append a diff' do 50 | diff = double('diff', empty?: false) 51 | block = proc { subject.push(diff) } 52 | expect(block).to change(subject.diffs, :size) 53 | end 54 | 55 | it 'should not append an empty diff' do 56 | diff = double('diff', empty?: true) 57 | block = proc { subject.push(diff) } 58 | expect(block).not_to change(subject.diffs, :size) 59 | end 60 | end 61 | 62 | describe '#reset' do 63 | before { subject.push('example') } 64 | 65 | let(:block) { proc { subject.reset } } 66 | 67 | it 'should empty diffs' do 68 | expect(block).to change(subject, :size) 69 | expect(subject.diffs).to be_empty 70 | end 71 | 72 | it 'should reset string' do 73 | expect(block).to change(subject, :to_s) 74 | expect(subject.to_s).to eq(diffable) 75 | end 76 | 77 | it 'should yield if a block is given' do 78 | local = 'test' 79 | block = proc { local = 'changed' } 80 | subject.reset(&block) 81 | expect(local).to eq('changed') 82 | end 83 | 84 | it 'should return the instance itself' do 85 | expect(block.call.object_id).to eq(subject.object_id) 86 | end 87 | end 88 | 89 | describe '#size' do 90 | it 'should use the diffs size' do 91 | expect(subject.size).to eq(subject.diffs.size) 92 | end 93 | 94 | it 'should not use the string size' do 95 | expect(subject.size).not_to eq(subject.to_s.size) 96 | end 97 | end 98 | 99 | describe '#__getobj__' do 100 | it 'should join the diff values if any' do 101 | subject.push('test') 102 | subject.push('example') 103 | expect(subject.__getobj__).to eq('testexample') 104 | end 105 | 106 | it 'should return the diffable if diffs are empty' do 107 | expect(subject.__getobj__).to eq(diffable) 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /spec/sub_diff/diff_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe SubDiff::Diff do 2 | let(:diff) { described_class.new(value, value_was) } 3 | let(:diff_without_value_was) { described_class.new(value) } 4 | let(:diff_with_empty_values) { described_class.new('', '') } 5 | 6 | let(:value) { 'test' } 7 | let(:value_was) { 'testing' } 8 | 9 | describe '#changed?' do 10 | it 'should return true if value does not match value_was' do 11 | expect(diff).to be_changed 12 | end 13 | 14 | it 'should return false if value matches value_was' do 15 | expect(diff_without_value_was).not_to be_changed 16 | expect(diff_with_empty_values).not_to be_changed 17 | end 18 | end 19 | 20 | describe '#empty?' do 21 | it 'should return false if the diff was changed' do 22 | expect(diff).not_to be_empty 23 | end 24 | 25 | it 'should return false if the diff value is not empty' do 26 | expect(diff_without_value_was).not_to be_empty 27 | end 28 | 29 | it 'should return true if the diff was not changed and is empty' do 30 | expect(diff_with_empty_values).to be_empty 31 | end 32 | end 33 | 34 | describe '#value' do 35 | it 'should return the diff value' do 36 | expect(diff.value).to eq(value) 37 | end 38 | end 39 | 40 | describe '#value_was' do 41 | it 'should accept value_was' do 42 | expect(diff.value_was).to eq(value_was) 43 | end 44 | 45 | it 'should set value_was to value if it is not specified' do 46 | expect(diff_without_value_was.value_was).to eq(value) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/sub_diff_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe SubDiff do 2 | subject { 'this is a simple test' } 3 | 4 | describe '#sub_diff' do 5 | context 'when called multiple times with the same arguments' do 6 | it 'should return a new object' do 7 | first = subject.sub_diff('simple', 'very simple') 8 | second = subject.sub_diff('simple', 'very simple') 9 | expect(second.object_id).not_to eq(first.object_id) 10 | end 11 | end 12 | 13 | context 'with a string' do 14 | it 'should process arguments correctly' do 15 | result = subject.sub_diff('simple', 'very simple') 16 | expect(result.to_s).to eq('this is a very simple test') 17 | expect(result.size).to eq(3) 18 | end 19 | 20 | it 'should handle no matches correctly' do 21 | result = subject.sub_diff('no-match', 'test') 22 | expect(result.to_s).to eq(subject) 23 | expect(result.size).to eq(0) 24 | end 25 | end 26 | 27 | context 'with a regex' do 28 | it 'should process arguments correctly' do 29 | result = subject.sub_diff(/a \S+/, 'an easy') 30 | expect(result.to_s).to eq('this is an easy test') 31 | expect(result.size).to eq(3) 32 | end 33 | 34 | it 'should handle captures correctly' do 35 | result = subject.sub_diff(/a (\S+)/, 'a very \1') 36 | expect(result.to_s).to eq('this is a very simple test') 37 | expect(result.size).to eq(3) 38 | end 39 | end 40 | 41 | context 'with a block' do 42 | it 'should process arguments correctly' do 43 | result = subject.sub_diff('simple') { |match| 'block' } 44 | expect(result.to_s).to eq('this is a block test') 45 | expect(result.size).to eq(3) 46 | end 47 | end 48 | 49 | context 'with a hash' do 50 | it 'should process arguments correctly' do 51 | result = subject.sub_diff(/simple/, 'simple' => 'harder') 52 | expect(result.to_s).to eq('this is a harder test') 53 | expect(result.size).to eq(3) 54 | end 55 | end 56 | end 57 | 58 | describe '#gsub_diff' do 59 | context 'when called multiple times with the same arguments' do 60 | it 'should return a new object' do 61 | first = subject.gsub_diff('i', 'x') 62 | second = subject.gsub_diff('x', 'x') 63 | expect(second.object_id).not_to eq(first.object_id) 64 | end 65 | end 66 | 67 | context 'with a string' do 68 | it 'should process arguments correctly' do 69 | result = subject.gsub_diff('i', 'x') 70 | expect(result.to_s).to eq('thxs xs a sxmple test') 71 | expect(result.size).to eq(7) 72 | end 73 | 74 | it 'should handle no matches correctly' do 75 | result = subject.gsub_diff('no-match', 'test') 76 | expect(result.to_s).to eq(subject) 77 | expect(result.size).to eq(0) 78 | end 79 | end 80 | 81 | context 'with a regex' do 82 | it 'should process arguments correctly' do 83 | result = subject.gsub_diff(/(\S*is)/, 'X') 84 | expect(result.to_s).to eq('X X a simple test') 85 | expect(result.size).to eq(4) 86 | end 87 | 88 | it 'should handle captures correctly' do 89 | result = subject.gsub_diff(/(\S*is)/, 'X(\1)') 90 | expect(result.to_s).to eq('X(this) X(is) a simple test') 91 | expect(result.size).to eq(4) 92 | end 93 | end 94 | 95 | context 'with a block' do 96 | it 'should process arguments correctly' do 97 | result = subject.gsub_diff('i') { |match| 'x' } 98 | expect(result.to_s).to eq('thxs xs a sxmple test') 99 | expect(result.size).to eq(7) 100 | end 101 | end 102 | 103 | context 'with a hash' do 104 | it 'should process arguments correctly' do 105 | result = subject.gsub_diff(/(\S*is)/, 'is' => 'IS', 'this' => 'THIS') 106 | expect(result.to_s).to eq('THIS IS a simple test') 107 | expect(result.size).to eq(4) 108 | end 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /sub_diff.gemspec: -------------------------------------------------------------------------------- 1 | require File.expand_path('../lib/sub_diff/version', __FILE__) 2 | 3 | Gem::Specification.new do |s| 4 | s.author = 'Sean Huber' 5 | s.email = 'github@shuber.io' 6 | s.extra_rdoc_files = %w(LICENSE) 7 | s.files = `git ls-files`.split("\n") 8 | s.homepage = 'https://github.com/shuber/sub_diff' 9 | s.license = 'MIT' 10 | s.name = 'sub_diff' 11 | s.rdoc_options = %w(--charset=UTF-8 --inline-source --line-numbers --main README.md) 12 | s.require_paths = %w(lib) 13 | s.required_ruby_version = '>= 2.0.0' 14 | s.summary = 'Inspect the changes of your `String#sub` and `String#gsub` replacements' 15 | s.test_files = `git ls-files -- spec/*`.split("\n") 16 | s.version = SubDiff::VERSION 17 | 18 | s.add_development_dependency 'rspec' 19 | end 20 | --------------------------------------------------------------------------------