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