multisync 0.3.7 → 0.4.0
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 +4 -4
- data/.standard.yml +3 -0
- data/CHANGELOG.md +59 -49
- data/LICENSE.txt +1 -1
- data/README.md +14 -11
- data/Rakefile +3 -1
- data/exe/multisync +2 -2
- data/lib/multisync/catalog.rb +10 -13
- data/lib/multisync/cli.rb +60 -88
- data/lib/multisync/colors.rb +23 -0
- data/lib/multisync/definition/dsl.rb +19 -21
- data/lib/multisync/definition/entity.rb +34 -47
- data/lib/multisync/definition/null.rb +11 -13
- data/lib/multisync/definition/template.rb +8 -9
- data/lib/multisync/definition.rb +5 -6
- data/lib/multisync/list.rb +29 -26
- data/lib/multisync/rsync_stat.rb +12 -13
- data/lib/multisync/runtime.rb +43 -48
- data/lib/multisync/selector.rb +44 -22
- data/lib/multisync/summary.rb +55 -18
- data/lib/multisync/version.rb +3 -1
- data/lib/multisync.rb +14 -9
- data/sample/multisync.rb +10 -18
- metadata +22 -14
- data/.gitignore +0 -13
- data/Gemfile +0 -10
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/multisync.gemspec +0 -41
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
|
|
2
1
|
class Multisync::Definition::Entity
|
|
3
|
-
|
|
4
2
|
include Multisync::Definition::Dsl
|
|
5
3
|
|
|
6
|
-
# The parent of the
|
|
4
|
+
# The parent of the entity
|
|
7
5
|
attr_reader :parent
|
|
8
|
-
|
|
9
|
-
# The name of the
|
|
6
|
+
|
|
7
|
+
# The name of the entity
|
|
10
8
|
attr_reader :name
|
|
11
|
-
|
|
12
|
-
#
|
|
9
|
+
|
|
10
|
+
# The level of the entity
|
|
11
|
+
attr_reader :level
|
|
12
|
+
|
|
13
|
+
# All members (groups or syncs) of this entity
|
|
13
14
|
attr_reader :members
|
|
14
|
-
|
|
15
|
-
# Collected
|
|
15
|
+
|
|
16
|
+
# Collected result after run
|
|
16
17
|
# {
|
|
17
18
|
# cmd: 'rsync --stats -v source destination',
|
|
18
19
|
# action: :run,
|
|
@@ -21,96 +22,82 @@ class Multisync::Definition::Entity
|
|
|
21
22
|
# stderr: '',
|
|
22
23
|
# skip_message: 'host not reachable',
|
|
23
24
|
# }
|
|
24
|
-
|
|
25
|
+
|
|
25
26
|
attr_reader :result
|
|
26
27
|
|
|
27
28
|
def initialize parent, name, &block
|
|
28
29
|
@members = []
|
|
29
|
-
@name = name
|
|
30
|
+
@name = name
|
|
30
31
|
@parent = parent
|
|
32
|
+
@level = parent.level + 1
|
|
31
33
|
parent.register self
|
|
32
34
|
instance_eval(&block) if block_given?
|
|
33
35
|
@result = {}
|
|
34
36
|
end
|
|
35
|
-
|
|
36
|
-
def to_h
|
|
37
|
-
{
|
|
38
|
-
fullname: fullname,
|
|
39
|
-
default: default?,
|
|
40
|
-
source: {
|
|
41
|
-
path: source,
|
|
42
|
-
description: source_description,
|
|
43
|
-
check: check_source?,
|
|
44
|
-
},
|
|
45
|
-
destination: {
|
|
46
|
-
path: destination,
|
|
47
|
-
description: destination_description,
|
|
48
|
-
check: check_destination?,
|
|
49
|
-
},
|
|
50
|
-
checks: checks,
|
|
51
|
-
rsync_options: rsync_options,
|
|
52
|
-
}
|
|
53
|
-
end
|
|
54
|
-
|
|
37
|
+
|
|
55
38
|
# Make the definition visitable
|
|
56
|
-
def accept visitor
|
|
57
|
-
visitor.visit self
|
|
39
|
+
def accept visitor
|
|
40
|
+
visitor.visit self
|
|
58
41
|
members.map do |member|
|
|
59
|
-
member.accept visitor
|
|
42
|
+
member.accept visitor
|
|
60
43
|
end
|
|
61
44
|
end
|
|
62
|
-
|
|
45
|
+
|
|
63
46
|
def register member
|
|
64
47
|
members << member
|
|
65
48
|
end
|
|
66
|
-
|
|
49
|
+
|
|
67
50
|
# The name including all parents separated by "/"
|
|
68
51
|
def fullname
|
|
69
|
-
[parent.fullname, name].join
|
|
52
|
+
[parent.fullname, name].reject(&:empty?).join("/")
|
|
70
53
|
end
|
|
71
|
-
|
|
54
|
+
|
|
72
55
|
# rsync source
|
|
73
56
|
def source
|
|
74
57
|
@from_value || parent.source
|
|
75
58
|
end
|
|
76
|
-
|
|
59
|
+
|
|
77
60
|
def source_description
|
|
78
61
|
@from_description || @from_value || parent.source_description
|
|
79
62
|
end
|
|
80
|
-
|
|
63
|
+
|
|
81
64
|
# rsync destination
|
|
82
65
|
def destination
|
|
83
66
|
@to_value || parent.destination
|
|
84
67
|
end
|
|
85
|
-
|
|
68
|
+
|
|
86
69
|
def destination_description
|
|
87
70
|
@to_description || @to_value || parent.destination_description
|
|
88
71
|
end
|
|
89
|
-
|
|
72
|
+
|
|
90
73
|
# rsync options
|
|
91
74
|
def rsync_options
|
|
92
75
|
opts = @rsync_options || []
|
|
93
76
|
return opts if @rsync_options_mode == :override
|
|
94
77
|
parent.rsync_options + opts
|
|
95
78
|
end
|
|
96
|
-
|
|
79
|
+
|
|
97
80
|
# Is this group/sync defined as default
|
|
98
81
|
def default?
|
|
99
82
|
@default || parent.default?
|
|
100
83
|
end
|
|
101
|
-
|
|
84
|
+
|
|
85
|
+
def executeable?
|
|
86
|
+
members.none?
|
|
87
|
+
end
|
|
88
|
+
|
|
102
89
|
# All checks from parent to child
|
|
103
90
|
def checks
|
|
104
91
|
(parent.checks + [@check]).compact
|
|
105
92
|
end
|
|
106
|
-
|
|
93
|
+
|
|
107
94
|
# Should source's host or path be checked before sync?
|
|
108
95
|
def check_source?
|
|
109
96
|
@from_check.nil? ? parent.check_source? : @from_check
|
|
110
97
|
end
|
|
111
|
-
|
|
98
|
+
|
|
112
99
|
# Should destination's host or path be checked before sync?
|
|
113
100
|
def check_destination?
|
|
114
101
|
@to_check.nil? ? parent.check_destination? : @to_check
|
|
115
102
|
end
|
|
116
|
-
end
|
|
103
|
+
end
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
|
|
2
1
|
class Multisync::Definition::Null < Multisync::Definition::Entity
|
|
3
|
-
|
|
4
2
|
def initialize
|
|
3
|
+
@level = -1
|
|
5
4
|
end
|
|
6
|
-
|
|
5
|
+
|
|
7
6
|
def register member
|
|
8
7
|
end
|
|
9
8
|
|
|
10
9
|
def fullname
|
|
11
|
-
|
|
10
|
+
""
|
|
12
11
|
end
|
|
13
12
|
|
|
14
13
|
def rsync_options
|
|
@@ -19,34 +18,33 @@ class Multisync::Definition::Null < Multisync::Definition::Entity
|
|
|
19
18
|
def source
|
|
20
19
|
raise "no source (from) defined"
|
|
21
20
|
end
|
|
22
|
-
|
|
21
|
+
|
|
23
22
|
def source_description
|
|
24
|
-
|
|
23
|
+
""
|
|
25
24
|
end
|
|
26
|
-
|
|
25
|
+
|
|
27
26
|
# to (destination) is a required option and should be set at least at root level
|
|
28
27
|
def destination
|
|
29
28
|
raise "no destination (to) defined"
|
|
30
29
|
end
|
|
31
|
-
|
|
30
|
+
|
|
32
31
|
def destination_description
|
|
33
|
-
|
|
32
|
+
""
|
|
34
33
|
end
|
|
35
|
-
|
|
34
|
+
|
|
36
35
|
def default?
|
|
37
36
|
false
|
|
38
37
|
end
|
|
39
|
-
|
|
38
|
+
|
|
40
39
|
def checks
|
|
41
40
|
[]
|
|
42
41
|
end
|
|
43
42
|
|
|
44
43
|
def check_source?
|
|
45
44
|
false
|
|
46
|
-
end
|
|
45
|
+
end
|
|
47
46
|
|
|
48
47
|
def check_destination?
|
|
49
48
|
false
|
|
50
49
|
end
|
|
51
50
|
end
|
|
52
|
-
|
|
@@ -1,26 +1,25 @@
|
|
|
1
|
-
|
|
2
1
|
class Multisync::Definition::Template
|
|
3
2
|
include Multisync::Definition::Dsl
|
|
4
|
-
|
|
3
|
+
|
|
5
4
|
@registered = []
|
|
6
|
-
|
|
5
|
+
|
|
7
6
|
def self.register instance
|
|
8
7
|
@registered << instance
|
|
9
8
|
end
|
|
10
|
-
|
|
9
|
+
|
|
11
10
|
def self.lookup name
|
|
12
|
-
@registered.find {
|
|
11
|
+
@registered.find { _1.name == name }
|
|
13
12
|
end
|
|
14
|
-
|
|
13
|
+
|
|
15
14
|
# The name of the template
|
|
16
15
|
attr_reader :name
|
|
17
|
-
|
|
16
|
+
|
|
18
17
|
# The block the template holds
|
|
19
18
|
attr_reader :block
|
|
20
|
-
|
|
19
|
+
|
|
21
20
|
def initialize name, &block
|
|
22
21
|
@name = name
|
|
23
22
|
self.class.register self
|
|
24
23
|
@block = block
|
|
25
24
|
end
|
|
26
|
-
end
|
|
25
|
+
end
|
data/lib/multisync/definition.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
1
|
class Multisync::Definition
|
|
3
|
-
autoload :Dsl,
|
|
4
|
-
autoload :Entity,
|
|
5
|
-
autoload :Null,
|
|
6
|
-
autoload :Template,
|
|
7
|
-
end
|
|
2
|
+
autoload :Dsl, "multisync/definition/dsl"
|
|
3
|
+
autoload :Entity, "multisync/definition/entity"
|
|
4
|
+
autoload :Null, "multisync/definition/null"
|
|
5
|
+
autoload :Template, "multisync/definition/template"
|
|
6
|
+
end
|
data/lib/multisync/list.rb
CHANGED
|
@@ -1,44 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
require 'rainbow/ext/string'
|
|
3
|
-
|
|
4
1
|
class Multisync::List
|
|
5
|
-
|
|
2
|
+
include Multisync::Colors
|
|
3
|
+
|
|
6
4
|
# Given catalog
|
|
7
5
|
attr_reader :catalog
|
|
8
|
-
|
|
6
|
+
|
|
9
7
|
# Tasks
|
|
10
8
|
attr_reader :tasks
|
|
11
9
|
|
|
12
|
-
def initialize
|
|
13
|
-
@
|
|
14
|
-
@tasks = []
|
|
10
|
+
def initialize tasks
|
|
11
|
+
@tasks = tasks
|
|
15
12
|
end
|
|
16
|
-
|
|
13
|
+
|
|
17
14
|
def to_s
|
|
18
|
-
|
|
19
|
-
table.to_s
|
|
15
|
+
"\n" + table.to_s
|
|
20
16
|
end
|
|
21
|
-
|
|
17
|
+
|
|
22
18
|
def table
|
|
23
|
-
Terminal::Table.new(rows:
|
|
19
|
+
Terminal::Table.new(rows: rows, style: table_style)
|
|
24
20
|
end
|
|
25
|
-
|
|
26
|
-
def
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
|
|
22
|
+
def rows
|
|
23
|
+
tasks.map do |task|
|
|
24
|
+
next unless task.level > 0
|
|
25
|
+
|
|
26
|
+
indent = "".ljust(2 * (task.level - 1), " ")
|
|
27
|
+
name = task.executeable? ? task.name : "#{task.name}#{as_note("/")}"
|
|
28
|
+
default = task.default? ? as_note(" *") : ""
|
|
29
|
+
[
|
|
30
|
+
[indent, name, default].join,
|
|
31
|
+
*descriptions(task)
|
|
32
|
+
]
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
|
-
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
|
|
36
|
+
def descriptions task
|
|
37
|
+
if [task.source_description, task.destination_description].any?(&:empty?)
|
|
38
|
+
["", "", ""]
|
|
39
|
+
else
|
|
40
|
+
[task.source_description, "-->", task.destination_description].map(&method(:as_note))
|
|
41
|
+
end
|
|
39
42
|
end
|
|
40
43
|
|
|
41
44
|
def table_style
|
|
42
|
-
{
|
|
45
|
+
{border_x: as_note("─"), border_y: "", border_i: "", border_top: false, border_bottom: false, padding_left: 0, padding_right: 3}
|
|
43
46
|
end
|
|
44
47
|
end
|
data/lib/multisync/rsync_stat.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
require "filesize"
|
|
2
2
|
|
|
3
3
|
class Multisync::RsyncStat
|
|
4
|
-
|
|
5
4
|
# Keep track of totals
|
|
6
5
|
def self.total
|
|
7
6
|
@total ||= Hash.new 0
|
|
@@ -31,18 +30,18 @@ class Multisync::RsyncStat
|
|
|
31
30
|
|
|
32
31
|
# extracted returns a hash with labels as keys and extracted strings as values
|
|
33
32
|
# {
|
|
34
|
-
# "Number of files" => "35,648",
|
|
35
|
-
# "Number of created files" => "2,120",
|
|
33
|
+
# "Number of files" => "35,648",
|
|
34
|
+
# "Number of created files" => "2,120",
|
|
36
35
|
# ...
|
|
37
36
|
# }
|
|
38
37
|
def extracted
|
|
39
|
-
@extraced ||= @output.scan(/(#{labels.join(
|
|
38
|
+
@extraced ||= @output.scan(/(#{labels.join("|")}):\s+([,0-9]+)/).to_h
|
|
40
39
|
end
|
|
41
40
|
|
|
42
41
|
# stats returns a hash with the follwing keys (and updates class total)
|
|
43
42
|
# {
|
|
44
|
-
# "Number of files" => 35648,
|
|
45
|
-
# "Number of created files" => 2120,
|
|
43
|
+
# "Number of files" => 35648,
|
|
44
|
+
# "Number of created files" => 2120,
|
|
46
45
|
# "Number of deleted files" => 37,
|
|
47
46
|
# "Number of regular files transferred" => 394,
|
|
48
47
|
# "Total file size" => 204936349,
|
|
@@ -81,12 +80,12 @@ class Multisync::RsyncStat
|
|
|
81
80
|
|
|
82
81
|
def self.format_map
|
|
83
82
|
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
"Number of files" => to_numbers,
|
|
84
|
+
"Number of created files" => to_numbers,
|
|
85
|
+
"Number of deleted files" => to_numbers,
|
|
86
|
+
"Number of regular files transferred" => to_numbers,
|
|
87
|
+
"Total file size" => to_filesize,
|
|
88
|
+
"Total transferred file size" => to_filesize
|
|
90
89
|
}
|
|
91
90
|
end
|
|
92
91
|
|
|
@@ -97,4 +96,4 @@ class Multisync::RsyncStat
|
|
|
97
96
|
def self.to_filesize
|
|
98
97
|
->(x) { Filesize.new(x).pretty }
|
|
99
98
|
end
|
|
100
|
-
end
|
|
99
|
+
end
|
data/lib/multisync/runtime.rb
CHANGED
|
@@ -1,82 +1,77 @@
|
|
|
1
|
-
|
|
2
|
-
require 'mixlib/shellout'
|
|
1
|
+
require "mixlib/shellout"
|
|
3
2
|
|
|
4
3
|
class Multisync::Runtime
|
|
4
|
+
include Multisync::Colors
|
|
5
5
|
|
|
6
6
|
# Runtime options
|
|
7
7
|
# dryrun: true|false
|
|
8
8
|
# show: true|false
|
|
9
9
|
attr_reader :options
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
def initialize options
|
|
12
12
|
@options = options
|
|
13
13
|
end
|
|
14
|
-
|
|
15
|
-
def dryrun?
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def quiet?
|
|
24
|
-
options[:quiet]
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def timeout
|
|
28
|
-
options[:timeout]
|
|
29
|
-
end
|
|
30
|
-
|
|
14
|
+
|
|
15
|
+
def dryrun? = options[:dryrun]
|
|
16
|
+
|
|
17
|
+
def show_only? = options[:print]
|
|
18
|
+
|
|
19
|
+
def quiet? = options[:quiet]
|
|
20
|
+
|
|
21
|
+
def timeout = options[:timeout]
|
|
22
|
+
|
|
31
23
|
def run sync
|
|
32
24
|
rsync_options = sync.rsync_options.dup
|
|
33
|
-
rsync_options.unshift
|
|
34
|
-
rsync_options.unshift
|
|
35
|
-
rsync_options.unshift
|
|
25
|
+
rsync_options.unshift "--stats"
|
|
26
|
+
rsync_options.unshift "--verbose" unless quiet?
|
|
27
|
+
rsync_options.unshift "--dry-run" if dryrun?
|
|
36
28
|
|
|
37
29
|
# escape path by hand, shellescape escapes also ~, but we want to keep its
|
|
38
30
|
# special meaning for home, instead of passing it as literal char
|
|
39
|
-
source, destination = [sync.source, sync.destination].map {
|
|
40
|
-
cmd = "rsync #{rsync_options.join(
|
|
41
|
-
cmd_options = {
|
|
42
|
-
|
|
31
|
+
source, destination = [sync.source, sync.destination].map { _1.gsub(/\s+/, "\\ ") }
|
|
32
|
+
cmd = "rsync #{rsync_options.join(" ")} #{source} #{destination}"
|
|
33
|
+
cmd_options = {timeout: timeout}
|
|
34
|
+
unless quiet?
|
|
35
|
+
cmd_options[:live_stdout] = $stdout
|
|
36
|
+
cmd_options[:live_stderr] = $stderr
|
|
37
|
+
end
|
|
43
38
|
rsync = Mixlib::ShellOut.new(cmd, cmd_options)
|
|
44
39
|
sync.result[:cmd] = rsync.command
|
|
45
40
|
|
|
46
41
|
unless quiet?
|
|
47
42
|
puts
|
|
48
|
-
puts [sync.source_description, sync.destination_description].join(
|
|
43
|
+
puts as_main([sync.source_description, sync.destination_description].join(" --> "))
|
|
49
44
|
end
|
|
50
|
-
|
|
45
|
+
|
|
51
46
|
# Perform all only_if checks, from top to bottom
|
|
52
47
|
sync.checks.each do |check|
|
|
53
48
|
next unless Mixlib::ShellOut.new(check[:cmd]).run_command.error?
|
|
54
49
|
|
|
55
|
-
puts check[:cmd]
|
|
56
|
-
puts "Skip:
|
|
50
|
+
puts as_skipped("#{check[:cmd]} (failed)")
|
|
51
|
+
puts as_note("Skip: #{rsync.command}")
|
|
57
52
|
sync.result[:action] = :skip
|
|
58
53
|
sync.result[:skip_message] = check[:message]
|
|
59
|
-
return
|
|
54
|
+
return false
|
|
60
55
|
end
|
|
61
|
-
|
|
56
|
+
|
|
62
57
|
# source check
|
|
63
|
-
if sync.check_source? && !
|
|
64
|
-
puts "Source #{sync.source} is not accessible"
|
|
65
|
-
puts "Skip:
|
|
58
|
+
if sync.check_source? && !check_path(sync.source, :source)
|
|
59
|
+
puts as_skipped("Source #{sync.source} is not accessible")
|
|
60
|
+
puts as_note("Skip: #{rsync.command}")
|
|
66
61
|
sync.result[:action] = :skip
|
|
67
62
|
sync.result[:skip_message] = "Source is not accessible"
|
|
68
63
|
return
|
|
69
64
|
end
|
|
70
|
-
|
|
65
|
+
|
|
71
66
|
# target check
|
|
72
|
-
if sync.check_destination? && !
|
|
73
|
-
puts "Destination #{sync.destination} is not accessible"
|
|
74
|
-
puts "Skip:
|
|
67
|
+
if sync.check_destination? && !check_path(sync.destination, :destination)
|
|
68
|
+
puts as_skipped("Destination #{sync.destination} is not accessible")
|
|
69
|
+
puts as_note("Skip: #{rsync.command}")
|
|
75
70
|
sync.result[:action] = :skip
|
|
76
71
|
sync.result[:skip_message] = "Destination is not accessible"
|
|
77
72
|
return
|
|
78
73
|
end
|
|
79
|
-
|
|
74
|
+
|
|
80
75
|
if show_only?
|
|
81
76
|
puts rsync.command
|
|
82
77
|
else
|
|
@@ -88,20 +83,20 @@ class Multisync::Runtime
|
|
|
88
83
|
sync.result[:stderr] = rsync.stderr
|
|
89
84
|
end
|
|
90
85
|
end
|
|
91
|
-
|
|
86
|
+
|
|
92
87
|
# checks a path
|
|
93
88
|
# if path includes a host, the reachability of the host will be checked
|
|
94
89
|
# the existence of the remote path will not be checked
|
|
95
90
|
# if path is a local source path, its existence will be checked
|
|
96
91
|
# if path is a local destination path, the existence of the parent will be checked
|
|
97
92
|
def check_path path, type = :source
|
|
98
|
-
if path.include?
|
|
99
|
-
host = path.split(
|
|
93
|
+
if path.include? ":"
|
|
94
|
+
host = path.split(":").first.split("@").last
|
|
100
95
|
Mixlib::ShellOut.new("ping -o -t 1 #{host}").run_command.status.success?
|
|
101
96
|
else
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
97
|
+
File.expand_path(path)
|
|
98
|
+
.then { (type == :destination) ? File.dirname(_1) : _1 }
|
|
99
|
+
.then { File.exist? _1 }
|
|
105
100
|
end
|
|
106
101
|
end
|
|
107
|
-
end
|
|
102
|
+
end
|
data/lib/multisync/selector.rb
CHANGED
|
@@ -1,36 +1,58 @@
|
|
|
1
|
-
|
|
2
1
|
class Multisync::Selector
|
|
3
|
-
|
|
4
2
|
# Given catalog
|
|
5
3
|
attr_reader :catalog
|
|
6
|
-
|
|
7
|
-
# Given
|
|
8
|
-
attr_reader :
|
|
9
|
-
|
|
4
|
+
|
|
5
|
+
# Given queries
|
|
6
|
+
attr_reader :queries
|
|
7
|
+
|
|
10
8
|
# Selected tasks
|
|
11
|
-
attr_reader :
|
|
9
|
+
attr_reader :results
|
|
12
10
|
|
|
13
|
-
def initialize catalog,
|
|
11
|
+
def initialize catalog, queries
|
|
14
12
|
@catalog = catalog
|
|
15
|
-
@
|
|
16
|
-
@
|
|
13
|
+
@queries = queries
|
|
14
|
+
@results = []
|
|
15
|
+
@all_subjects = []
|
|
16
|
+
@subjects_by_name = []
|
|
17
17
|
end
|
|
18
|
-
|
|
19
|
-
def tasks
|
|
18
|
+
|
|
19
|
+
def tasks parents: false
|
|
20
20
|
catalog.traverse self
|
|
21
|
-
|
|
21
|
+
parents ? selected_with_parents : selected
|
|
22
22
|
end
|
|
23
|
-
|
|
24
|
-
def visit subject
|
|
25
|
-
|
|
23
|
+
|
|
24
|
+
def visit subject
|
|
25
|
+
results << subject
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def selected_with_parents
|
|
29
|
+
@selected_with_parents ||= results.select { selected_or_parent_of_selected? _1 }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def selected_or_parent_of_selected? subject
|
|
33
|
+
!subject.fullname.empty? &&
|
|
34
|
+
selected_fullnames.any? { %r{^#{subject.fullname}(?:/|$)}.match _1 }
|
|
26
35
|
end
|
|
27
|
-
|
|
36
|
+
|
|
37
|
+
def selected_fullnames
|
|
38
|
+
@selected_fullnames ||= selected.map(&:fullname)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def selected
|
|
42
|
+
@selected ||= results.select { selected? _1 }
|
|
43
|
+
end
|
|
44
|
+
|
|
28
45
|
def selected? subject
|
|
46
|
+
# return only subjects with a fullname
|
|
47
|
+
return false if subject.fullname.empty?
|
|
48
|
+
|
|
49
|
+
# no queries defined, but subject is in the default set
|
|
50
|
+
return true if queries.empty? && subject.default?
|
|
51
|
+
|
|
29
52
|
# only return the leaves of the definition tree
|
|
30
|
-
return false unless subject.members.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
sets.any? {|set| /\b#{set}\b/.match subject.fullname }
|
|
53
|
+
# return false unless subject.members.any?
|
|
54
|
+
|
|
55
|
+
# subject matches any of the given queries
|
|
56
|
+
queries.any? { /\b#{_1}\b/.match subject.fullname }
|
|
35
57
|
end
|
|
36
58
|
end
|