cuketagger 1.5 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: aca5d51f29ef842db370f4ebcfdf6637c1b04d84
4
+ data.tar.gz: 8a654ad26ef3eb1f9d516d192061c12a9c1c2ad5
5
+ SHA512:
6
+ metadata.gz: 455c33a05e17c3ade2dbcec02fab34d1a04152580ce2bd3fbed451eee5908ae1b0d637824e261bda2d3953e1de3719875cf7fe52a8f061cd43aae53a17c28f76
7
+ data.tar.gz: 06be308f0f01bf9cf1be6b076e0594e5d13b1afaa436e865262effcd447f547e02e35e6a8ad6a8e024e32bf2614de76e5ae7e7b6a49cb8b7830fa88a53bbbefb
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.idea/
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1.6
7
+ - 2.2.2
8
+
9
+ # todo - Remove this once TravisCI fixes their bundler issue (https://github.com/alphagov/govuk_template/pull/186)
10
+ before_install:
11
+ - gem install bundler -v 1.9.10
12
+
13
+ script: bundle exec rake cuketagger:ci_build
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cuketagger.gemspec
4
+ gemspec
5
+
6
+ if RUBY_VERSION =~ /^1\.8/
7
+ gem 'cucumber', '<1.3.0'
8
+ gem 'gherkin', '<2.12.0'
9
+ gem 'rake', '< 11.0' # Rake dropped 1.8.x support after this version
10
+ elsif RUBY_VERSION =~ /^1\./
11
+ gem 'cucumber', '<2.0.0'
12
+ end
13
+
14
+ if RUBY_VERSION =~ /^1\./
15
+ gem 'tins', '< 1.7' # The 'tins' gem requires Ruby 2.x on/after this version
16
+ gem 'json', '< 2.0' # The 'json' gem drops pre-Ruby 2.x support on/after this version
17
+ gem 'term-ansicolor', '< 1.4' # The 'term-ansicolor' gem requires Ruby 2.x on/after this version
18
+ end
@@ -0,0 +1,11 @@
1
+ ### Version 1.6.0 / 2016-12-11
2
+
3
+ * Updated the dependencies used by the gem in order to be compatible with current
4
+ libraries.
5
+ * File modification has been minimized. Only the lines of a file that are affected
6
+ by tagging are modified. Previously, the entire file was potentially reformatted.
7
+
8
+
9
+ ### Version 1.5.0 / 2011-04-17
10
+
11
+ * The Great Before Times...
@@ -0,0 +1,23 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2009-2012 Jari Bakken
4
+ Copyright (c) 2015 Eric Kessler
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,8 +1,60 @@
1
-
2
- <pre><code>
3
- $ sudo gem install jarib-cuketagger
4
- $ cuketagger add:wip features/foo.feature:6 features/bar.feature
5
- // check that the output is as expected
6
- $ cuketagger --force add:wip features/foo.feature:6 features/bar.feature
7
- // --force rewrites your files
8
- </code></pre>
1
+ [![Gem Version](https://badge.fury.io/rb/cuketagger.svg)](https://rubygems.org/gems/cuketagger)
2
+ [![Build Status](https://travis-ci.org/enkessler/cuketagger.svg?branch=dev)](https://travis-ci.org/enkessler/cuketagger)
3
+ [![Dependency Status](https://gemnasium.com/enkessler/cuketagger.svg)](https://gemnasium.com/enkessler/cuketagger)
4
+
5
+
6
+ # Cuketagger
7
+
8
+ This gem provides the ability to manipulate, in bulk, the tags in a Cucumber test
9
+ suite.
10
+
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ gem 'cuketagger'
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install cuketagger
25
+
26
+
27
+ ## Usage
28
+
29
+ Tags can be added,
30
+
31
+ $ cuketagger add:foo path/to/some.feature
32
+
33
+ removed,
34
+
35
+ $ cuketagger remove:foo path/to/some.feature
36
+
37
+ or replaced.
38
+
39
+ $ cuketagger replace:old_tag:new_tag path/to/some.feature
40
+
41
+ Multiple tags and files can be manipulated at the same time
42
+
43
+ $ cuketagger remove:wip add:release5 replace:qa:prod features/foo.feature:6 features/bar.feature
44
+
45
+ and the modified file content will be output to the console. To modify the files
46
+ themselves, add the explicit `force` tag.
47
+
48
+ $ cuketagger --force remove:wip add:release5 replace:qa:prod features/foo.feature:6 features/bar.feature
49
+
50
+
51
+ ## Contributing
52
+
53
+ 1. Fork it
54
+ 2. Create your feature branch (off of the development branch)
55
+ `git checkout -b my-new-feature`
56
+ 3. Commit your changes
57
+ `git commit -am 'Add some feature'`
58
+ 4. Push to the branch
59
+ `git push origin my-new-feature`
60
+ 5. Create new Pull Request
data/Rakefile CHANGED
@@ -1,55 +1,21 @@
1
- # encoding: utf-8
2
-
3
- require "rake/clean"
4
- require "rake/gempackagetask"
5
- require File.expand_path("../lib/cuketagger/version", __FILE__)
6
- require "cucumber/rake/task"
7
- CLEAN.include %w[pkg]
8
-
9
- GEM_NAME = "cuketagger"
10
- GEM_VERSION = CukeTagger::Version
11
-
12
- spec = Gem::Specification.new do |s|
13
- s.name = GEM_NAME
14
- s.version = GEM_VERSION
15
- s.has_rdoc = false
16
- s.summary = "batch tagging of cucumber features and scenarios"
17
- s.description = s.summary
18
- s.authors = %w[Jari Bakken]
19
- s.email = "jari.bakken@gmail.com"
20
- s.homepage = "http://cukes.info"
21
- s.files = %w[Rakefile README.markdown] + Dir['lib/**/*']
22
- s.bindir = 'bin'
23
- s.executables = Dir['bin/*'].map { |f| File.basename(f) }
24
-
25
- s.add_runtime_dependency 'cucumber', '>= 0.9.2'
26
- end
27
-
28
- Rake::GemPackageTask.new(spec) do |pkg|
29
- pkg.gem_spec = spec
30
- end
31
-
32
- namespace :gem do
33
- desc "install the gem locally"
34
- task :install => [:package] do
35
- sh %{sudo #{Gem.ruby} -S gem install pkg/#{GEM_NAME}-#{GEM_VERSION}}
36
- end
37
-
38
- desc "Create a .gemspec file"
39
- task :spec do
40
- file = "#{GEM_NAME.downcase}.gemspec"
41
- File.unlink file if ::File.exists?(file)
42
- File.open(file, "w+") { |f| f << spec.to_ruby }
43
- end
44
-
45
- desc "Release cuketagger-#{GEM_VERSION}"
46
- task :release => [:clean, :gem] do
47
- sh "gem push pkg/#{GEM_NAME}-#{GEM_VERSION}.gem"
48
- end
49
- end
50
-
51
- Cucumber::Rake::Task.new(:features) do |t|
52
- t.cucumber_opts = "--format pretty"
53
- end
54
-
55
- task :default => :features
1
+ require 'racatt'
2
+
3
+
4
+ namespace 'cuketagger' do
5
+ Racatt.create_tasks
6
+
7
+ # The task that CI will use
8
+ task :ci_build => [:smart_test]
9
+
10
+
11
+ task :smart_test do |t, args|
12
+ rspec_args = '--pattern testing/rspec/spec/**/*_spec.rb'
13
+ cucumber_args = 'testing/cucumber/features -r testing/cucumber/features -f progress'
14
+
15
+ Rake::Task['cuketagger:test_everything'].invoke(rspec_args, cucumber_args)
16
+ end
17
+
18
+ end
19
+
20
+
21
+ task :default => 'cuketagger:smart_test'
@@ -1,5 +1,5 @@
1
- #!/usr/bin/env ruby
2
- # encoding: utf-8
3
-
4
- require File.expand_path("../../lib/cuketagger", __FILE__)
5
- CukeTagger::Tagger.execute(ARGV)
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require File.expand_path("../../lib/cuketagger", __FILE__)
5
+ CukeTagger::Tagger.execute(ARGV)
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cuketagger/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cuketagger"
8
+ spec.version = CukeTagger::VERSION
9
+ spec.authors = ["Jari Bakken", "Eric Kessler"]
10
+ spec.email = ["morrow748@gmail.com"]
11
+
12
+ # todo - Update summary and description
13
+ spec.summary = "batch tagging of cucumber features and scenarios"
14
+ # spec.description = %q{TODO: Write a longer description or delete this line.}
15
+ spec.homepage = "https://github.com/jarib/cuketagger"
16
+ spec.license = "MIT"
17
+
18
+ spec.files = `git ls-files -z`.split("\x0")
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_runtime_dependency 'cuke_modeler', '~>1.0'
24
+ spec.add_runtime_dependency 'cql', '~>1.0', '>= 1.3.0'
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.5"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "racatt", '~> 1.0'
29
+ spec.add_development_dependency 'rspec', '~> 3.0'
30
+ spec.add_development_dependency 'cucumber', '< 3.0.0'
31
+ end
@@ -1,18 +1,13 @@
1
- $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
-
3
- require "set"
4
-
5
- require "gherkin"
6
- require "gherkin/formatter/pretty_formatter"
7
- require "stringio"
8
-
9
- require "cuketagger/version"
10
- require "cuketagger/tag_formatter"
11
- require "cuketagger/tagger"
12
- require "cuketagger/array_list_extension" if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
13
-
14
- module CukeTagger
15
- def self.log(*args)
16
- File.open("/tmp/cuketagger.log", "a") { |file| file.puts args.inspect } if $DEBUG
17
- end
18
- end
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ require "set"
4
+ require 'cuke_modeler'
5
+ require 'cql'
6
+ require 'cql/model_dsl'
7
+
8
+ require "cuketagger/version"
9
+ require "cuketagger/tagger"
10
+ require "cuketagger/array_list_extension" if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
11
+
12
+ module CukeTagger
13
+ end
@@ -1,13 +1,13 @@
1
- require 'java'
2
-
3
- class Java::JavaUtil::ArrayList
4
- alias_method :delete, :remove
5
- alias_method :push, :add
6
-
7
- def index(obj)
8
- idx = indexOf(obj)
9
- return if idx == -1
10
- idx
11
- end
12
- end
13
-
1
+ require 'java'
2
+
3
+ class Java::JavaUtil::ArrayList
4
+ alias_method :delete, :remove
5
+ alias_method :push, :add
6
+
7
+ def index(obj)
8
+ idx = indexOf(obj)
9
+ return if idx == -1
10
+ idx
11
+ end
12
+ end
13
+
@@ -1,130 +1,231 @@
1
- module CukeTagger
2
- class Tagger
3
- USAGE = "#{File.basename $0} [-v|--version] [-f|--force] [add|remove|replace]:TAG[:REPLACEMENT] [FILE[:LINE]]+"
4
-
5
- def self.execute(args)
6
- new.execute(args)
7
- end
8
-
9
- def execute(args)
10
- abort(USAGE) if args.empty? || args.first =~ /^(-h|--help)$/
11
- CukeTagger.log :args, args
12
-
13
- force = false
14
-
15
- args.each do |arg|
16
- case arg
17
- when /^-v|--version$/
18
- puts CukeTagger::Version
19
- when /^(.+?\.feature)((:\d+)*)$/
20
- add_feature $1, $2.to_s
21
- when /^(add|remove):(.+?)$/
22
- alterations << [$1.to_sym, $2]
23
- when /^(replace):(.+?):(.+)$/
24
- alterations << [$1.to_sym, [$2, $3]]
25
- when /^(-f|--force)$/
26
- force = true
27
- else
28
- abort(USAGE)
29
- end
30
- end
31
-
32
- alterations.uniq!
33
-
34
- CukeTagger.log :alterations, alterations
35
-
36
- files = features_to_change.map { |file, line| file }.uniq
37
- files.each { |file| parse file, force }
38
- end
39
-
40
- def parse(file, write)
41
- content = File.read(file)
42
-
43
- io = write ? File.open(file, "w") : $stdout
44
- begin
45
- pretty_formatter = Gherkin::Formatter::PrettyFormatter.new(io,
46
- !(io == $stdout && $stdout.tty?), # monochrome
47
- false) # executing
48
- pretty_formatter.extend TagFormatter
49
- pretty_formatter.tagger = self
50
-
51
- parser = Gherkin::Parser::Parser.new(pretty_formatter, true)
52
- parser.parse content, file, 0
53
- ensure
54
- io.close unless io == $stdout
55
- end
56
- end
57
-
58
- def process(uri, element)
59
- CukeTagger.log :process, :element => element.class
60
- return unless should_alter?(uri, element)
61
-
62
- @uri = uri
63
-
64
- alterations.each do |op, tag_name|
65
- case op
66
- when :add
67
- push_tag element, "@#{tag_name}"
68
- when :remove
69
- remove_tag element, "@#{tag_name}"
70
- when :replace
71
- replace_tag element, tag_name
72
- end
73
- end
74
- end
75
-
76
- def push_tag(element, tag_name)
77
- element.tags << Gherkin::Formatter::Model::Tag.new(tag_name, element.line)
78
- end
79
-
80
- def remove_tag(element, tag_name)
81
- element.tags.delete_if { |tag| tag.name == tag_name }
82
- end
83
-
84
- def replace_tag(element, tag_name)
85
- to_replace, replacement = tag_name
86
- to_replace = "@#{to_replace}"
87
- replacement = "@#{replacement}"
88
-
89
- idx = element.tags.find_index { |tag| tag.name == to_replace }
90
-
91
- if idx.nil?
92
- $stderr.puts "expected #{to_replace.inspect} at #{@uri}:#{element.line}, skipping"
93
- else
94
- element.tags[idx] = Gherkin::Formatter::Model::Tag.new(replacement, element.line)
95
- end
96
- end
97
-
98
- def should_alter?(uri, element)
99
- CukeTagger.log(:file_and_line => [uri, element.line], :features_to_change => features_to_change)
100
-
101
- features_to_change.any? do |file, line|
102
- file == uri && (element.line == line || (line.nil? && element.kind_of?(Gherkin::Formatter::Model::Feature)))
103
- end
104
- end
105
-
106
- private
107
-
108
- def add_feature(path, lines)
109
- lines = lines.split(":")
110
- lines.delete ""
111
-
112
- if lines.empty?
113
- features_to_change << [path, nil]
114
- else
115
- lines.each do |line|
116
- features_to_change << [path, Integer(line)]
117
- end
118
- end
119
- end
120
-
121
- def alterations
122
- @alterations ||= []
123
- end
124
-
125
- def features_to_change
126
- @features_to_change ||= Set.new
127
- end
128
-
129
- end
130
- end
1
+ module CukeTagger
2
+ class Tagger
3
+ USAGE = "#{File.basename $0} [-v|--version] [-f|--force] [add|remove|replace]:TAG[:REPLACEMENT] [FILE[:LINE]]+"
4
+
5
+ def self.execute(args)
6
+ new.execute(args)
7
+ end
8
+
9
+ def execute(args)
10
+ abort(USAGE) if args.empty? || args.first =~ /^(-h|--help)$/
11
+
12
+ force = false
13
+
14
+ args.each do |arg|
15
+ case arg
16
+ when /^-v|--version$/
17
+ puts CukeTagger::Version
18
+ when /^(.+?\.feature)((:\d+)*)$/
19
+ add_feature $1, $2.to_s
20
+ when /^(add|remove):(.+?)$/
21
+ alterations << [$1.to_sym, $2]
22
+ when /^(replace):(.+?):(.+)$/
23
+ alterations << [$1.to_sym, [$2, $3]]
24
+ when /^(-f|--force)$/
25
+ force = true
26
+ else
27
+ abort(USAGE)
28
+ end
29
+ end
30
+
31
+ alterations.uniq!
32
+
33
+ files = features_to_change.map { |file, line| file }.uniq
34
+ files.each { |file| parse file, force }
35
+ end
36
+
37
+
38
+ private
39
+
40
+
41
+ def parse(file_path, write)
42
+ return unless feature_to_change?(file_path)
43
+
44
+ content = File.open(file_path) { |file| file.readlines }
45
+
46
+ feature_model = CukeModeler::FeatureFile.new(file_path).feature
47
+
48
+ io = write ? File.open(file_path, "w") : $stdout
49
+
50
+ begin
51
+ taggable_things = collect_taggable_models(feature_model)
52
+
53
+ # Elements must be altered in the order that they appear in the file in order to
54
+ # guarantee that any line adjustments are applied appropriately.
55
+ taggable_things.sort!{|a,b| a.source_line <=> b.source_line}
56
+
57
+ taggable_things.each do |thing|
58
+ if thing_to_tag?(thing)
59
+ alterations.each do |alteration|
60
+ alter_thing(thing, alteration, content)
61
+ end
62
+ end
63
+ end
64
+
65
+ content = content.join
66
+
67
+ io.write(content)
68
+ ensure
69
+ io.close unless io == $stdout
70
+ end
71
+ end
72
+
73
+ def should_alter?(uri, element)
74
+ features_to_change.any? do |file, line|
75
+ file == uri && (element.line == line || (line.nil? && element.kind_of?(Gherkin::Formatter::Model::Feature)))
76
+ end
77
+ end
78
+
79
+ def add_feature(path, lines)
80
+ lines = lines.split(":")
81
+ lines.delete ""
82
+
83
+ if lines.empty?
84
+ features_to_change << [path, nil]
85
+ else
86
+ lines.each do |line|
87
+ features_to_change << [path, Integer(line)]
88
+ end
89
+ end
90
+ end
91
+
92
+ def alterations
93
+ @alterations ||= []
94
+ end
95
+
96
+ # todo - add warning if there are features that do not get changed (e.g. the user provided an incorrect file/line number or replaces a non-existant tag)
97
+ def features_to_change
98
+ @features_to_change ||= Set.new
99
+ end
100
+
101
+ def feature_to_change?(file_name)
102
+ features_to_change.any? { |name, line_number| name == file_name }
103
+ end
104
+
105
+ def thing_to_tag?(thing)
106
+ #todo - pass in file name as well for performance?
107
+ features_to_change.any? { |name, line_number|
108
+ name_match = (name == thing.get_ancestor(:feature_file).path)
109
+ number_match = (thing.source_line == line_number)
110
+
111
+ (name_match && number_match) || (name_match && thing.is_a?(CukeModeler::Feature) && line_number.nil?
112
+ )
113
+ }
114
+ end
115
+
116
+ def collect_taggable_models(feature_model)
117
+ results = feature_model.query do
118
+ select :model
119
+ from scenarios, outlines, examples
120
+ end
121
+ [feature_model] + results.collect { |result| result[:model] }
122
+ end
123
+
124
+ def alter_thing(thing, alteration, content)
125
+
126
+ case alteration.first
127
+ when :add
128
+ add_tag(thing, alteration.last, content)
129
+ when :remove
130
+ remove_tag(thing, alteration.last, content)
131
+ when :replace
132
+ replace_tag(thing, alteration.last.first, alteration.last.last, content)
133
+ else
134
+ raise "Unknown alteration type: #{alteration.first}"
135
+ end
136
+ end
137
+
138
+ def replace_tag(thing, old_tag, new_tag, content)
139
+ @file_offset ||= Hash.new(0)
140
+ @line_removed ||= {}
141
+
142
+ relevant_tag = thing.tags.select { |tag_model| tag_model.name == "@#{old_tag}" }.first
143
+
144
+ if relevant_tag
145
+ insertion_index = relevant_tag.source_line + @file_offset[thing.get_ancestor(:feature_file).path] - 1
146
+ content[insertion_index] = content[insertion_index].sub("@#{old_tag}", "@#{new_tag}")
147
+ else
148
+ $stderr.puts "expected \"@#{old_tag}\" at #{thing.get_ancestor(:feature_file).name}:#{thing.source_line}, skipping"
149
+ end
150
+ end
151
+
152
+ def add_tag(thing, tag, content)
153
+ @file_offset ||= Hash.new(0)
154
+ @line_removed ||= {}
155
+
156
+ insertion_index = thing.source_line + @file_offset[thing.get_ancestor(:feature_file).path] - 2
157
+
158
+ if new_line_needed?(thing, content, insertion_index)
159
+ insertion_index += 1
160
+ content.insert(insertion_index, '')
161
+
162
+ @line_removed[thing] = false
163
+ @file_offset[thing.get_ancestor(:feature_file).path] += 1
164
+ end
165
+
166
+ empty_line = content[insertion_index].chomp =~ /^\s*$/
167
+ trim_line(content, insertion_index, !empty_line)
168
+
169
+ content[insertion_index] = content[insertion_index].chomp + "#{tag_spacing(content, insertion_index)}@#{tag}\n"
170
+ end
171
+
172
+ def remove_tag(thing, tag, content)
173
+ @file_offset ||= Hash.new(0)
174
+ @line_removed ||= {}
175
+
176
+ relevant_tag = thing.tags.select { |tag_model| tag_model.name == "@#{tag}" }.first
177
+
178
+ return unless relevant_tag
179
+
180
+ insertion_index = relevant_tag.source_line + @file_offset[thing.get_ancestor(:feature_file).path] - 1
181
+ content[insertion_index] = content[insertion_index].sub(/@#{Regexp.escape(tag)} ?/, '')
182
+
183
+ trim_line(content, insertion_index, true)
184
+
185
+ if content[insertion_index] =~ /^\s*$/
186
+ content[insertion_index] = nil
187
+ content.compact!
188
+ @file_offset[thing.get_ancestor(:feature_file).path] -= 1
189
+ @line_removed[thing] = true
190
+ end
191
+ end
192
+
193
+ def trim_line(content, insertion_index, keep_indentation)
194
+ line_match = content[insertion_index].match(/^(\s*)(\S.*)?/)
195
+ indentation = line_match[1]
196
+ line_content = line_match[2]
197
+
198
+ trimmed_line = keep_indentation ? indentation : ''
199
+ trimmed_line += line_content.squeeze(' ').strip if line_content
200
+ trimmed_line = "#{trimmed_line.chomp}\n"
201
+
202
+ content[insertion_index] = trimmed_line
203
+ end
204
+
205
+ def tag_spacing(content, insertion_index)
206
+ if content[insertion_index] =~ /\S/
207
+ ' '
208
+ else
209
+ next_line_leading_spaces = content[insertion_index + 1].match(/^(\s*)/)[1]
210
+ ' ' * next_line_leading_spaces.length
211
+ end
212
+ end
213
+
214
+ def new_line_needed?(thing, content, insertion_index)
215
+ line_was_removed?(thing) || (non_tag_line?(content, insertion_index) && non_empty_line?(content, insertion_index))
216
+ end
217
+
218
+ def line_was_removed?(thing)
219
+ @line_removed[thing]
220
+ end
221
+
222
+ def non_tag_line?(content, insertion_index)
223
+ content[insertion_index] !~ /^\s*@/
224
+ end
225
+
226
+ def non_empty_line?(content, insertion_index)
227
+ content[insertion_index] !~ /^\s*$/
228
+ end
229
+
230
+ end
231
+ end