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