Skip to content

Commit

Permalink
Merge pull request #2 from jdee/patch-class
Browse files Browse the repository at this point in the history
Patch class
  • Loading branch information
jdee authored Nov 6, 2017
2 parents 7872731 + 1b0f507 commit 38b70ae
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 36 deletions.
58 changes: 23 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,30 @@ This is a very preliminary utility gem to apply and revert patches to strings (t
of the main intended use cases for this plugin is source-code modification, e.g.
when automatically integrating an SDK.

The current API is low-level, having evolved out of a Fastlane plugin helper. A better data model
will probably arise in later releases.

Please provide any feedback via issues in this repo.

```Ruby
require "pattern_patch"

# Add a meta-data key to the end of the application element of an Android manifest
modified = File.open("AndroidManifest.xml") do |file|
PatternPatch::Utilities.apply_patch file.read,
%r{^\s*</application>},
" <meta-data android:name=\"foo\" android:value=\"bar\" />\n",
false,
:prepend,
0
end

File.open("AndroidManifest.xml", "w") { |f| f.write modified }
PatternPatch::Patch.new(
regexp: %r{^\s*</application>},
text: " <meta-data android:name=\"foo\" android:value=\"bar\" />\n",
mode: :prepend
).apply "AndroidManifest.xml"
```

Capture groups may be used within the text argument in any mode. Note that
this works best without interpolation (single quotes or %q). If you use double
quotes, the backslash must be escaped, e.g. `text: "\\1\"MyOtherpod\""`.

```Ruby
require "pattern_patch"

# Change the name of a pod in a podspec
modified = File.open("AndroidManifest.xml") do |file|
PatternPatch::Utilities.apply_patch file.read,
/(s\.name\s*=\s*)"MyPod"/,
'\1"MyOtherPod"',
false,
:replace,
0
end

File.open("AndroidManifest.xml", "w") { |f| f.write modified }
PatternPatch::Patch.new(
regexp: /(s\.name\s*=\s*)"MyPod"/,
text: '\1"MyOtherPod"',
mode: :replace
).apply "MyPod.podspec"
```

Patches in `:append` mode using capture groups in the text argument may be
Expand All @@ -61,16 +46,19 @@ Revert patches by passing the optional `:revert` parameter:

```Ruby
# Revert the patch that added the metadata key to the end of the Android manifest, resulting in the original.
modified = File.open("AndroidManifest.xml") do |file|
PatternPatch::Utilities.revert_patch file.read,
%r{^\s*</application>},
" <meta-data android:name=\"foo\" android:value=\"bar\" />\n",
false,
:prepend,
0
end

File.open("AndroidManifest.xml", "w") { |f| f.write modified }
PatternPatch::Patch.new(
regexp: %r{^\s*</application>},
text: " <meta-data android:name=\"foo\" android:value=\"bar\" />\n",
mode: :prepend
).apply "AndroidManifest.xml"
```

Patches using the `:replace` mode cannot be reverted.

#### Define patches in YAML files

Load a patch defined in YAML and apply it.

```Ruby
PatternPatch::Patch.from_yaml("patch.yaml").apply "file.txt"
```
1 change: 1 addition & 0 deletions lib/pattern_patch.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require "pattern_patch/core_ext"
require "pattern_patch/patch"
require "pattern_patch/utilities"
require "pattern_patch/version"
70 changes: 70 additions & 0 deletions lib/pattern_patch/patch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
require "active_support/core_ext/hash"
require "yaml"

module PatternPatch
class Patch
attr_accessor :regexp
attr_accessor :text
attr_accessor :mode
attr_accessor :global

class << self
def from_yaml(path)
hash = YAML.load_file(path).symbolize_keys

# Adjust string fields from YAML

if hash[:regexp].kind_of? String
hash[:regexp] = /#{hash[:regexp]}/
end

if hash[:mode].kind_of? String
hash[:mode] = hash[:mode].to_sym
end

new hash
end
end

def initialize(options = {})
@regexp = options[:regexp]
@text = options[:text]
@mode = options[:mode] || :append
@global = options[:global].nil? ? false : options[:global]
end

def apply(files, options = {})
offset = options[:offset] || 0
files = [files] if files.kind_of? String

files.each do |path|
modified = Utilities.apply_patch File.read(path),
regexp,
text,
global,
mode,
offset
File.write path, modified
end
end

def revert(files, options = {})
offset = options[:offset] || 0
files = [files] if files.kind_of? String

files.each do |path|
modified = Utilities.revert_patch File.read(path),
regexp,
text,
global,
mode,
offset
File.write path, modified
end
end

def inspect
"#<PatternPatch::Patch regexp=#{regexp.inspect} text=#{text.inspect} mode=#{mode.inspect} global=#{global.inspect}>"
end
end
end
2 changes: 1 addition & 1 deletion lib/pattern_patch/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module PatternPatch
VERSION = "0.1.2"
VERSION = "0.2.0"
end
2 changes: 2 additions & 0 deletions pattern_patch.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Gem::Specification.new do |spec|
spec.homepage = 'http://github.com/jdee/pattern_patch'
spec.license = 'MIT'

spec.add_dependency 'activesupport'

spec.add_development_dependency 'bundler'
spec.add_development_dependency 'pry'
spec.add_development_dependency 'rake'
Expand Down
114 changes: 114 additions & 0 deletions spec/patch_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
describe PatternPatch::Patch do
describe 'initialization' do
it 'initializes parameters from options' do
patch = PatternPatch::Patch.new regexp: //, text: '', mode: :prepend, global: true
expect(patch.regexp).to eq(//)
expect(patch.text).to eq ''
expect(patch.mode).to eq :prepend
expect(patch.global).to be true
end

it 'initializes to default values when no options passed' do
patch = PatternPatch::Patch.new
expect(patch.regexp).to be_nil
expect(patch.text).to be_nil
expect(patch.mode).to eq :append
expect(patch.global).to be false
end
end

describe '#inspect' do
it 'includes the value of each field' do
text = PatternPatch::Patch.new.inspect
expect(text).to match(/regexp=/)
expect(text).to match(/text=/)
expect(text).to match(/mode=/)
expect(text).to match(/global=/)
end
end

describe '#apply' do
it 'passes field values to the Utilities#apply_patch method' do
patch = PatternPatch::Patch.new regexp: /x/, text: 'y'
expect(File).to receive(:read).with('file.txt') { 'x' }

expect(PatternPatch::Utilities).to receive(:apply_patch).with(
'x',
patch.regexp,
patch.text,
patch.global,
patch.mode, 0
) { 'xy' }

expect(File).to receive(:write).with('file.txt', 'xy')

patch.apply 'file.txt'
end

it 'passes the offset option if present' do
patch = PatternPatch::Patch.new regexp: /x/, text: 'y'
expect(File).to receive(:read).with('file.txt') { 'x' }

expect(PatternPatch::Utilities).to receive(:apply_patch).with(
'x',
patch.regexp,
patch.text,
patch.global,
patch.mode,
1
) { 'x' }

expect(File).to receive(:write).with('file.txt', 'x')

patch.apply 'file.txt', offset: 1
end
end

describe '#revert' do
it 'passes field values to the Utilities#revert_patch method' do
patch = PatternPatch::Patch.new regexp: /x/, text: 'y'
expect(File).to receive(:read).with('file.txt') { 'xy' }

expect(PatternPatch::Utilities).to receive(:revert_patch).with(
'xy',
patch.regexp,
patch.text,
patch.global,
patch.mode, 0
) { 'x' }

expect(File).to receive(:write).with('file.txt', 'x')

patch.revert 'file.txt'
end

it 'passes the offset option if present' do
patch = PatternPatch::Patch.new regexp: /x/, text: 'y'
expect(File).to receive(:read).with('file.txt') { 'x' }

expect(PatternPatch::Utilities).to receive(:revert_patch).with(
'x',
patch.regexp,
patch.text,
patch.global,
patch.mode,
1
) { 'x' }

expect(File).to receive(:write).with('file.txt', 'x')

patch.revert 'file.txt', offset: 1
end
end

describe '::from_yaml' do
it 'loads a patch from a YAML file' do
expect(YAML).to receive(:load_file).with('file.yml') { { regexp: 'x', text: 'y', mode: 'prepend', global: true } }
patch = PatternPatch::Patch.from_yaml 'file.yml'
expect(patch.regexp).to eq(/x/)
expect(patch.text).to eq 'y'
expect(patch.mode).to eq :prepend
expect(patch.global).to be true
end
end
end

0 comments on commit 38b70ae

Please sign in to comment.