hostlist_expression 1.0.3 → 1.0.4

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 (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hostlist_expression.rb +57 -82
  3. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0992689ca6dd58441493afaf85a639388f17d265f2fd24a825ee618137190f5
4
- data.tar.gz: 27f65117b4e7307ebf734c97caac3872d85b1b7ada0c1c07c697e21e687f4749
3
+ metadata.gz: b8ced6089230749389cf14d25a15b05432435942e3ff8b8011da3a0b9891659e
4
+ data.tar.gz: d613f8364b2cf83aa8beecf3a14566da30680353d305b5e9127a1c419d15f9b2
5
5
  SHA512:
6
- metadata.gz: ed2e47a07fd75fcce47942774949c3a7461c73bf4b954639c5f80b3494ffa96ebfe33529683d68973517c42107c54d294085a3f3be6d65a286f8099713ca3052
7
- data.tar.gz: 6646f9aa7bb1decd99e9deb296f5e4b194ec672d6eda80501770b4725651c445a2f2e8d4a6bd6baa6cfd2847078c9d98a78516b8a3d87e9e5e04440fdfdfbe44
6
+ metadata.gz: e8baf7b857a06fff1093c00f4113c3bebb2660c717f9a864c49fa25877b2dd32e856e5e95e54bf0f053ff9ac352d4b48b93a59bb531ea98a1b124d9cc5bb122f
7
+ data.tar.gz: 4ec08df27a097d36b323ca285bb93dd96c04c3a6bd26ab11aa2e4f6edc585db164911258bd8b3b62e52629dbb31dc854933725dfa7ace7e47b45750216c15df8
@@ -4,101 +4,76 @@
4
4
  # License: MIT
5
5
  # Home: https://github.com/udondan/hostlist_expression-ruby
6
6
 
7
- # Expand hostlist expression
8
- #
9
- # @param [ String ] expression A host list expression.
10
- # @param [ String, Array<String> ] separator - Character(s) for separating ranges.
11
- #
12
- # @example
13
- # hostlist_expression("your-host-[1-3].com")
14
- # # => ["your-host-1.com", "your-host-2.com", "your-host-3.com"]
15
- #
16
- # @return [ Array<String> ] A list of expanded hosts.
17
- #
18
- # @since 0.1.0
19
- def hostlist_expression(expression, separator = [":", "-"])
20
- # Validate range separator
21
- separator = separator.join if separator.is_a?(Array)
22
- if !separator.is_a?(String)
23
- raise "Error: Range separator must be either of type String or Array. given: #{separator.class}"
24
- elsif separator.empty?
25
- raise "Error: Range separator is empty"
26
- end
27
-
28
- # Prepare separator for use in regular expressions
29
- separator = Regexp.escape(separator)
7
+ # Expansion logic behind {#hostlist_expression}.
8
+ module HostlistExpression
9
+ class << self
10
+ # Expand hostlist expression. See {#hostlist_expression}.
11
+ def expand(expression, separator = [":", "-"])
12
+ separator = separator_pattern(separator)
13
+
14
+ # Return input as single host, if this is not a hostlist expression
15
+ return [expression] unless expression.match?(/\[(?:[\da-z]+(?:[#{separator}][\da-z]+)?,?)+\]/i)
16
+
17
+ # Replace each bracket group, e.g. [0:10] or [A,C-E], in every partial host
18
+ expression.scan(/\[([\da-z#{separator},]+)\]/i).reduce([expression]) do |hosts, (group)|
19
+ replacements = group.split(",").flat_map { |range| expand_range(range, separator, expression) }
20
+ hosts.flat_map { |host| replacements.map { |replacement| host.sub("[#{group}]", replacement) } }
21
+ end.uniq
22
+ end
30
23
 
31
- # Return input as single host, if this is not a hostlist expression
32
- return [expression] unless expression.match(/\[(?:[\da-z]+(?:[#{separator}][\da-z]+)?,?)+\]/i)
24
+ private
33
25
 
34
- # hosts array will hold all expanded results,
35
- # it as well is the working array where partially resolved results are stored
36
- hosts = [expression]
26
+ # Validate the separator and escape it for use in a regexp character class
27
+ def separator_pattern(separator)
28
+ separator = separator.join if separator.is_a?(Array)
29
+ unless separator.is_a?(String)
30
+ raise "Error: Range separator must be either of type String or Array. given: #{separator.class}"
31
+ end
32
+ raise "Error: Range separator is empty" if separator.empty?
37
33
 
38
- # Iterate over all range definitions, e.g. [0:10] or [A:Z]
39
- expression.scan(/\[([\da-z#{separator},]+)\]/i).each do |match|
40
- # Will hold replacements for each match
41
- replacements = []
34
+ Regexp.escape(separator)
35
+ end
42
36
 
43
- # The pattern may be a sequence with multiple ranges. Split...
44
- match[0].split(",").each do |range|
45
- # Split ranges by range separator
46
- range_items = range.split(/[#{separator}]/)
37
+ # Expand a single sequence item, e.g. "1-3" or "A", into its values
38
+ def expand_range(range, separator, expression)
39
+ items = range.split(/[#{separator}]/)
47
40
 
48
41
  # If it's not really a range, duplicate the single item
49
- range_items.push(range_items[0]) if range_items.length == 1
50
-
51
- # Numeric range
52
- if range_items.all? { |item| item.match(/^[0-9]+$/) }
53
- isnum = true
54
- from, to = range_items.map(&:to_i).sort
55
-
56
- # Uppercase alphabetic range
57
- elsif range_items.all? { |item| item.match(/^[A-Z]$/) }
58
- alphabet = ("A".."Z").to_a
59
- from, to = range_items.map { |item| alphabet.index(item) }.sort
60
-
61
- # Lowercase alphabetic range
62
- elsif range_items.all? { |item| item.match(/^[a-z]$/) }
63
- alphabet = ("a".."z").to_a
64
- from, to = range_items.map { |item| alphabet.index(item) }.sort
42
+ items.push(items[0]) if items.length == 1
65
43
 
44
+ if items.all?(/\A[0-9]+\z/)
45
+ numeric_range(items)
46
+ elsif items.all?(/\A[A-Z]\z/) || items.all?(/\A[a-z]\z/)
47
+ from, to = items.sort
48
+ (from..to).to_a
66
49
  else
67
50
  raise "Error: Invalid host range definition #{expression}"
68
51
  end
69
-
70
- # Pad numbers only if a bound has leading zeros, to the width of the longest bound
71
- padding = range_items.any? { |item| item.match(/^0[0-9]/) } ? range_items.map(&:length).max : 0
72
-
73
- # Iterate over all hosts and store the resolved patterns in "replacements"
74
- hosts.each do |_host|
75
- # Iterate over the range
76
- (from..to).each do |i|
77
- if isnum
78
- # Formatting number with leading zeros
79
- replacements.push(i.to_s.rjust(padding, "0"))
80
- else
81
- # Select correct letter from alphabet
82
- replacements.push(alphabet[i])
83
- end
84
- end
85
- end
86
52
  end
87
53
 
88
- # We clone the hosts array, because we can't modify it while iterating over its elements.
89
- # So we iterate over the clone instead
90
- hosts.clone.each do |host|
91
- # Remove the original element
92
- hosts.delete(host)
93
-
94
- # Iterate over previously stored replacements
95
- replacements.each do |replacement|
96
- # Adding replacement to hosts array
97
- hosts.push(host.sub(/\[#{match[0]}\]/, replacement))
98
- end
54
+ # Pad numbers only if a bound has leading zeros, to the width of the longest bound
55
+ def numeric_range(items)
56
+ from, to = items.map(&:to_i).sort
57
+ padding = items.any?(/\A0[0-9]/) ? items.map(&:length).max : 0
58
+ (from..to).map { |i| i.to_s.rjust(padding, "0") }
99
59
  end
100
60
  end
61
+ end
101
62
 
102
- # Return unique results
103
- hosts.uniq
63
+ # Expand hostlist expression
64
+ #
65
+ # @param [ String ] expression A host list expression.
66
+ # @param [ String, Array<String> ] separator - Character(s) for separating ranges.
67
+ #
68
+ # @example
69
+ # hostlist_expression("your-host-[1-3].com")
70
+ # # => ["your-host-1.com", "your-host-2.com", "your-host-3.com"]
71
+ #
72
+ # @return [ Array<String> ] A list of expanded hosts.
73
+ #
74
+ # @raise [ RuntimeError ] If the separator or a range definition is invalid.
75
+ #
76
+ # @since 0.1.0
77
+ def hostlist_expression(expression, separator = [":", "-"])
78
+ HostlistExpression.expand(expression, separator)
104
79
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hostlist_expression
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Schroeder