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,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'article_fixture_gen'
|
5
|
+
require 'article_fixture_gen/exe/config'
|
6
|
+
require 'article_fixture_gen/exe/generate_config'
|
7
|
+
require 'article_fixture_gen/exe/option_parser'
|
8
|
+
|
9
|
+
parsed_options = ArticleFixtureGen::Exe::OptionParser.call.to_h
|
10
|
+
config = ArticleFixtureGen::Exe::Config.load parsed_options
|
11
|
+
ArticleFixtureGen::Exe::GenerateConfig.call(config) if config.generate_config_given
|
12
|
+
|
13
|
+
articles = Array.new(config.article_count) do
|
14
|
+
ArticleFixtureGen::Data::Article.new(config: config).to_s
|
15
|
+
end
|
16
|
+
puts articles.join("\n\n")
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
require 'article_fixture_gen/config'
|
6
|
+
require 'article_fixture_gen/support/logging'
|
7
|
+
|
8
|
+
module ArticleFixtureGen
|
9
|
+
module CLI
|
10
|
+
# CLI implementation class that reads YAML config from file and uses it to
|
11
|
+
# create an `ArticleFixtureGen::Config` instance.
|
12
|
+
class Config
|
13
|
+
def self.call(filename:, defaults: nil, overrides: {})
|
14
|
+
new(filename, defaults, overrides).call
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
ArticleFixtureGen::Config.new final
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def initialize(filename, defaults, overrides)
|
24
|
+
@defaults = Internals.defaults_based_on(defaults)
|
25
|
+
@filename = filename
|
26
|
+
@overrides = overrides
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :defaults, :filename, :overrides
|
33
|
+
|
34
|
+
def file_contents
|
35
|
+
YAML.load_file filename
|
36
|
+
end
|
37
|
+
|
38
|
+
def final
|
39
|
+
defaults.merge(non_defaults)
|
40
|
+
end
|
41
|
+
|
42
|
+
def non_defaults
|
43
|
+
file_contents.merge(overrides)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Stateless methods.
|
47
|
+
module Internals
|
48
|
+
# Reek complains about a :reek:ControlParameter. :pedantic: :neckbeard:
|
49
|
+
def self.defaults_based_on(specified_defaults)
|
50
|
+
ret = specified_defaults || ArticleFixtureGen::Config.defaults
|
51
|
+
ret.to_hash
|
52
|
+
end
|
53
|
+
end
|
54
|
+
private_constant :Internals
|
55
|
+
end # class ArticleFixtureGen::CLI::Config
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'awesome_print'
|
4
|
+
|
5
|
+
require 'article_fixture_gen/config'
|
6
|
+
|
7
|
+
# Generate blog post/article fixture data, with embedded marker tag pairs.
|
8
|
+
module ArticleFixtureGen
|
9
|
+
# Command-line-specific classes.
|
10
|
+
module CLI
|
11
|
+
# Report contents of configuration data.
|
12
|
+
class DumpConfig
|
13
|
+
# Reek doesn't like :reek:BooleanParameter. Tough.
|
14
|
+
def self.call(config:, use_colour: false)
|
15
|
+
DumpConfig.new(config).call use_colour
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(use_colour)
|
19
|
+
@use_colour = use_colour
|
20
|
+
report
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def initialize(config)
|
26
|
+
@config = config.to_hash
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :config, :use_colour
|
33
|
+
|
34
|
+
def dump_options
|
35
|
+
{ plain: !use_colour, sort_keys: true }
|
36
|
+
end
|
37
|
+
|
38
|
+
def report
|
39
|
+
config.ai dump_options
|
40
|
+
end
|
41
|
+
end # class ArticleFixtureGen::CLI::DumpConfig
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'prolog/dry_types'
|
4
|
+
|
5
|
+
require_relative './constants'
|
6
|
+
|
7
|
+
# Generate blog post/article fixture data, with embedded marker tag pairs.
|
8
|
+
module ArticleFixtureGen
|
9
|
+
# Maintain configuration information for app.
|
10
|
+
class Config
|
11
|
+
# Builds hash with correct keys based on input. What's the value of this,
|
12
|
+
# you ask? Two things:
|
13
|
+
# 1. It defines `*_given` entries which are `true` if their corresponding
|
14
|
+
# entries are specified, whether those other entries are truthy or not;
|
15
|
+
# 2. It confirms filtering of the input hash, offering a guarantee that any
|
16
|
+
# entry accepted is one supported elsehwere in our code.
|
17
|
+
#
|
18
|
+
# And yes, this class is *awfully* procedural. Got ideas for a fix? We'd
|
19
|
+
# appreciate your PR!
|
20
|
+
class Builder
|
21
|
+
def self.call(options_hash)
|
22
|
+
new.call options_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(options)
|
26
|
+
@options = options
|
27
|
+
hashes
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :options
|
33
|
+
|
34
|
+
def config_hash
|
35
|
+
value = options[:config]
|
36
|
+
given = Internals.string_given?(value)
|
37
|
+
{ config_given: given }.tap do |ret|
|
38
|
+
ret[:config] = value || ''
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def generate_config_hash
|
43
|
+
value = options[:generate_config]
|
44
|
+
given = Internals.string_given?(value)
|
45
|
+
{ generate_config_given: given }.tap do |ret|
|
46
|
+
ret[:generate_config] = value || ''
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def hashes
|
51
|
+
items = hash_items
|
52
|
+
{}.tap do |ret|
|
53
|
+
items.each { |item| ret.merge! item }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def hash_items
|
58
|
+
items = [:config_hash, :generate_config_hash, :other_hash]
|
59
|
+
items.map { |sym| send(sym) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def other_hash
|
63
|
+
ret = Internals.other_specified_values(options)
|
64
|
+
ret.merge! Internals.defaults_not_in(ret)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Stateless methods.
|
68
|
+
module Internals
|
69
|
+
def self.defaults_not_in(hash)
|
70
|
+
keys = _other_keys_not_in(hash)
|
71
|
+
_defaults_for(keys)
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.other_specified_values(options)
|
75
|
+
keys = _other_hash_keys
|
76
|
+
options.select { |key, _| keys.include? key }
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.string_given?(value)
|
80
|
+
!value.to_s.empty?
|
81
|
+
end
|
82
|
+
|
83
|
+
def self._defaults_for(keys)
|
84
|
+
Constants::DEFAULTS.select { |key| keys.include? key }
|
85
|
+
end
|
86
|
+
|
87
|
+
def self._other_hash_keys
|
88
|
+
[:article_count, :para_count_max, :para_count_min, :sent_count_max,
|
89
|
+
:sent_count_min, :pmtp_count, :pmtp_text, :smtp_count, :smtp_text]
|
90
|
+
end
|
91
|
+
|
92
|
+
def self._other_keys_not_in(hash)
|
93
|
+
_other_hash_keys.reject { |key| hash.key? key }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
private_constant :Internals
|
97
|
+
end # class ArticleFixtureGen::Config::Builder
|
98
|
+
end # class ArticleFixtureGen::Config
|
99
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Generate blog post/article fixture data, with embedded marker tag pairs.
|
4
|
+
module ArticleFixtureGen
|
5
|
+
# Maintain configuration information for app.
|
6
|
+
class Config
|
7
|
+
# Constants used semi-internally to the class.
|
8
|
+
module Constants
|
9
|
+
DEFAULTS = {
|
10
|
+
article_count: 5,
|
11
|
+
config: '',
|
12
|
+
generate_config: '',
|
13
|
+
para_count_max: 10,
|
14
|
+
para_count_min: 2,
|
15
|
+
pmtp_count: 2,
|
16
|
+
pmtp_text: 'paired-mtp',
|
17
|
+
sent_count_max: 8,
|
18
|
+
sent_count_min: 1,
|
19
|
+
smtp_count: 4,
|
20
|
+
smtp_text: 'single-mtp'
|
21
|
+
}.freeze
|
22
|
+
end
|
23
|
+
# private_constant :Constants
|
24
|
+
end # class ArticleFixtureGen::Config
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'prolog/dry_types'
|
4
|
+
|
5
|
+
# Generate blog post/article fixture data, with embedded marker tag pairs.
|
6
|
+
module ArticleFixtureGen
|
7
|
+
# Maintain configuration information for app.
|
8
|
+
class Config
|
9
|
+
# Immutable value object containing config settings.
|
10
|
+
class Data < Dry::Struct::Value
|
11
|
+
attribute :article_count, Types::Strict::Int
|
12
|
+
attribute :config, Types::Strict::String.default('')
|
13
|
+
attribute :config_given, Types::Strict::Bool
|
14
|
+
attribute :generate_config, Types::String.default('')
|
15
|
+
attribute :generate_config_given, Types::Strict::Bool
|
16
|
+
attribute :para_count_max, Types::Strict::Int
|
17
|
+
attribute :para_count_min, Types::Strict::Int
|
18
|
+
attribute :pmtp_count, Types::Strict::Int
|
19
|
+
attribute :pmtp_text, Types::Strict::String
|
20
|
+
attribute :sent_count_max, Types::Strict::Int
|
21
|
+
attribute :sent_count_min, Types::Strict::Int
|
22
|
+
attribute :smtp_count, Types::Strict::Int
|
23
|
+
attribute :smtp_text, Types::Strict::String
|
24
|
+
end # class ArticleFixtureGen::Config::data
|
25
|
+
end # class ArticleFixtureGen::Config
|
26
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Generate blog post/article fixture data, with embedded marker tag pairs.
|
4
|
+
module ArticleFixtureGen
|
5
|
+
# Maintain configuration information for app.
|
6
|
+
class Config
|
7
|
+
# Encapsulates validations of configuration option values.
|
8
|
+
class OptionValidator
|
9
|
+
def self.call(options:)
|
10
|
+
OptionValidator.new.call(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@errors = []
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(options)
|
19
|
+
@options = options
|
20
|
+
FATALS.each { |entry| validate_option entry }
|
21
|
+
errors
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :errors, :options
|
27
|
+
|
28
|
+
FATALS = [
|
29
|
+
{ key: :para_count_max, message: 'must be at least 1',
|
30
|
+
cond: -> (opts) { opts[:para_count_max] < 1 } },
|
31
|
+
{ key: :para_count_max,
|
32
|
+
message: 'must be >= the value for --para_count_min',
|
33
|
+
cond: -> (opts) { opts[:para_count_max] < opts[:para_count_min] } },
|
34
|
+
{ key: :para_count_min, message: 'must be at least 1',
|
35
|
+
cond: -> (opts) { opts[:para_count_min] < 1 } },
|
36
|
+
{ key: :para_count_min,
|
37
|
+
message: 'must be <= the value for --para_count_max',
|
38
|
+
cond: -> (opts) { opts[:para_count_min] > opts[:para_count_max] } },
|
39
|
+
{ key: :pmtp_count, message: 'must be at least 0',
|
40
|
+
cond: -> (opts) { (opts[:pmtp_count]).negative? } },
|
41
|
+
{ key: :pmtp_text, message: 'may not be empty',
|
42
|
+
cond: -> (opts) { opts[:pmtp_text].to_s.strip.empty? } },
|
43
|
+
{ key: :sent_count_max, message: 'must be at least 1',
|
44
|
+
cond: -> (opts) { opts[:sent_count_max] < 1 } },
|
45
|
+
{ key: :sent_count_max,
|
46
|
+
message: 'must be >= the value for --sent_count_min',
|
47
|
+
cond: -> (opts) { opts[:sent_count_max] < opts[:sent_count_min] } },
|
48
|
+
{ key: :sent_count_min, message: 'must be at least 1',
|
49
|
+
cond: -> (opts) { opts[:sent_count_min] < 1 } },
|
50
|
+
{ key: :sent_count_min,
|
51
|
+
message: 'must be <= the value for --sent_count_max',
|
52
|
+
cond: -> (opts) { opts[:sent_count_min] > opts[:sent_count_max] } },
|
53
|
+
{ key: :smtp_count, message: 'must be at least 0',
|
54
|
+
cond: -> (opts) { (opts[:smtp_count]).negative? } },
|
55
|
+
{ key: :smtp_text, message: 'may not be empty',
|
56
|
+
cond: -> (opts) { opts[:smtp_text].to_s.strip.empty? } }
|
57
|
+
].freeze
|
58
|
+
private_constant :FATALS
|
59
|
+
|
60
|
+
def fails?(entry)
|
61
|
+
entry[:cond].call(options)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Reek and hashes = :reek:FeatureEnvy; aka petrol and flame. FIXME
|
65
|
+
def report_failure(entry)
|
66
|
+
@errors << [entry[:key], entry[:message]]
|
67
|
+
end
|
68
|
+
|
69
|
+
def validate_option(entry)
|
70
|
+
report_failure(entry) if fails?(entry)
|
71
|
+
end
|
72
|
+
end # class ArticleFixtureGen::Config::OptionValidator
|
73
|
+
end # class ArticleFixtureGen::Config
|
74
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
require_relative './config/builder'
|
6
|
+
require_relative './config/constants'
|
7
|
+
require_relative './config/data'
|
8
|
+
require_relative './config/option_validator'
|
9
|
+
|
10
|
+
# Generate blog post/article fixture data, with embedded marker tag pairs.
|
11
|
+
module ArticleFixtureGen
|
12
|
+
# Maintain configuration information for app.
|
13
|
+
class Config
|
14
|
+
# Error raised when validating one or more attributes fails.
|
15
|
+
class ValidationError < RuntimeError
|
16
|
+
end # class ArticleFixtureGen::Config::ValidationError
|
17
|
+
|
18
|
+
extend Forwardable
|
19
|
+
|
20
|
+
def initialize(options_hash)
|
21
|
+
data = Builder.call(options_hash)
|
22
|
+
validate(data) # will raise if not valid
|
23
|
+
@values = Data.new data
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.defaults
|
28
|
+
new Constants::DEFAULTS
|
29
|
+
end
|
30
|
+
|
31
|
+
def_delegators :@values, :article_count, :config, :config_given,
|
32
|
+
:generate_config, :generate_config_given,
|
33
|
+
:para_count_max, :para_count_min, :pmtp_count, :pmtp_text,
|
34
|
+
:sent_count_max, :sent_count_min, :smtp_count, :smtp_text,
|
35
|
+
:to_hash
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def validate(data)
|
40
|
+
errors = OptionValidator.call options: data
|
41
|
+
return self if errors.empty?
|
42
|
+
raise ValidationError, errors.first
|
43
|
+
end
|
44
|
+
end # class ArticleFixtureGen::Config
|
45
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ffaker'
|
4
|
+
|
5
|
+
require 'article_fixture_gen/data/article_content'
|
6
|
+
require 'article_fixture_gen/data/pmtp_decorated_markup'
|
7
|
+
require 'article_fixture_gen/data/smtp_decorated_markup'
|
8
|
+
require 'article_fixture_gen/support/logging'
|
9
|
+
|
10
|
+
# Generate blog post/article fixture data, with embedded marker tag pairs.
|
11
|
+
module ArticleFixtureGen
|
12
|
+
# Details of data built by the generator.
|
13
|
+
module Data
|
14
|
+
# Details of generated Article and internal details thereof.
|
15
|
+
class Article
|
16
|
+
# Reek kvetches about a :reek:ControlParameter. Too bad.
|
17
|
+
def initialize(config:, base_content: nil)
|
18
|
+
@config = config
|
19
|
+
logger = SemanticLogger['Article#initialize']
|
20
|
+
@base_content = base_content || Default.base_content(config)
|
21
|
+
logger.trace "Line #{__LINE__}", base_content: @base_content
|
22
|
+
@str = nil
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
return @str if @str
|
28
|
+
str = add_mtps(base_content, PmtpDecoratedMarkup)
|
29
|
+
@str = add_mtps(str, SmtpDecoratedMarkup)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :base_content, :config
|
35
|
+
|
36
|
+
def add_mtps(base_markup, decorator)
|
37
|
+
decorator.call(base_markup: base_markup, config: config).rstrip
|
38
|
+
end
|
39
|
+
|
40
|
+
# Default-value builders for #initialize parameters.
|
41
|
+
module Default
|
42
|
+
# FIXME: We would *prefer* `fancy: true`. However, there is the problem
|
43
|
+
# of https://github.com/ffaker/ffaker/issues/298 to contend with.
|
44
|
+
DEFAULT_FAKER_OPTS = { fancy: false }.freeze
|
45
|
+
|
46
|
+
def self.base_content(config, faker_opts = DEFAULT_FAKER_OPTS)
|
47
|
+
ArticleContent.new(config: config) do |sentence_count|
|
48
|
+
FFaker::HTMLIpsum.p(sentence_count, faker_opts)
|
49
|
+
end.to_s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
private_constant :Default
|
53
|
+
end # class ArticleFixtureGen::Data::Article
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Generate blog post/article fixture data, with embedded marker tag pairs.
|
4
|
+
module ArticleFixtureGen
|
5
|
+
# Details of data built by the generator.
|
6
|
+
module Data
|
7
|
+
# Details of generated Article and internal details thereof.
|
8
|
+
class ArticleContent
|
9
|
+
def initialize(config:, ¶s_builder)
|
10
|
+
@config = config
|
11
|
+
@paras_builder = paras_builder
|
12
|
+
@str = nil
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
return @str if @str
|
18
|
+
@str = Internals.wrap_content(content)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :config, :paras_builder
|
24
|
+
|
25
|
+
def content
|
26
|
+
paragraphs.join("\n")
|
27
|
+
end
|
28
|
+
|
29
|
+
def paragraph(sentence_count)
|
30
|
+
paras_builder.call sentence_count
|
31
|
+
end
|
32
|
+
|
33
|
+
def paragraphs
|
34
|
+
Array.new(para_count) { paragraph(sentence_count) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def para_count
|
38
|
+
rand(para_count_range)
|
39
|
+
end
|
40
|
+
|
41
|
+
def para_count_range
|
42
|
+
config.para_count_min..config.para_count_max
|
43
|
+
end
|
44
|
+
|
45
|
+
def sentence_count
|
46
|
+
rand(sentence_count_range)
|
47
|
+
end
|
48
|
+
|
49
|
+
def sentence_count_range
|
50
|
+
config.sent_count_min..config.sent_count_max
|
51
|
+
end
|
52
|
+
|
53
|
+
# Stateless methods and supporting constants.
|
54
|
+
module Internals
|
55
|
+
CONTENT_WRAPPER = ['<div class="article">', '</div>'].freeze
|
56
|
+
|
57
|
+
def self.wrap_content(inner_content)
|
58
|
+
CONTENT_WRAPPER.join inner_content
|
59
|
+
end
|
60
|
+
end
|
61
|
+
private_constant :Internals
|
62
|
+
end # class ArticleFixtureGen::Data::ArticleContent
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'prolog/dry_types'
|
4
|
+
|
5
|
+
module ArticleFixtureGen
|
6
|
+
module Data
|
7
|
+
# Assembles a "fragment list", a list of objects associating a string as
|
8
|
+
# found in a DOM leaf text node with the DOM position of that leaf node
|
9
|
+
# relative to a specified parent node and initial position. The default
|
10
|
+
# initial position, `[]`, indicates that the parent node is the root node
|
11
|
+
# of a DOM tree known to the caller.
|
12
|
+
class BuildFragmentList
|
13
|
+
def self.call(parent_node:, position: [])
|
14
|
+
BuildFragmentList.new.call(parent_node, position)
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(parent_node, position)
|
18
|
+
ret = []
|
19
|
+
Internals.with_each_child_of(parent_node) do |node, index|
|
20
|
+
ret += add_items_for(node, index, position)
|
21
|
+
end
|
22
|
+
ret
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def add_items_for(node, index, position)
|
28
|
+
item_pos = Internals.position_with(position, index)
|
29
|
+
return call(node, item_pos) if Internals.node?(node)
|
30
|
+
Internals.item_with(item_pos, node)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Stateless methods.
|
34
|
+
module Internals
|
35
|
+
def self.item_with(position, string)
|
36
|
+
Array(FragmentPosition.new(position: position, string: string))
|
37
|
+
end
|
38
|
+
|
39
|
+
# Reek doesn't like .respond_to?; it flags :reek:ManualDispatch. Tough.
|
40
|
+
def self.node?(item)
|
41
|
+
item.respond_to?(:nodes)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.position_with(position, index)
|
45
|
+
position + Array(index)
|
46
|
+
end
|
47
|
+
|
48
|
+
# This is here because we need *both* the node and the index, so
|
49
|
+
# `Array#map` doesn't do us for.
|
50
|
+
def self.with_each_child_of(parent_node)
|
51
|
+
parent_node.nodes.each_with_index { |node, index| yield(node, index) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
private_constant :Internals
|
55
|
+
|
56
|
+
# Associates a DOM-fragment string with a node position. The position is
|
57
|
+
# given as an array of indexes relative to the specified parent node.
|
58
|
+
# For example, given an object `obj` with :position of [0, 1, 7, 4], then
|
59
|
+
# obj.string == parent_node.nodes[0].nodes[1].nodes[7].nodes[4]
|
60
|
+
# would be true.
|
61
|
+
class FragmentPosition < Dry::Struct::Value
|
62
|
+
attribute :position, Types::Strict::Array.member(Types::Strict::Int)
|
63
|
+
attribute :string, Types::Strict::String
|
64
|
+
end
|
65
|
+
private_constant :FragmentPosition
|
66
|
+
end # class ArticleFixtureGen::Data::BuildFragmentList
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './build_word_list'
|
4
|
+
|
5
|
+
module ArticleFixtureGen
|
6
|
+
module Data
|
7
|
+
# Builds a word list suitable for randomly selecting MTP locations; these
|
8
|
+
# may be used for either single- or paired-marker tag pairs by the simple
|
9
|
+
# expedient of reverse-sorting the returned list of entries. Existing MTPs
|
10
|
+
# in the content will be unaffected, as they will be treated as any other
|
11
|
+
# element node and, since they have no contained text, will not affect the
|
12
|
+
# result.
|
13
|
+
class BuildTargetEntryList
|
14
|
+
def self.call(parent_node, count)
|
15
|
+
new(parent_node, count).call
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
entries.sort.reverse
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def initialize(parent_node, count)
|
25
|
+
@parent_node = parent_node
|
26
|
+
@count = count
|
27
|
+
@word_list = nil
|
28
|
+
@chosen_indexes = []
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :chosen_indexes, :count, :parent_node
|
35
|
+
|
36
|
+
def add_index(index)
|
37
|
+
chosen_indexes << index
|
38
|
+
index
|
39
|
+
end
|
40
|
+
|
41
|
+
def entries
|
42
|
+
Array.new(count) { item_from_word_list }
|
43
|
+
end
|
44
|
+
|
45
|
+
def index_taken?(index)
|
46
|
+
return true if index.to_i.zero? # covers nil
|
47
|
+
chosen_indexes.include?(index)
|
48
|
+
end
|
49
|
+
|
50
|
+
def item_from_word_list
|
51
|
+
word_list[random_index]
|
52
|
+
end
|
53
|
+
|
54
|
+
def random_index
|
55
|
+
add_index random_index_value
|
56
|
+
end
|
57
|
+
|
58
|
+
def random_index_value
|
59
|
+
index = rand(word_list.count) while index_taken?(index)
|
60
|
+
index
|
61
|
+
end
|
62
|
+
|
63
|
+
def word_list
|
64
|
+
@word_list ||= BuildWordList.call(parent_node: parent_node)
|
65
|
+
end
|
66
|
+
end # class ArticleFixtureGen::Data::BuildTargetEntryList
|
67
|
+
end
|
68
|
+
end
|