hostlist-expand-collect 0.0.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/lib/hostlist_expand_collect.rb +246 -0
- metadata +44 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 3196f3d9812b32c67da4b1308d5a447f7b81b721
|
|
4
|
+
data.tar.gz: 3db1f500ef6879ea348cdf14534e5c6304aa15be
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f366f0a776c5773c851dcd42225aedc761cf0d5333b43cf1115404e493c59b5215e009ae6b599e95fef84ac285475e7d9b4bf0c97a2faa94f65cf94d1a73fbb6
|
|
7
|
+
data.tar.gz: 81cd9094ae40833b4b56c2fbfe4062d2038d0c24157206fae2a7a7501536627a110293d6ef203286fe21ed195ce5a7cb90bf6c0788c4608abe9fb180960cd6e1
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
3
|
+
MAX_SIZE = 1000
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# Expand a range (e.g/ 1-10 or 14), putting a prefix before
|
|
7
|
+
def expand_range(prefix, r)
|
|
8
|
+
return ["#{prefix}#{r}"] if /^[0-9]+$/.match r
|
|
9
|
+
|
|
10
|
+
m = /^([0-9]+)-([0-9]+)$/.match r
|
|
11
|
+
raise 'Error! Bad hostlist (bad range)' unless m
|
|
12
|
+
|
|
13
|
+
s_low, s_high = m[1], m[2]
|
|
14
|
+
|
|
15
|
+
low = s_low.to_i
|
|
16
|
+
high = s_high.to_i
|
|
17
|
+
|
|
18
|
+
raise 'Error! Start > Stop' if high < low
|
|
19
|
+
raise 'Error! Range too large' if high - low > MAX_SIZE
|
|
20
|
+
|
|
21
|
+
result = []
|
|
22
|
+
|
|
23
|
+
(low..high).each { |v| result.push "#{prefix}%0#{s_low.length}d" % v }
|
|
24
|
+
|
|
25
|
+
result
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# Expand a rangelist (e.g "1-10,14"), putting a prefix before.
|
|
30
|
+
def expand_rangelist(prefix, rangelist)
|
|
31
|
+
result = []
|
|
32
|
+
|
|
33
|
+
rangelist.split(",").each do |r|
|
|
34
|
+
result.concat(expand_range(prefix, r))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
result
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
##
|
|
41
|
+
# Expand a part (e.g. "x[1-2]y[1-3][1-3] (no outer level commas)")
|
|
42
|
+
def expand_part(s)
|
|
43
|
+
# Базовый случай - пустая часть развернётся в список с ""
|
|
44
|
+
return [""] if s == ''
|
|
45
|
+
|
|
46
|
+
# Разбить на:
|
|
47
|
+
# 1) Строку префикс (может быть пустой)
|
|
48
|
+
# 2) Rangelist в скобках (может быть пропущен)
|
|
49
|
+
# 3) Остальное
|
|
50
|
+
|
|
51
|
+
m = /([^,\[]*)(\[[^\]]*\])?(.*)/.match s
|
|
52
|
+
|
|
53
|
+
prefix, rangelist, rest = m[1], m[2], m[3]
|
|
54
|
+
|
|
55
|
+
# Развернём остаток
|
|
56
|
+
rest_expanded = expand_part rest
|
|
57
|
+
|
|
58
|
+
# Развернём собственную часть
|
|
59
|
+
unless rangelist
|
|
60
|
+
us_expanded = [prefix]
|
|
61
|
+
else
|
|
62
|
+
us_expanded = expand_rangelist(prefix, rangelist[1..-2])
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Комбинируем список со списком
|
|
66
|
+
if us_expanded.length * rest_expanded.length > MAX_SIZE
|
|
67
|
+
raise 'Error! Bad hostlist (results too large)'
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
us_expanded.product(rest_expanded).collect { |x, y| x + y }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# Удалить дубликаты из списка
|
|
75
|
+
def remove_duplicates(l)
|
|
76
|
+
l.to_set.to_a
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
##
|
|
80
|
+
# Expand a hostlist expression string to list
|
|
81
|
+
#
|
|
82
|
+
# Example: expand_hostlist("n[9-11],d[01-02]") ==>
|
|
83
|
+
# ['n9', 'n10', 'n11', 'd01', 'd02']
|
|
84
|
+
def expand_hostlist(hostlist, allow_duplicates=false)
|
|
85
|
+
results = []
|
|
86
|
+
bracket_level = 0
|
|
87
|
+
part = ''
|
|
88
|
+
|
|
89
|
+
(hostlist + ',').each_char do |c|
|
|
90
|
+
if c == ',' and bracket_level == 0
|
|
91
|
+
if part
|
|
92
|
+
results.concat(expand_part part)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
part = ''
|
|
96
|
+
bad_part = false
|
|
97
|
+
else
|
|
98
|
+
part += c
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
bracket_level += 1 if c == '['
|
|
102
|
+
bracket_level -= 1 if c == ']'
|
|
103
|
+
|
|
104
|
+
if bracket_level > 1
|
|
105
|
+
raise 'Error! Bad hostlist (nested brackets)'
|
|
106
|
+
elsif bracket_level < 0
|
|
107
|
+
raise 'Error! Bad hostlist (unbalanced brackets)'
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
if bracket_level > 0
|
|
112
|
+
raise 'Error! Bad hostlist (unbalanced brackets)'
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
return remove_duplicates(results) unless allow_duplicates
|
|
116
|
+
|
|
117
|
+
results
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
##
|
|
121
|
+
# Collect a hostlist string from a list of hosts
|
|
122
|
+
def collect_hostlist(hosts, silently_discard_bad = false)
|
|
123
|
+
left_right = []
|
|
124
|
+
|
|
125
|
+
hosts.each do |host|
|
|
126
|
+
# Remove leading and trailing whitespace first, and skip empty lines
|
|
127
|
+
host = host.strip
|
|
128
|
+
next if host.empty?
|
|
129
|
+
|
|
130
|
+
# We cannot accept a host containing any of the three special
|
|
131
|
+
# characters in the hostlist syntax (comma and flat brackets)
|
|
132
|
+
|
|
133
|
+
unless host.scan(/[\[\]\,]/).empty?
|
|
134
|
+
next if silently_discard_bad
|
|
135
|
+
|
|
136
|
+
raise 'Error! Forbidden character!'
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
left_right.push [host, ""]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Call the iterative function until it syas it's done
|
|
143
|
+
looping = true
|
|
144
|
+
|
|
145
|
+
while looping
|
|
146
|
+
left_right, looping = collect_hostlist_1 left_right
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
return (left_right.collect { |left, right| left + right }).join(",")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
##
|
|
153
|
+
# Format a range from low to high inclusively, with a certain width
|
|
154
|
+
def format_range(low, high, width)
|
|
155
|
+
return "%0#{width}d" % low if low == high
|
|
156
|
+
|
|
157
|
+
"%0#{width}d-" % low + "%0#{width}d" % high
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
##
|
|
161
|
+
# Collect a hostlist string from a list of hosts (left + right)
|
|
162
|
+
def collect_hostlist_1(left_right, silently_discard_bad = false)
|
|
163
|
+
# Scan a list of hosts (left+right) and build two things:
|
|
164
|
+
# 1) a set of all hosts seen (used later)
|
|
165
|
+
# 2) a list where each host entry is preprocessed for correct sorting
|
|
166
|
+
|
|
167
|
+
sortlist = []
|
|
168
|
+
remeaning = Set.new
|
|
169
|
+
|
|
170
|
+
left_right.each do |left, right|
|
|
171
|
+
host = left + right
|
|
172
|
+
remeaning.add host
|
|
173
|
+
|
|
174
|
+
m = /^(.*?)([0-9]+)?([^0-9]*)$/.match left
|
|
175
|
+
|
|
176
|
+
prefix, num_str, suffix = m[1], m[2], m[3]
|
|
177
|
+
|
|
178
|
+
suffix = suffix + right
|
|
179
|
+
|
|
180
|
+
unless num_str
|
|
181
|
+
throw 'Error! Prefix is not empty' unless prefix.empty?
|
|
182
|
+
sortlist.push [[host, nil], nil, nil, host]
|
|
183
|
+
else
|
|
184
|
+
num_int = num_str.to_i
|
|
185
|
+
sortlist.push [[prefix, suffix], num_int, num_str.length, host]
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
sortlist.sort!
|
|
190
|
+
|
|
191
|
+
results = []
|
|
192
|
+
need_another_loop = false
|
|
193
|
+
|
|
194
|
+
sortlist.group_by { |x| x[0] }.each do |prefix_suffix, group|
|
|
195
|
+
prefix, suffix = prefix_suffix
|
|
196
|
+
|
|
197
|
+
unless suffix
|
|
198
|
+
# Special case: a host with no numeric part
|
|
199
|
+
results.push ["", prefix]
|
|
200
|
+
remeaning.delete prefix
|
|
201
|
+
else
|
|
202
|
+
# General case
|
|
203
|
+
range_list = []
|
|
204
|
+
|
|
205
|
+
group.each do |prefix_suffix_2, num_int, num_width, host|
|
|
206
|
+
prefix_2, suffix_2 = prefix_suffix_2
|
|
207
|
+
|
|
208
|
+
next unless remeaning.include? host
|
|
209
|
+
raise 'Error! num_int is nil' unless num_int
|
|
210
|
+
|
|
211
|
+
# Scan for a range starting at the current host
|
|
212
|
+
low = num_int
|
|
213
|
+
|
|
214
|
+
while true
|
|
215
|
+
host = "#{prefix}%0#{num_width}d#{suffix}" % num_int
|
|
216
|
+
|
|
217
|
+
if remeaning.include? host
|
|
218
|
+
remeaning.delete host
|
|
219
|
+
num_int += 1
|
|
220
|
+
else
|
|
221
|
+
break
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
high = num_int - 1
|
|
226
|
+
|
|
227
|
+
raise 'Error! High < Low' if high < low
|
|
228
|
+
|
|
229
|
+
range_list.push [low, high, num_width]
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
need_another_loop = true
|
|
233
|
+
|
|
234
|
+
if range_list.length == 1 and range_list[0][0] == range_list[0][1]
|
|
235
|
+
results.push [prefix, "%0#{range_list[0][2]}d#{suffix}" % range_list[0][0]]
|
|
236
|
+
else
|
|
237
|
+
results.push [prefix, "[" + range_list.map { |l, h, w| format_range(l, h, w)}.join(',') + "]" + suffix]
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
raise 'Error! Remeaning is not empty' unless remeaning.empty?
|
|
243
|
+
|
|
244
|
+
return results, need_another_loop
|
|
245
|
+
end
|
|
246
|
+
|
metadata
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: hostlist-expand-collect
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Georgy Evtushenko
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2017-10-03 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Hostlist on native ruby (adaptation of python-hostlist)
|
|
14
|
+
email: evtushenko.georgy@gmail.com
|
|
15
|
+
executables: []
|
|
16
|
+
extensions: []
|
|
17
|
+
extra_rdoc_files: []
|
|
18
|
+
files:
|
|
19
|
+
- lib/hostlist_expand_collect.rb
|
|
20
|
+
homepage: https://github.com/senior-zero/Hostlist
|
|
21
|
+
licenses:
|
|
22
|
+
- MIT
|
|
23
|
+
metadata: {}
|
|
24
|
+
post_install_message:
|
|
25
|
+
rdoc_options: []
|
|
26
|
+
require_paths:
|
|
27
|
+
- lib
|
|
28
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
34
|
+
requirements:
|
|
35
|
+
- - ">="
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: '0'
|
|
38
|
+
requirements: []
|
|
39
|
+
rubyforge_project:
|
|
40
|
+
rubygems_version: 2.5.1
|
|
41
|
+
signing_key:
|
|
42
|
+
specification_version: 4
|
|
43
|
+
summary: Hostlist on native ruby
|
|
44
|
+
test_files: []
|