article_fixture_gen 0.1.1

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.
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