cuketagger 1.5 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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