Skip to content

Commit 38b70ae

Browse files
authored
Merge pull request #2 from jdee/patch-class
Patch class
2 parents 7872731 + 1b0f507 commit 38b70ae

File tree

6 files changed

+211
-36
lines changed

6 files changed

+211
-36
lines changed

README.md

Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,45 +11,30 @@ This is a very preliminary utility gem to apply and revert patches to strings (t
1111
of the main intended use cases for this plugin is source-code modification, e.g.
1212
when automatically integrating an SDK.
1313

14-
The current API is low-level, having evolved out of a Fastlane plugin helper. A better data model
15-
will probably arise in later releases.
16-
1714
Please provide any feedback via issues in this repo.
1815

1916
```Ruby
2017
require "pattern_patch"
2118

2219
# Add a meta-data key to the end of the application element of an Android manifest
23-
modified = File.open("AndroidManifest.xml") do |file|
24-
PatternPatch::Utilities.apply_patch file.read,
25-
%r{^\s*</application>},
26-
" <meta-data android:name=\"foo\" android:value=\"bar\" />\n",
27-
false,
28-
:prepend,
29-
0
30-
end
31-
32-
File.open("AndroidManifest.xml", "w") { |f| f.write modified }
20+
PatternPatch::Patch.new(
21+
regexp: %r{^\s*</application>},
22+
text: " <meta-data android:name=\"foo\" android:value=\"bar\" />\n",
23+
mode: :prepend
24+
).apply "AndroidManifest.xml"
3325
```
3426

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

3931
```Ruby
40-
require "pattern_patch"
41-
4232
# Change the name of a pod in a podspec
43-
modified = File.open("AndroidManifest.xml") do |file|
44-
PatternPatch::Utilities.apply_patch file.read,
45-
/(s\.name\s*=\s*)"MyPod"/,
46-
'\1"MyOtherPod"',
47-
false,
48-
:replace,
49-
0
50-
end
51-
52-
File.open("AndroidManifest.xml", "w") { |f| f.write modified }
33+
PatternPatch::Patch.new(
34+
regexp: /(s\.name\s*=\s*)"MyPod"/,
35+
text: '\1"MyOtherPod"',
36+
mode: :replace
37+
).apply "MyPod.podspec"
5338
```
5439

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

6247
```Ruby
6348
# Revert the patch that added the metadata key to the end of the Android manifest, resulting in the original.
64-
modified = File.open("AndroidManifest.xml") do |file|
65-
PatternPatch::Utilities.revert_patch file.read,
66-
%r{^\s*</application>},
67-
" <meta-data android:name=\"foo\" android:value=\"bar\" />\n",
68-
false,
69-
:prepend,
70-
0
71-
end
72-
73-
File.open("AndroidManifest.xml", "w") { |f| f.write modified }
49+
PatternPatch::Patch.new(
50+
regexp: %r{^\s*</application>},
51+
text: " <meta-data android:name=\"foo\" android:value=\"bar\" />\n",
52+
mode: :prepend
53+
).apply "AndroidManifest.xml"
7454
```
7555

7656
Patches using the `:replace` mode cannot be reverted.
57+
58+
#### Define patches in YAML files
59+
60+
Load a patch defined in YAML and apply it.
61+
62+
```Ruby
63+
PatternPatch::Patch.from_yaml("patch.yaml").apply "file.txt"
64+
```

lib/pattern_patch.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
require "pattern_patch/core_ext"
2+
require "pattern_patch/patch"
23
require "pattern_patch/utilities"
34
require "pattern_patch/version"

lib/pattern_patch/patch.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
require "active_support/core_ext/hash"
2+
require "yaml"
3+
4+
module PatternPatch
5+
class Patch
6+
attr_accessor :regexp
7+
attr_accessor :text
8+
attr_accessor :mode
9+
attr_accessor :global
10+
11+
class << self
12+
def from_yaml(path)
13+
hash = YAML.load_file(path).symbolize_keys
14+
15+
# Adjust string fields from YAML
16+
17+
if hash[:regexp].kind_of? String
18+
hash[:regexp] = /#{hash[:regexp]}/
19+
end
20+
21+
if hash[:mode].kind_of? String
22+
hash[:mode] = hash[:mode].to_sym
23+
end
24+
25+
new hash
26+
end
27+
end
28+
29+
def initialize(options = {})
30+
@regexp = options[:regexp]
31+
@text = options[:text]
32+
@mode = options[:mode] || :append
33+
@global = options[:global].nil? ? false : options[:global]
34+
end
35+
36+
def apply(files, options = {})
37+
offset = options[:offset] || 0
38+
files = [files] if files.kind_of? String
39+
40+
files.each do |path|
41+
modified = Utilities.apply_patch File.read(path),
42+
regexp,
43+
text,
44+
global,
45+
mode,
46+
offset
47+
File.write path, modified
48+
end
49+
end
50+
51+
def revert(files, options = {})
52+
offset = options[:offset] || 0
53+
files = [files] if files.kind_of? String
54+
55+
files.each do |path|
56+
modified = Utilities.revert_patch File.read(path),
57+
regexp,
58+
text,
59+
global,
60+
mode,
61+
offset
62+
File.write path, modified
63+
end
64+
end
65+
66+
def inspect
67+
"#<PatternPatch::Patch regexp=#{regexp.inspect} text=#{text.inspect} mode=#{mode.inspect} global=#{global.inspect}>"
68+
end
69+
end
70+
end

lib/pattern_patch/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module PatternPatch
2-
VERSION = "0.1.2"
2+
VERSION = "0.2.0"
33
end

pattern_patch.gemspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Gem::Specification.new do |spec|
1515
spec.homepage = 'http://github.com/jdee/pattern_patch'
1616
spec.license = 'MIT'
1717

18+
spec.add_dependency 'activesupport'
19+
1820
spec.add_development_dependency 'bundler'
1921
spec.add_development_dependency 'pry'
2022
spec.add_development_dependency 'rake'

spec/patch_spec.rb

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
describe PatternPatch::Patch do
2+
describe 'initialization' do
3+
it 'initializes parameters from options' do
4+
patch = PatternPatch::Patch.new regexp: //, text: '', mode: :prepend, global: true
5+
expect(patch.regexp).to eq(//)
6+
expect(patch.text).to eq ''
7+
expect(patch.mode).to eq :prepend
8+
expect(patch.global).to be true
9+
end
10+
11+
it 'initializes to default values when no options passed' do
12+
patch = PatternPatch::Patch.new
13+
expect(patch.regexp).to be_nil
14+
expect(patch.text).to be_nil
15+
expect(patch.mode).to eq :append
16+
expect(patch.global).to be false
17+
end
18+
end
19+
20+
describe '#inspect' do
21+
it 'includes the value of each field' do
22+
text = PatternPatch::Patch.new.inspect
23+
expect(text).to match(/regexp=/)
24+
expect(text).to match(/text=/)
25+
expect(text).to match(/mode=/)
26+
expect(text).to match(/global=/)
27+
end
28+
end
29+
30+
describe '#apply' do
31+
it 'passes field values to the Utilities#apply_patch method' do
32+
patch = PatternPatch::Patch.new regexp: /x/, text: 'y'
33+
expect(File).to receive(:read).with('file.txt') { 'x' }
34+
35+
expect(PatternPatch::Utilities).to receive(:apply_patch).with(
36+
'x',
37+
patch.regexp,
38+
patch.text,
39+
patch.global,
40+
patch.mode, 0
41+
) { 'xy' }
42+
43+
expect(File).to receive(:write).with('file.txt', 'xy')
44+
45+
patch.apply 'file.txt'
46+
end
47+
48+
it 'passes the offset option if present' do
49+
patch = PatternPatch::Patch.new regexp: /x/, text: 'y'
50+
expect(File).to receive(:read).with('file.txt') { 'x' }
51+
52+
expect(PatternPatch::Utilities).to receive(:apply_patch).with(
53+
'x',
54+
patch.regexp,
55+
patch.text,
56+
patch.global,
57+
patch.mode,
58+
1
59+
) { 'x' }
60+
61+
expect(File).to receive(:write).with('file.txt', 'x')
62+
63+
patch.apply 'file.txt', offset: 1
64+
end
65+
end
66+
67+
describe '#revert' do
68+
it 'passes field values to the Utilities#revert_patch method' do
69+
patch = PatternPatch::Patch.new regexp: /x/, text: 'y'
70+
expect(File).to receive(:read).with('file.txt') { 'xy' }
71+
72+
expect(PatternPatch::Utilities).to receive(:revert_patch).with(
73+
'xy',
74+
patch.regexp,
75+
patch.text,
76+
patch.global,
77+
patch.mode, 0
78+
) { 'x' }
79+
80+
expect(File).to receive(:write).with('file.txt', 'x')
81+
82+
patch.revert 'file.txt'
83+
end
84+
85+
it 'passes the offset option if present' do
86+
patch = PatternPatch::Patch.new regexp: /x/, text: 'y'
87+
expect(File).to receive(:read).with('file.txt') { 'x' }
88+
89+
expect(PatternPatch::Utilities).to receive(:revert_patch).with(
90+
'x',
91+
patch.regexp,
92+
patch.text,
93+
patch.global,
94+
patch.mode,
95+
1
96+
) { 'x' }
97+
98+
expect(File).to receive(:write).with('file.txt', 'x')
99+
100+
patch.revert 'file.txt', offset: 1
101+
end
102+
end
103+
104+
describe '::from_yaml' do
105+
it 'loads a patch from a YAML file' do
106+
expect(YAML).to receive(:load_file).with('file.yml') { { regexp: 'x', text: 'y', mode: 'prepend', global: true } }
107+
patch = PatternPatch::Patch.from_yaml 'file.yml'
108+
expect(patch.regexp).to eq(/x/)
109+
expect(patch.text).to eq 'y'
110+
expect(patch.mode).to eq :prepend
111+
expect(patch.global).to be true
112+
end
113+
end
114+
end

0 commit comments

Comments
 (0)