article_fixture_gen 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|