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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +6 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +220 -0
- data/Rakefile +48 -0
- data/article_fixture_gen.gemspec +55 -0
- data/bin/console +14 -0
- data/bin/setup +16 -0
- data/config.reek +8 -0
- data/data/all_specs.yml +127 -0
- data/data/validations.yml +35 -0
- data/exe/article_fixture_gen +16 -0
- data/lib/article_fixture_gen/cli/config.rb +57 -0
- data/lib/article_fixture_gen/cli/dump_config.rb +43 -0
- data/lib/article_fixture_gen/cli.rb +5 -0
- data/lib/article_fixture_gen/config/builder.rb +99 -0
- data/lib/article_fixture_gen/config/constants.rb +25 -0
- data/lib/article_fixture_gen/config/data.rb +26 -0
- data/lib/article_fixture_gen/config/option_validator.rb +74 -0
- data/lib/article_fixture_gen/config.rb +45 -0
- data/lib/article_fixture_gen/data/article.rb +55 -0
- data/lib/article_fixture_gen/data/article_content.rb +64 -0
- data/lib/article_fixture_gen/data/build_fragment_list.rb +68 -0
- data/lib/article_fixture_gen/data/build_target_entry_list.rb +68 -0
- data/lib/article_fixture_gen/data/build_word_list.rb +140 -0
- data/lib/article_fixture_gen/data/marker_array.rb +29 -0
- data/lib/article_fixture_gen/data/marker_tag_pair.rb +25 -0
- data/lib/article_fixture_gen/data/mtp_decorated_markup.rb +67 -0
- data/lib/article_fixture_gen/data/node_markup.rb +25 -0
- data/lib/article_fixture_gen/data/parent_element_for.rb +18 -0
- data/lib/article_fixture_gen/data/pmtp_attributes.rb +44 -0
- data/lib/article_fixture_gen/data/pmtp_decorated_markup.rb +27 -0
- data/lib/article_fixture_gen/data/pmtp_decorator_params.rb +16 -0
- data/lib/article_fixture_gen/data/replace_child_nodes_using_new_child.rb +67 -0
- data/lib/article_fixture_gen/data/single_marker_tag_pair.rb +31 -0
- data/lib/article_fixture_gen/data/smtp_attributes.rb +30 -0
- data/lib/article_fixture_gen/data/smtp_decorated_markup.rb +27 -0
- data/lib/article_fixture_gen/data/smtp_decorator_params.rb +16 -0
- data/lib/article_fixture_gen/data/split_text_at_target_word.rb +59 -0
- data/lib/article_fixture_gen/exe/config.rb +42 -0
- data/lib/article_fixture_gen/exe/generate_config.rb +51 -0
- data/lib/article_fixture_gen/exe/option_parser/trollop/option_spec.rb +30 -0
- data/lib/article_fixture_gen/exe/option_parser/trollop/option_spec_item.rb +16 -0
- data/lib/article_fixture_gen/exe/option_parser/trollop/options_and_mods.rb +57 -0
- data/lib/article_fixture_gen/exe/option_parser/trollop/options_with_defaults.rb +45 -0
- data/lib/article_fixture_gen/exe/option_parser/trollop/validator.rb +63 -0
- data/lib/article_fixture_gen/exe/option_parser/trollop.rb +49 -0
- data/lib/article_fixture_gen/exe/option_parser.rb +13 -0
- data/lib/article_fixture_gen/support/logging.rb +25 -0
- data/lib/article_fixture_gen/version.rb +6 -0
- data/lib/article_fixture_gen.rb +10 -0
- data/lib/tasks/prolog_flog_task.rb +12 -0
- data/vendor/ruby/2.3.0/bin/article_fixture_gen +22 -0
- 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
|