multisync 0.3.6 → 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/{History.md → CHANGELOG.md} +73 -12
 - data/LICENSE.txt +1 -1
 - data/README.md +14 -11
 - data/Rakefile +9 -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 +89 -48
 - data/lib/multisync/runtime.rb +43 -48
 - data/lib/multisync/selector.rb +44 -22
 - data/lib/multisync/summary.rb +56 -21
 - data/lib/multisync/version.rb +3 -1
 - data/lib/multisync.rb +14 -9
 - data/sample/multisync.rb +10 -18
 - metadata +12 -59
 - data/.gitignore +0 -13
 - data/Gemfile +0 -5
 - data/bin/console +0 -14
 - data/bin/setup +0 -8
 - data/multisync.gemspec +0 -45
 
| 
         @@ -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,58 +1,99 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
             
     | 
| 
       2 
     | 
    
         
            -
            require 'filesize'
         
     | 
| 
      
 1 
     | 
    
         
            +
            require "filesize"
         
     | 
| 
       3 
2 
     | 
    
         | 
| 
       4 
3 
     | 
    
         
             
            class Multisync::RsyncStat
         
     | 
| 
       5 
     | 
    
         
            -
              
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Keep track of totals
         
     | 
| 
      
 5 
     | 
    
         
            +
              def self.total
         
     | 
| 
      
 6 
     | 
    
         
            +
                @total ||= Hash.new 0
         
     | 
| 
      
 7 
     | 
    
         
            +
              end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              # Sample output
         
     | 
| 
      
 10 
     | 
    
         
            +
              #   ...
         
     | 
| 
      
 11 
     | 
    
         
            +
              #   Number of files: 89,633 (reg: 59,322, dir: 30,311)
         
     | 
| 
      
 12 
     | 
    
         
            +
              #   Number of created files: 356 (reg: 281, dir: 75)
         
     | 
| 
      
 13 
     | 
    
         
            +
              #   Number of deleted files: 114 (reg: 101, dir: 13)
         
     | 
| 
      
 14 
     | 
    
         
            +
              #   Number of regular files transferred: 344
         
     | 
| 
      
 15 
     | 
    
         
            +
              #   Total file size: 546,410,522,192 bytes
         
     | 
| 
      
 16 
     | 
    
         
            +
              #   Total transferred file size: 7,991,491,676 bytes
         
     | 
| 
      
 17 
     | 
    
         
            +
              #   Literal data: 7,952,503,842 bytes
         
     | 
| 
      
 18 
     | 
    
         
            +
              #   Matched data: 38,987,834 bytes
         
     | 
| 
      
 19 
     | 
    
         
            +
              #   File list size: 3,063,808
         
     | 
| 
      
 20 
     | 
    
         
            +
              #   File list generation time: 2.414 seconds
         
     | 
| 
      
 21 
     | 
    
         
            +
              #   File list transfer time: 0.000 seconds
         
     | 
| 
      
 22 
     | 
    
         
            +
              #   Total bytes sent: 7,957,645,803
         
     | 
| 
      
 23 
     | 
    
         
            +
              #   Total bytes received: 101,299
         
     | 
| 
      
 24 
     | 
    
         
            +
              #
         
     | 
| 
      
 25 
     | 
    
         
            +
              #   sent 7,957,645,803 bytes  received 101,299 bytes  23,719,067.37 bytes/sec
         
     | 
| 
      
 26 
     | 
    
         
            +
              #   total size is 546,410,522,192  speedup is 68.66
         
     | 
| 
       6 
27 
     | 
    
         
             
              def initialize output
         
     | 
| 
       7 
28 
     | 
    
         
             
                @output = output
         
     | 
| 
       8 
29 
     | 
    
         
             
              end
         
     | 
| 
       9 
30 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
              #  
     | 
| 
       11 
     | 
    
         
            -
              def parse
         
     | 
| 
       12 
     | 
    
         
            -
                @stats = definitions.each_with_object({}) do |definition, stats|
         
     | 
| 
       13 
     | 
    
         
            -
                  value = scan[definition[:match]]
         
     | 
| 
       14 
     | 
    
         
            -
                  stats[definition[:key]] = value ? (definition[:coerce] ? definition[:coerce].call(value) : value) : definition[:default]
         
     | 
| 
       15 
     | 
    
         
            -
                end
         
     | 
| 
       16 
     | 
    
         
            -
                self
         
     | 
| 
       17 
     | 
    
         
            -
              end
         
     | 
| 
       18 
     | 
    
         
            -
              
         
     | 
| 
       19 
     | 
    
         
            -
              # Scan output and return a hash
         
     | 
| 
      
 31 
     | 
    
         
            +
              # extracted returns a hash with labels as keys and extracted strings as values
         
     | 
| 
       20 
32 
     | 
    
         
             
              #   {
         
     | 
| 
       21 
33 
     | 
    
         
             
              #     "Number of files" => "35,648",
         
     | 
| 
       22 
     | 
    
         
            -
              #     "Number of created files" => " 
     | 
| 
       23 
     | 
    
         
            -
              #     "Number of deleted files" => "0",
         
     | 
| 
       24 
     | 
    
         
            -
              #     "Number of regular files transferred"=>"0",
         
     | 
| 
      
 34 
     | 
    
         
            +
              #     "Number of created files" => "2,120",
         
     | 
| 
       25 
35 
     | 
    
         
             
              #     ...
         
     | 
| 
       26 
36 
     | 
    
         
             
              #   }
         
     | 
| 
       27 
     | 
    
         
            -
              def  
     | 
| 
       28 
     | 
    
         
            -
                @ 
     | 
| 
       29 
     | 
    
         
            -
              end
         
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
               
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
               
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
              
         
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
                   
     | 
| 
       56 
     | 
    
         
            -
                 
     | 
| 
       57 
     | 
    
         
            -
              end
         
     | 
| 
       58 
     | 
    
         
            -
             
     | 
| 
      
 37 
     | 
    
         
            +
              def extracted
         
     | 
| 
      
 38 
     | 
    
         
            +
                @extraced ||= @output.scan(/(#{labels.join("|")}):\s+([,0-9]+)/).to_h
         
     | 
| 
      
 39 
     | 
    
         
            +
              end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              # stats returns a hash with the follwing keys (and updates class total)
         
     | 
| 
      
 42 
     | 
    
         
            +
              #   {
         
     | 
| 
      
 43 
     | 
    
         
            +
              #     "Number of files" => 35648,
         
     | 
| 
      
 44 
     | 
    
         
            +
              #     "Number of created files" => 2120,
         
     | 
| 
      
 45 
     | 
    
         
            +
              #     "Number of deleted files" => 37,
         
     | 
| 
      
 46 
     | 
    
         
            +
              #     "Number of regular files transferred" => 394,
         
     | 
| 
      
 47 
     | 
    
         
            +
              #     "Total file size" => 204936349,
         
     | 
| 
      
 48 
     | 
    
         
            +
              #     "Total transferred file size" => 49239,
         
     | 
| 
      
 49 
     | 
    
         
            +
              #   }
         
     | 
| 
      
 50 
     | 
    
         
            +
              def stats
         
     | 
| 
      
 51 
     | 
    
         
            +
                @stats ||= labels.each_with_object({}) do |label, h|
         
     | 
| 
      
 52 
     | 
    
         
            +
                  value = extracted[label]&.delete(",").to_i
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  self.class.total[label] += value # update total
         
     | 
| 
      
 55 
     | 
    
         
            +
                  h[label] = value
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
              end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
              def labels
         
     | 
| 
      
 60 
     | 
    
         
            +
                self.class.format_map.keys
         
     | 
| 
      
 61 
     | 
    
         
            +
              end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
              def formatted_values
         
     | 
| 
      
 64 
     | 
    
         
            +
                self.class.format_values do |label|
         
     | 
| 
      
 65 
     | 
    
         
            +
                  stats[label]
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
              end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
              def self.formatted_totals
         
     | 
| 
      
 70 
     | 
    
         
            +
                format_values do |label|
         
     | 
| 
      
 71 
     | 
    
         
            +
                  total[label]
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
              end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
              def self.format_values
         
     | 
| 
      
 76 
     | 
    
         
            +
                format_map.map do |label, format|
         
     | 
| 
      
 77 
     | 
    
         
            +
                  format.call(yield label)
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
              end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
              def self.format_map
         
     | 
| 
      
 82 
     | 
    
         
            +
                {
         
     | 
| 
      
 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
         
     | 
| 
      
 89 
     | 
    
         
            +
                }
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
              def self.to_numbers
         
     | 
| 
      
 93 
     | 
    
         
            +
                ->(x) { x.to_s.gsub(/\B(?=(...)*\b)/, "'") }
         
     | 
| 
      
 94 
     | 
    
         
            +
              end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
              def self.to_filesize
         
     | 
| 
      
 97 
     | 
    
         
            +
                ->(x) { Filesize.new(x).pretty }
         
     | 
| 
      
 98 
     | 
    
         
            +
              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
         
     | 
    
        data/lib/multisync/summary.rb
    CHANGED
    
    | 
         @@ -1,55 +1,90 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
             
     | 
| 
       2 
1 
     | 
    
         
             
            class Multisync::Summary
         
     | 
| 
       3 
     | 
    
         
            -
              
         
     | 
| 
      
 2 
     | 
    
         
            +
              include Multisync::Colors
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
       4 
4 
     | 
    
         
             
              # All tasks to include in the summary
         
     | 
| 
       5 
5 
     | 
    
         
             
              attr_reader :tasks
         
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
       7 
7 
     | 
    
         
             
              def initialize tasks
         
     | 
| 
       8 
8 
     | 
    
         
             
                @tasks = tasks
         
     | 
| 
       9 
9 
     | 
    
         
             
              end
         
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
       11 
11 
     | 
    
         
             
              def to_s
         
     | 
| 
       12 
     | 
    
         
            -
                table.to_s
         
     | 
| 
      
 12 
     | 
    
         
            +
                ["", as_main("Summary"), table.to_s, failures].compact.join("\n")
         
     | 
| 
       13 
13 
     | 
    
         
             
              end
         
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
       15 
15 
     | 
    
         
             
              def table
         
     | 
| 
       16 
16 
     | 
    
         
             
                Terminal::Table.new(headings: headings, rows: data, style: table_style)
         
     | 
| 
       17 
17 
     | 
    
         
             
              end
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
       19 
19 
     | 
    
         
             
              def headings
         
     | 
| 
       20 
     | 
    
         
            -
                %w 
     | 
| 
      
 20 
     | 
    
         
            +
                %w[SOURCE DESTINATION FILES + - → ∑ ↑]
         
     | 
| 
      
 21 
     | 
    
         
            +
                  .map(&method(:as_note))
         
     | 
| 
      
 22 
     | 
    
         
            +
                  .zip(%i[left left right right right right right right])
         
     | 
| 
      
 23 
     | 
    
         
            +
                  .map { |v, a| {value: v, alignment: a} }
         
     | 
| 
       21 
24 
     | 
    
         
             
              end
         
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
       23 
26 
     | 
    
         
             
              def data
         
     | 
| 
       24 
     | 
    
         
            -
                # Exclude tasks with an empty result (> not run) first
         
     | 
| 
       25 
27 
     | 
    
         
             
                tasks.map do |task|
         
     | 
| 
       26 
28 
     | 
    
         
             
                  result = task.result
         
     | 
| 
       27 
     | 
    
         
            -
                  desc = [task.source_description,  
     | 
| 
      
 29 
     | 
    
         
            +
                  desc = [task.source_description, task.destination_description]
         
     | 
| 
       28 
30 
     | 
    
         | 
| 
       29 
31 
     | 
    
         
             
                  case result[:action]
         
     | 
| 
       30 
32 
     | 
    
         
             
                  when :run
         
     | 
| 
       31 
     | 
    
         
            -
                    if result[:status] 
     | 
| 
      
 33 
     | 
    
         
            +
                    if result[:status]&.success?
         
     | 
| 
       32 
34 
     | 
    
         
             
                      # successfull run
         
     | 
| 
       33 
     | 
    
         
            -
                       
     | 
| 
       34 
     | 
    
         
            -
                      [*desc, * 
     | 
| 
      
 35 
     | 
    
         
            +
                      stats = Multisync::RsyncStat.new(result[:stdout])
         
     | 
| 
      
 36 
     | 
    
         
            +
                      [*desc, *stats.formatted_values.map { {value: as_success(_1), alignment: :right} }]
         
     | 
| 
       35 
37 
     | 
    
         
             
                    else
         
     | 
| 
       36 
38 
     | 
    
         
             
                      # failed or interrupted run
         
     | 
| 
       37 
     | 
    
         
            -
                      [*desc,  
     | 
| 
      
 39 
     | 
    
         
            +
                      [*desc, as_message(as_fail("Failed, for more information see details below"))]
         
     | 
| 
      
 40 
     | 
    
         
            +
                      # [*desc, as_message(as_fail(result[:stderr] || "n/a").strip)]
         
     | 
| 
       38 
41 
     | 
    
         
             
                    end
         
     | 
| 
       39 
42 
     | 
    
         | 
| 
       40 
43 
     | 
    
         
             
                  when :skip
         
     | 
| 
       41 
44 
     | 
    
         
             
                    # skiped sync
         
     | 
| 
       42 
     | 
    
         
            -
                    [*desc,  
     | 
| 
      
 45 
     | 
    
         
            +
                    [*desc, as_message(as_skipped(result[:skip_message]))]
         
     | 
| 
       43 
46 
     | 
    
         | 
| 
       44 
47 
     | 
    
         
             
                  else
         
     | 
| 
       45 
48 
     | 
    
         
             
                    # not executed
         
     | 
| 
       46 
     | 
    
         
            -
                    [*desc,  
     | 
| 
      
 49 
     | 
    
         
            +
                    [*desc, as_message(as_note("not executed"))]
         
     | 
| 
       47 
50 
     | 
    
         
             
                  end
         
     | 
| 
       48 
     | 
    
         
            -
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
                end.push(
         
     | 
| 
      
 52 
     | 
    
         
            +
                  [
         
     | 
| 
      
 53 
     | 
    
         
            +
                    as_note("Total"),
         
     | 
| 
      
 54 
     | 
    
         
            +
                    "",
         
     | 
| 
      
 55 
     | 
    
         
            +
                    *Multisync::RsyncStat
         
     | 
| 
      
 56 
     | 
    
         
            +
                      .formatted_totals
         
     | 
| 
      
 57 
     | 
    
         
            +
                      .map { {value: as_note(_1), alignment: :right} }
         
     | 
| 
      
 58 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 59 
     | 
    
         
            +
                )
         
     | 
| 
       49 
60 
     | 
    
         
             
              end
         
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
              def  
     | 
| 
       52 
     | 
    
         
            -
                 
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
              def failures
         
     | 
| 
      
 63 
     | 
    
         
            +
                tasks
         
     | 
| 
      
 64 
     | 
    
         
            +
                  .select { _1.result[:action] == :run && !_1.result[:status]&.success? }
         
     | 
| 
      
 65 
     | 
    
         
            +
                  .flat_map do |task|
         
     | 
| 
      
 66 
     | 
    
         
            +
                    [
         
     | 
| 
      
 67 
     | 
    
         
            +
                      as_fail([task.source_description, task.destination_description].join(" --> ")),
         
     | 
| 
      
 68 
     | 
    
         
            +
                      message_or_nil(task.result[:stdout]),
         
     | 
| 
      
 69 
     | 
    
         
            +
                      message_or_nil(task.result[:stderr]),
         
     | 
| 
      
 70 
     | 
    
         
            +
                      ""
         
     | 
| 
      
 71 
     | 
    
         
            +
                    ].compact
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
                  # Add title if any failures
         
     | 
| 
      
 74 
     | 
    
         
            +
                  .tap { _1.unshift "\n#{as_main("Failures")}" if _1.any? }
         
     | 
| 
      
 75 
     | 
    
         
            +
                  # Return failures as string or nil
         
     | 
| 
      
 76 
     | 
    
         
            +
                  .then { _1.any? ? _1.join("\n") : nil }
         
     | 
| 
      
 77 
     | 
    
         
            +
              end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
              def as_message message
         
     | 
| 
      
 80 
     | 
    
         
            +
                {value: message, colspan: 6}
         
     | 
| 
       53 
81 
     | 
    
         
             
              end
         
     | 
| 
       54 
82 
     | 
    
         | 
| 
      
 83 
     | 
    
         
            +
              def message_or_nil message
         
     | 
| 
      
 84 
     | 
    
         
            +
                (message.nil? || message.empty?) ? nil : message
         
     | 
| 
      
 85 
     | 
    
         
            +
              end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
              def table_style
         
     | 
| 
      
 88 
     | 
    
         
            +
                {border_x: as_note("─"), border_y: "", border_i: "", border_top: false, border_bottom: false, padding_left: 0, padding_right: 3}
         
     | 
| 
      
 89 
     | 
    
         
            +
              end
         
     | 
| 
       55 
90 
     | 
    
         
             
            end
         
     | 
    
        data/lib/multisync/version.rb
    CHANGED