article_fixture_gen 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rubocop.yml +6 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +49 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +220 -0
  9. data/Rakefile +48 -0
  10. data/article_fixture_gen.gemspec +55 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +16 -0
  13. data/config.reek +8 -0
  14. data/data/all_specs.yml +127 -0
  15. data/data/validations.yml +35 -0
  16. data/exe/article_fixture_gen +16 -0
  17. data/lib/article_fixture_gen/cli/config.rb +57 -0
  18. data/lib/article_fixture_gen/cli/dump_config.rb +43 -0
  19. data/lib/article_fixture_gen/cli.rb +5 -0
  20. data/lib/article_fixture_gen/config/builder.rb +99 -0
  21. data/lib/article_fixture_gen/config/constants.rb +25 -0
  22. data/lib/article_fixture_gen/config/data.rb +26 -0
  23. data/lib/article_fixture_gen/config/option_validator.rb +74 -0
  24. data/lib/article_fixture_gen/config.rb +45 -0
  25. data/lib/article_fixture_gen/data/article.rb +55 -0
  26. data/lib/article_fixture_gen/data/article_content.rb +64 -0
  27. data/lib/article_fixture_gen/data/build_fragment_list.rb +68 -0
  28. data/lib/article_fixture_gen/data/build_target_entry_list.rb +68 -0
  29. data/lib/article_fixture_gen/data/build_word_list.rb +140 -0
  30. data/lib/article_fixture_gen/data/marker_array.rb +29 -0
  31. data/lib/article_fixture_gen/data/marker_tag_pair.rb +25 -0
  32. data/lib/article_fixture_gen/data/mtp_decorated_markup.rb +67 -0
  33. data/lib/article_fixture_gen/data/node_markup.rb +25 -0
  34. data/lib/article_fixture_gen/data/parent_element_for.rb +18 -0
  35. data/lib/article_fixture_gen/data/pmtp_attributes.rb +44 -0
  36. data/lib/article_fixture_gen/data/pmtp_decorated_markup.rb +27 -0
  37. data/lib/article_fixture_gen/data/pmtp_decorator_params.rb +16 -0
  38. data/lib/article_fixture_gen/data/replace_child_nodes_using_new_child.rb +67 -0
  39. data/lib/article_fixture_gen/data/single_marker_tag_pair.rb +31 -0
  40. data/lib/article_fixture_gen/data/smtp_attributes.rb +30 -0
  41. data/lib/article_fixture_gen/data/smtp_decorated_markup.rb +27 -0
  42. data/lib/article_fixture_gen/data/smtp_decorator_params.rb +16 -0
  43. data/lib/article_fixture_gen/data/split_text_at_target_word.rb +59 -0
  44. data/lib/article_fixture_gen/exe/config.rb +42 -0
  45. data/lib/article_fixture_gen/exe/generate_config.rb +51 -0
  46. data/lib/article_fixture_gen/exe/option_parser/trollop/option_spec.rb +30 -0
  47. data/lib/article_fixture_gen/exe/option_parser/trollop/option_spec_item.rb +16 -0
  48. data/lib/article_fixture_gen/exe/option_parser/trollop/options_and_mods.rb +57 -0
  49. data/lib/article_fixture_gen/exe/option_parser/trollop/options_with_defaults.rb +45 -0
  50. data/lib/article_fixture_gen/exe/option_parser/trollop/validator.rb +63 -0
  51. data/lib/article_fixture_gen/exe/option_parser/trollop.rb +49 -0
  52. data/lib/article_fixture_gen/exe/option_parser.rb +13 -0
  53. data/lib/article_fixture_gen/support/logging.rb +25 -0
  54. data/lib/article_fixture_gen/version.rb +6 -0
  55. data/lib/article_fixture_gen.rb +10 -0
  56. data/lib/tasks/prolog_flog_task.rb +12 -0
  57. data/vendor/ruby/2.3.0/bin/article_fixture_gen +22 -0
  58. metadata +515 -0
@@ -0,0 +1,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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'article_fixture_gen'
4
+
5
+ require 'article_fixture_gen/cli/dump_config'
@@ -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:, &paras_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