article_fixture_gen 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rubocop.yml +6 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +49 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +220 -0
  9. data/Rakefile +48 -0
  10. data/article_fixture_gen.gemspec +55 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +16 -0
  13. data/config.reek +8 -0
  14. data/data/all_specs.yml +127 -0
  15. data/data/validations.yml +35 -0
  16. data/exe/article_fixture_gen +16 -0
  17. data/lib/article_fixture_gen/cli/config.rb +57 -0
  18. data/lib/article_fixture_gen/cli/dump_config.rb +43 -0
  19. data/lib/article_fixture_gen/cli.rb +5 -0
  20. data/lib/article_fixture_gen/config/builder.rb +99 -0
  21. data/lib/article_fixture_gen/config/constants.rb +25 -0
  22. data/lib/article_fixture_gen/config/data.rb +26 -0
  23. data/lib/article_fixture_gen/config/option_validator.rb +74 -0
  24. data/lib/article_fixture_gen/config.rb +45 -0
  25. data/lib/article_fixture_gen/data/article.rb +55 -0
  26. data/lib/article_fixture_gen/data/article_content.rb +64 -0
  27. data/lib/article_fixture_gen/data/build_fragment_list.rb +68 -0
  28. data/lib/article_fixture_gen/data/build_target_entry_list.rb +68 -0
  29. data/lib/article_fixture_gen/data/build_word_list.rb +140 -0
  30. data/lib/article_fixture_gen/data/marker_array.rb +29 -0
  31. data/lib/article_fixture_gen/data/marker_tag_pair.rb +25 -0
  32. data/lib/article_fixture_gen/data/mtp_decorated_markup.rb +67 -0
  33. data/lib/article_fixture_gen/data/node_markup.rb +25 -0
  34. data/lib/article_fixture_gen/data/parent_element_for.rb +18 -0
  35. data/lib/article_fixture_gen/data/pmtp_attributes.rb +44 -0
  36. data/lib/article_fixture_gen/data/pmtp_decorated_markup.rb +27 -0
  37. data/lib/article_fixture_gen/data/pmtp_decorator_params.rb +16 -0
  38. data/lib/article_fixture_gen/data/replace_child_nodes_using_new_child.rb +67 -0
  39. data/lib/article_fixture_gen/data/single_marker_tag_pair.rb +31 -0
  40. data/lib/article_fixture_gen/data/smtp_attributes.rb +30 -0
  41. data/lib/article_fixture_gen/data/smtp_decorated_markup.rb +27 -0
  42. data/lib/article_fixture_gen/data/smtp_decorator_params.rb +16 -0
  43. data/lib/article_fixture_gen/data/split_text_at_target_word.rb +59 -0
  44. data/lib/article_fixture_gen/exe/config.rb +42 -0
  45. data/lib/article_fixture_gen/exe/generate_config.rb +51 -0
  46. data/lib/article_fixture_gen/exe/option_parser/trollop/option_spec.rb +30 -0
  47. data/lib/article_fixture_gen/exe/option_parser/trollop/option_spec_item.rb +16 -0
  48. data/lib/article_fixture_gen/exe/option_parser/trollop/options_and_mods.rb +57 -0
  49. data/lib/article_fixture_gen/exe/option_parser/trollop/options_with_defaults.rb +45 -0
  50. data/lib/article_fixture_gen/exe/option_parser/trollop/validator.rb +63 -0
  51. data/lib/article_fixture_gen/exe/option_parser/trollop.rb +49 -0
  52. data/lib/article_fixture_gen/exe/option_parser.rb +13 -0
  53. data/lib/article_fixture_gen/support/logging.rb +25 -0
  54. data/lib/article_fixture_gen/version.rb +6 -0
  55. data/lib/article_fixture_gen.rb +10 -0
  56. data/lib/tasks/prolog_flog_task.rb +12 -0
  57. data/vendor/ruby/2.3.0/bin/article_fixture_gen +22 -0
  58. metadata +515 -0
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'prolog/dry_types'
5
+
6
+ require_relative './build_fragment_list'
7
+
8
+ module ArticleFixtureGen
9
+ module Data
10
+ # Value object associating a "word" (non-whitespace substring of a text leaf
11
+ # node) with a DOM position and an index within that leaf node.
12
+ class WordEntry < Dry::Struct::Value
13
+ attribute :dom_position, Types::Strict::Array.member(Types::Strict::Int)
14
+ attribute :word, Types::Strict::String
15
+ attribute :word_index, Types::Strict::Int.constrained(gteq: 0)
16
+
17
+ def <=>(other)
18
+ WordEntryComparator.call(self, other)
19
+ end
20
+ end # class ArticleFixtureGen::Data::WordEntry
21
+
22
+ # Compares two WordEntry instances to each other, per comparison interface.
23
+ class WordEntryComparator
24
+ def self.call(obj, other)
25
+ WordEntryComparator.new(obj, other).call
26
+ end
27
+
28
+ def call
29
+ Internals.first_nonzero([dom_difference, index_difference]) || 0
30
+ # [dom_difference, index_difference].reject(&:zero?).first
31
+ end
32
+
33
+ protected
34
+
35
+ def initialize(obj, other)
36
+ @obj = obj
37
+ @other = other
38
+ self
39
+ end
40
+
41
+ private
42
+
43
+ def dom_difference
44
+ @obj.dom_position <=> @other.dom_position
45
+ end
46
+
47
+ def index_difference
48
+ @obj.word_index <=> @other.word_index
49
+ end
50
+
51
+ # Stateless methods.
52
+ module Internals
53
+ def self.first_nonzero(values)
54
+ _nonzero_values_for(values).first
55
+ end
56
+
57
+ def self._nonzero_values_for(values)
58
+ values.select(&:nonzero?)
59
+ end
60
+ end
61
+ private_constant :Internals
62
+ end # class ArticleFixtureGen::Data::WordEntryComparator
63
+
64
+ # Builds an array of WordEntry items, associating fragments of DOM text leaf
65
+ # nodes with position information of their containing node within the DOM
66
+ # and of the fragment within a (whitespace-split) array of fragments within
67
+ # the containing DOM node's text.
68
+ class BuildWordList
69
+ def self.call(parent_node:, dom_position: [])
70
+ BuildWordList.new(parent_node, dom_position).call
71
+ end
72
+
73
+ def call
74
+ ret = string_items.map do |string_item|
75
+ Internals.entries_for string_item
76
+ end
77
+ ret.flatten
78
+ end
79
+
80
+ protected
81
+
82
+ def initialize(parent_node, dom_position)
83
+ @dom_position = dom_position
84
+ @parent_node = parent_node
85
+ self
86
+ end
87
+
88
+ private
89
+
90
+ attr_reader :dom_position, :parent_node
91
+
92
+ def string_items
93
+ BuildFragmentList.call(parent_node: parent_node, position: dom_position)
94
+ end
95
+
96
+ # Statelesss methods
97
+ module Internals
98
+ def self.entries_for(string_item)
99
+ EntriesForStringItem.call string_item: string_item
100
+ end
101
+ end
102
+ private_constant :Internals
103
+
104
+ # Build an arary of WordEntry instances for "words" in a "string item".
105
+ class EntriesForStringItem
106
+ def self.call(string_item:)
107
+ EntriesForStringItem.new(string_item).call
108
+ end
109
+
110
+ def call
111
+ words.map { |word| entry_for word }
112
+ end
113
+
114
+ protected
115
+
116
+ def initialize(string_item)
117
+ @position = string_item.position
118
+ @word_index = 0
119
+ @words = string_item.string.split
120
+ self
121
+ end
122
+
123
+ private
124
+
125
+ attr_reader :position, :word_index, :words
126
+
127
+ def entry_for(word)
128
+ ret = WordEntry.new entry_params_for(word)
129
+ @word_index += 1
130
+ ret
131
+ end
132
+
133
+ def entry_params_for(word)
134
+ { dom_position: position, word: word, word_index: word_index }
135
+ end
136
+ end # class EntriesForStringItem
137
+ private_constant :EntriesForStringItem
138
+ end # class ArticleFixtureGen::Data::BuildWordList
139
+ end
140
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './marker_tag_pair'
4
+
5
+ module ArticleFixtureGen
6
+ module Data
7
+ # Builds an array of marker-tag ID strings.
8
+ # FIXME: This should be `BuildMarkerArray` or similar.
9
+ class MarkerArray
10
+ def self.call(attributes:)
11
+ new(attributes).call
12
+ end
13
+
14
+ def call
15
+ @attributes.map do |attrib|
16
+ mtp = MarkerTagPair.call id_string: attrib.id_string
17
+ NodeFromMarkup.call mtp
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ def initialize(attributes)
24
+ @attributes = attributes
25
+ self
26
+ end
27
+ end # class ArticleFixtureGen::Data::MarkerArray
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArticleFixtureGen
4
+ module Data
5
+ # Builds an HTML marker tag pair, either a single MTP or a paired MTP; we
6
+ # don't care at this level (the conventions for ID attributes distinguishing
7
+ # one from the other are applied at a higher level).
8
+ class MarkerTagPair
9
+ def self.call(id_string:)
10
+ Internals.to_s(id_string)
11
+ end
12
+
13
+ FORMAT_STR = %(<a href="" id="%s"></a>)
14
+ private_constant :FORMAT_STR
15
+
16
+ # Stateless methods.
17
+ module Internals
18
+ def self.to_s(id_attrib)
19
+ format FORMAT_STR, id_attrib
20
+ end
21
+ end
22
+ private_constant :Internals
23
+ end # class ArticleFixtureGen::Data::MarkerTagPair
24
+ end
25
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './build_target_entry_list'
4
+ require_relative './marker_array'
5
+ require_relative './node_markup'
6
+ require_relative './replace_child_nodes_using_new_child'
7
+ require_relative './split_text_at_target_word'
8
+
9
+ module ArticleFixtureGen
10
+ module Data
11
+ # Shared logic for marker tag pairs, both single MTPs and paired MTPs. This
12
+ # has been reworked so that the `attributes` parameter specifies the number
13
+ # of marker-tag pairs to be generated, by specifying the ID attributes of
14
+ # each MTP to be generated, working from the end of the content toward the
15
+ # beginning. From the standpoint of this class and its collaborators, doing
16
+ # so eliminates any need to differentiate between sMTPs and pMTPs, since
17
+ # each marker-tag pair is generated based on passed-in information.
18
+ class MtpDecoratedMarkup
19
+ def self.call(attributes:, base_markup:)
20
+ new(attributes).call(base_markup)
21
+ end
22
+
23
+ def call(base_markup)
24
+ parent_node = NodeFromMarkup.call base_markup
25
+ add_entries(parent_node)
26
+ MarkupFromNode.call parent_node
27
+ end
28
+
29
+ protected
30
+
31
+ def initialize(attributes)
32
+ @attributes = attributes
33
+ @markers = build_markers
34
+ self
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :attributes, :markers
40
+
41
+ def add_entries(parent_node)
42
+ each_entry_for(parent_node) do |target_entry, index|
43
+ add_marker(parent_node, target_entry, markers[index])
44
+ end
45
+ end
46
+
47
+ def add_marker(parent_node, target_entry, marker)
48
+ ReplaceChildNodesUsingNewChild.call(parent_node, target_entry, marker)
49
+ self
50
+ end
51
+
52
+ def build_markers
53
+ MarkerArray.call attributes: attributes
54
+ end
55
+
56
+ def each_entry_for(parent_node)
57
+ target_entries(parent_node).each_with_index do |target_entry, index|
58
+ yield target_entry, index
59
+ end
60
+ end
61
+
62
+ def target_entries(parent_node)
63
+ BuildTargetEntryList.call(parent_node, attributes.count)
64
+ end
65
+ end # class ArticleFixtureGen::Data::MtpDecoratedMarkup
66
+ end
67
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ox'
4
+
5
+ module ArticleFixtureGen
6
+ module Data
7
+ # Render an Ox::Element instance as HTML.
8
+ class MarkupFromNode
9
+ DEFAULT_OPTIONS = { indent: -1 }.freeze
10
+
11
+ def self.call(node, options = DEFAULT_OPTIONS)
12
+ Ox.dump(node, options)
13
+ end
14
+ end # class ArticleFixtureGen::Data::MarkupFromNode
15
+
16
+ # Convert an HTML string to an Ox::Element.
17
+ # NOTE: HTML *must* comprise a single outermost element, that may have an
18
+ # arbitrary number of nested elements within it.
19
+ class NodeFromMarkup
20
+ def self.call(markup)
21
+ Ox.parse(markup)
22
+ end
23
+ end # class ArticleFixtureGen::Data::NodeFromMarkup
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArticleFixtureGen
4
+ module Data
5
+ # Given a parent element and an array of integers which fully specify the
6
+ # DOM position of a node (or element, really), walk the DOM from the given
7
+ # parent to find the element which contains a node at the specified point
8
+ # below that parent. This is to work around the fact that Ox maintains no
9
+ # links between a node (or element) with its parent.
10
+ class ParentElementFor
11
+ def self.call(parent, dom_pos_indexes)
12
+ el = parent
13
+ dom_pos_indexes[0..-2].each { |index| el = el.nodes[index] }
14
+ el
15
+ end
16
+ end # class ArticleFixtureGen::Data::ParentElementFor
17
+ end
18
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArticleFixtureGen
4
+ module Data
5
+ # Wraps attributes for a single marker-tag pair instance. Initially, the
6
+ # only attribute supported is `:id_string`.
7
+ class PmtpAttributes
8
+ include Enumerable
9
+
10
+ def initialize(config:)
11
+ @items = build_all_items(config.pmtp_count, config.pmtp_text)
12
+ self
13
+ end
14
+
15
+ def each(&_block)
16
+ @items.each { |item| yield item }
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :items
22
+
23
+ def build_all_items(count, text)
24
+ Array.new(count) { build_items(text) }.flatten
25
+ end
26
+
27
+ def build_items(text, rand_limit: 10_000)
28
+ id_num = rand(rand_limit) + 1
29
+ begin_item = Internals.build_item(text, id_num, 'begin')
30
+ end_item = Internals.build_item(text, id_num, 'end')
31
+ [end_item, begin_item]
32
+ end
33
+
34
+ # Stateless methods.
35
+ module Internals
36
+ def self.build_item(text, id_number, end_str)
37
+ id_string = "#{text}-#{id_number}-#{end_str}"
38
+ Struct.new(:id_string).new(id_string).freeze
39
+ end
40
+ end
41
+ private_constant :Internals
42
+ end # class ArticleFixtureGen::Data::PmtpAttributes
43
+ end
44
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './mtp_decorated_markup'
4
+ require_relative './pmtp_decorator_params'
5
+
6
+ module ArticleFixtureGen
7
+ module Data
8
+ # Generate a copy of the specified base content with single marker-tag pairs
9
+ # inserted before random words in the content.
10
+ #
11
+ # Parameters:
12
+ # `base_markup`: *Must* be a string representing a single HTML element with
13
+ # child nodes, some containing text strings. The canonical
14
+ # example is article-body markup presented as a containing
15
+ # `div` with paragraphs and so on;
16
+ # `config`: A configuration-information object, which *must* respond to
17
+ # the `:pmtp_text` message with a string, and *must* respond
18
+ # to the `:pmtp_count` message with a non-negative integer.
19
+ #
20
+ class PmtpDecoratedMarkup
21
+ def self.call(base_markup:, config:, param_builder: PmtpDecoratorParams)
22
+ params = param_builder.call(base_markup: base_markup, config: config)
23
+ MtpDecoratedMarkup.call(params)
24
+ end
25
+ end # class ArticleFixtureGen::Data::PmtpDecoratedMarkup
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './pmtp_attributes'
4
+
5
+ module ArticleFixtureGen
6
+ module Data
7
+ # Builds a parameter hash for calling `MtpDecoratedMarkup` to decorate
8
+ # markup with paired MTPs (as opposed to single MTPs).
9
+ class PmtpDecoratorParams
10
+ def self.call(base_markup:, config:)
11
+ attributes = PmtpAttributes.new config: config
12
+ { attributes: attributes, base_markup: base_markup }
13
+ end
14
+ end # class ArticleFixtureGen::Data::SmtpDecoratorParams
15
+ end
16
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './parent_element_for'
4
+ require_relative './split_text_at_target_word'
5
+
6
+ module ArticleFixtureGen
7
+ module Data
8
+ # Replaces the (INTERNAL!) list of child nodes of an element with a list
9
+ # generated elsewhere (that replaces a single text node with two text nodes
10
+ # separated by a new element).
11
+ class ReplaceChildNodesUsingNewChild
12
+ def self.call(parent_node, target_entry, new_el)
13
+ new(parent_node, target_entry, new_el).call
14
+ end
15
+
16
+ def call
17
+ element.instance_variable_set :@nodes, nodes
18
+ end
19
+
20
+ protected
21
+
22
+ def initialize(parent_node, target_entry, new_el)
23
+ @element = ParentElementFor.call(parent_node, target_entry.dom_position)
24
+ @target_entry = target_entry
25
+ @new_el = new_el
26
+ self
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :element, :new_el, :target_entry
32
+
33
+ def leading_nodes
34
+ element.nodes[leading_node_indexes]
35
+ end
36
+
37
+ def leading_node_indexes
38
+ 0...node_index
39
+ end
40
+
41
+ def node_index
42
+ target_entry.dom_position.last
43
+ end
44
+
45
+ def nodes
46
+ parts = SplitTextAtTargetWord.call(element, target_entry)
47
+ replacement_nodes_for(parts)
48
+ end
49
+
50
+ def parts_and_element(parts)
51
+ parts.insert 1, new_el
52
+ end
53
+
54
+ def replacement_nodes_for(parts)
55
+ [leading_nodes, parts_and_element(parts), trailing_nodes].flatten(1)
56
+ end
57
+
58
+ def trailing_nodes
59
+ element.nodes[trailing_node_indexes]
60
+ end
61
+
62
+ def trailing_node_indexes
63
+ node_index + 1..-1
64
+ end
65
+ end # class ArticleFixtureGen::Data::ReplaceChildNodesUsingNewChild
66
+ end
67
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArticleFixtureGen
4
+ module Data
5
+ # Encapsulates a "single marker-tag pair", or sMTP; an HTML anchor tag with
6
+ # a specifically-formatted ID attribute, an empty :href attribute, and no
7
+ # enclosed content. Different than a "paired marker-tag pair", which is
8
+ # analogous to two slightly differently-formatted sMTPs separated by content
9
+ # (and with no overlapping pMTPs).
10
+ class SingleMarkerTagPair
11
+ def initialize(config:, counter:)
12
+ @counter = counter
13
+ @label = config.smtp_text
14
+ IceNine.deep_freeze self
15
+ end
16
+
17
+ def to_html
18
+ ['<a href="" id="', '"></a>'].join html_id
19
+ end
20
+ alias_method :to_s, :to_html
21
+
22
+ private
23
+
24
+ attr_reader :counter, :label
25
+
26
+ def html_id
27
+ [label, counter.to_s].join '-'
28
+ end
29
+ end # class ArticleFixtureGen::Data::SingleMarkerTagPair
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArticleFixtureGen
4
+ module Data
5
+ # Wraps attributes for a single marker-tag pair instance. Initially, the
6
+ # only attribute supported is `:id_string`.
7
+ class SmtpAttributes
8
+ include Enumerable
9
+
10
+ def initialize(config:)
11
+ text = config.smtp_text
12
+ @items = Array.new(config.smtp_count) { build_item(text) }
13
+ self
14
+ end
15
+
16
+ def each(&_block)
17
+ @items.each { |item| yield item }
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :items
23
+
24
+ def build_item(text, rand_limit: 10_000)
25
+ id_num = rand(rand_limit) + 1
26
+ Struct.new(:id_string).new "#{text}-#{id_num}"
27
+ end
28
+ end # class ArticleFixtureGen::Data::SmtpAttributes
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './mtp_decorated_markup'
4
+ require_relative './smtp_decorator_params'
5
+
6
+ module ArticleFixtureGen
7
+ module Data
8
+ # Generate a copy of the specified base content with single marker-tag pairs
9
+ # inserted before random words in the content.
10
+ #
11
+ # Parameters:
12
+ # `base_markup`: *Must* be a string representing a single HTML element with
13
+ # child nodes, some containing text strings. The canonical
14
+ # example is article-body markup presented as a containing
15
+ # `div` with paragraphs and so on;
16
+ # `config`: A configuration-information object, which *must* respond to
17
+ # the `:smtp_text` message with a string, and *must* respond
18
+ # to the `:smtp_count` message with a non-negative integer.
19
+ #
20
+ class SmtpDecoratedMarkup
21
+ def self.call(base_markup:, config:, param_builder: SmtpDecoratorParams)
22
+ params = param_builder.call(base_markup: base_markup, config: config)
23
+ MtpDecoratedMarkup.call(params)
24
+ end
25
+ end # class ArticleFixtureGen::Data::SmtpDecoratedMarkup
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './smtp_attributes'
4
+
5
+ module ArticleFixtureGen
6
+ module Data
7
+ # Builds a parameter hash for calling `MtpDecoratedMarkup` to decorate
8
+ # markup with single MTPs (as opposed to paired MTPs).
9
+ class SmtpDecoratorParams
10
+ def self.call(base_markup:, config:)
11
+ attributes = SmtpAttributes.new config: config
12
+ { attributes: attributes, base_markup: base_markup }
13
+ end
14
+ end # class ArticleFixtureGen::Data::SmtpDecoratorParams
15
+ end
16
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArticleFixtureGen
4
+ module Data
5
+ # Produces an array of two strings such that, for a specified text node
6
+ # (string), the first string will be the text before a specified word, and
7
+ # the second (last) string will be the text after that word. The word in
8
+ # question is specified by a child-node index relative to the `el` parameter
9
+ # (an `Ox::Element` instance) and a word index within that child node.
10
+ class SplitTextAtTargetWord
11
+ def self.call(el, target_entry)
12
+ new(el, target_entry).call
13
+ end
14
+
15
+ def call
16
+ [head, tail]
17
+ end
18
+
19
+ protected
20
+
21
+ def initialize(el, target_entry)
22
+ @original_text = Internals.original_text(el, target_entry)
23
+ @target_entry = target_entry
24
+ @word_index = target_entry.word_index
25
+ self
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :original_text, :target_entry, :word_index
31
+
32
+ def ends_in_space?
33
+ original_text[-1] == ' '
34
+ end
35
+
36
+ def head
37
+ words_in_range(0...word_index) + ' '
38
+ end
39
+
40
+ def tail
41
+ ret = words_in_range(word_index..-1)
42
+ ret += ' ' if ends_in_space?
43
+ ret
44
+ end
45
+
46
+ def words_in_range(range)
47
+ original_text.split[range].join(' ')
48
+ end
49
+
50
+ # Stateless methods.
51
+ module Internals
52
+ def self.original_text(el, target_entry)
53
+ el.nodes[target_entry.dom_position.last]
54
+ end
55
+ end
56
+ private_constant :Internals
57
+ end # class ArticleFixtureGen::Data::SplitTextAtTargetWord
58
+ end
59
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'article_fixture_gen/cli/config'
4
+
5
+ module ArticleFixtureGen
6
+ module Exe
7
+ # Build configuration, consolidating defaults, possible YAML config-file
8
+ # contents, and possible command-line-specified settings.
9
+ class Config
10
+ def self.load(modified:, options:)
11
+ new(options, modified).load
12
+ end
13
+
14
+ def load
15
+ return config_based_on_file if config_given?
16
+ ArticleFixtureGen::Config.new options
17
+ end
18
+
19
+ protected
20
+
21
+ def initialize(options, modified)
22
+ @options = options.to_hash
23
+ @modified = modified.to_hash
24
+ self
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :modified, :options
30
+
31
+ def config_given?
32
+ options[:config_given] == true
33
+ end
34
+
35
+ def config_based_on_file
36
+ effective_options = { defaults: options, filename: options[:config],
37
+ overrides: modified }
38
+ ArticleFixtureGen::CLI::Config.call effective_options
39
+ end
40
+ end # class ArticleFixtureGen::Exe::Config
41
+ end
42
+ end