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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d0293de3d6bf07fd58fcaf551c5469aad676d1b51682dcf58a59b85024dbe1f5
4
- data.tar.gz: 55170bd7e42c82b2f3e3dcb7c5a9caede2ad53980611f209d7adad2c2a613ffc
3
+ metadata.gz: e799810bb5e357087f31733c9b885581a349832f6806a94e726d581c49e044b6
4
+ data.tar.gz: b2682c3578aa9381ee3e3b0b36fc6d4566462cea8c1c10a5458ae6951f959c87
5
5
  SHA512:
6
- metadata.gz: bf970e9e365204d4af64b975ec165ba616f17bcf90fb6e752cf22348dccf8c515d57471ea22b4410bee8a38fa2de08030922835b8d43eac1bcf88c434bd187a5
7
- data.tar.gz: 4ce7a0b3dc4d9317883558e2c137c2da63e2ed4c8a9a103dffa6e4d24a3ae74f15af04ac07036f4868dc9e5425b769503d5be514df213035d1d34a0e5ab8b46c
6
+ metadata.gz: f9fbf97fda456b7d7f29f5822321960e1eab877d609c6456905039b05d8908010faae42967d1cd2e2644b1196611fcea0cabf5198247372d439aeece761bc827
7
+ data.tar.gz: 457c9cbf9c5c4a8ed1c06574594dc348e2d1fdf2febf75372ba96adbf31c76bb3734a87165cc30ca3a1825f8ab8c159691bef641a242686f5cb5a7dbc5d98089
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/standardrb/standard
3
+ ruby_version: 3.1
@@ -1,7 +1,48 @@
1
+ ## [0.4.0] - 2025-11-02
1
2
 
2
- # multisync
3
+ - New CLI
4
+ - List and start accept the same query argument to select the catalog tasks
5
+ - New option to omit summary
6
+ - New failure summary after task summary
7
+ - Adjustment in output colors
8
+ - Breaking change: to run tasks you must call multisync start
9
+
10
+
11
+ ## [0.3.7] - 2021-10-28
12
+
13
+ - Add grand total to summary
14
+
15
+
16
+ ## [0.3.6] - 2019-08-20
17
+
18
+ - Tweak output when specifing --print and/or --quiet
19
+
20
+
21
+ ## [0.3.5] - 2019-08-13
22
+
23
+ - Fix option quiet
24
+ - Catch SIGINT and print a summary after an interrupt
25
+
26
+
27
+ ## [0.3.4] - 2019-07-23
28
+
29
+ - Update help
30
+ - Update readme
31
+ - Update sample file
32
+
33
+
34
+ ## [0.3.3] - 2019-07-23
35
+
36
+ - Update gem description
37
+
38
+
39
+ ## [0.3.2] - 2019-07-23
40
+
41
+ - First public release
42
+
43
+
44
+ ## [0.3.1] - 2018-06-01
3
45
 
4
- ## v0.3.1 (2018-06-01)
5
46
  Changes in DSL
6
47
  - removed "desc"
7
48
  - "from" accepts a description option
@@ -13,38 +54,58 @@ Changes in CLI
13
54
  - New: quiet option
14
55
  - polished output
15
56
 
16
- ## v0.2.4 (2017-09-23)
57
+
58
+ ## [0.2.4] - 2017-09-23
59
+
17
60
  - Fix: check command
18
61
 
19
- ## v0.2.3 (2017-09-23)
62
+
63
+ ## [0.2.3] - 2017-09-23
64
+
20
65
  - Fix: check remote path
21
66
 
22
- ## v0.2.2 (2017-09-23)
67
+
68
+ ## [0.2.2] - 2017-09-23
69
+
23
70
  - replaced shell_cmd gem with mixlib-shellout
24
71
  - Fix: check path for paths containing spaces
25
72
 
26
- ## v0.2.1 (2017-09-22)
73
+
74
+ ## [0.2.1] - 2017-09-22
75
+
27
76
  - New: option "check_from" and "check_to" to let check host or path before sync
28
77
  - New: "from" and "to" accept an optional check: true|false parameter
29
78
  - Change summery output to a more compact tabular form
30
79
  - Use rainbow for colorization
31
80
  - Move "only_if" checks to runtime
32
81
 
33
- ## v0.2.0 (2016-03-22)
82
+
83
+ ## [0.2.0] - 2016-03-22
84
+
34
85
  - New: option only_if for preflight checks, prior to sync
35
86
  - Command line option -p/--print changed to --show
36
87
 
37
- ## v0.1.2 (2014-08-29)
88
+
89
+ ## [0.1.2] - 2014-08-29
90
+
38
91
  - Mark default sets with an * when listing
39
92
 
40
- ## v0.1.1 (2014-08-28)
93
+
94
+ ## [0.1.1] - 2014-08-28
95
+
41
96
  - Add gem dependecy 'text-highlight'
42
97
 
43
- ## v0.1.0 (2014-07-18)
98
+
99
+ ## [0.1.0] - 2014-07-18
100
+
44
101
  - New: define one or more groups/syncs as default, to run when no sets have been given as args
45
102
 
46
- ## v0.0.2 (2014-07-17)
103
+
104
+ ## [0.0.2] - 2014-07-17
105
+
47
106
  - Fix: do no escape option strings
48
107
 
49
- ## v0.0.1 (2014-07-11)
108
+
109
+ ## [0.0.1] - 2014-07-11
110
+
50
111
  - First release
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2019 Patrick Marchi
3
+ Copyright (c) 2025 Patrick Marchi
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -7,9 +7,17 @@ Multisync offers a DSL to organize sets of rsync tasks. It takes advantage of te
7
7
 
8
8
  ## Installation
9
9
 
10
- Install multisync with:
10
+ Install the gem and add to the application's Gemfile by executing:
11
11
 
12
- $ gem install multisync
12
+ ```bash
13
+ bundle add multisync
14
+ ```
15
+
16
+ If bundler is not being used to manage dependencies, install the gem by executing:
17
+
18
+ ```bash
19
+ gem install multisync
20
+ ```
13
21
 
14
22
 
15
23
  ## Usage
@@ -18,24 +26,19 @@ In order to run multisync you first need a catalog file (default: `~/.multisync.
18
26
 
19
27
  List your configuration (and check your catalog file for errors):
20
28
 
21
- $ multisync -l
22
-
23
-
24
- Print out the rsync commands without executing them:
25
-
26
- $ multisync -p
29
+ $ multisync list [QUERY ...]
27
30
 
28
31
 
29
- Run a group or task defined in your catalog file:
32
+ Run the tasks defined in your catalog file:
30
33
 
31
- $ multisync nas
34
+ $ multisync start [QUERY ...]
32
35
 
33
36
 
34
37
  ## Development
35
38
 
36
39
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
37
40
 
38
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
41
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
39
42
 
40
43
 
41
44
  ## Contributing
data/Rakefile CHANGED
@@ -1,2 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
- task :default => :spec
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[spec standard]
data/exe/multisync CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'multisync'
3
+ require "multisync"
4
4
 
5
- Multisync::Cli.start
5
+ Multisync::Cli.start ARGV
@@ -1,29 +1,26 @@
1
-
2
1
  class Multisync::Catalog
3
- # top entity of definition
4
- attr_reader :definition
5
-
6
2
  def initialize path
7
3
  @path = File.expand_path(path)
8
4
  end
9
-
5
+
6
+ # top entity of definition
10
7
  def definition
11
- @_definition ||= Multisync::Definition::Entity.new(Multisync::Definition::Null.new, '__MAIN__').tap do |e|
12
- e.instance_eval File.read(path)
13
- end
8
+ @definition ||= Multisync::Definition::Entity
9
+ .new(Multisync::Definition::Null.new, "")
10
+ .tap { _1.instance_eval File.read(path) }
14
11
  end
15
-
12
+
16
13
  def traverse visitor
17
14
  definition.accept visitor
18
15
  end
19
-
16
+
20
17
  def path
21
18
  return @path if File.exist? @path
22
- sample_path = File.expand_path('../../../sample/multisync.rb', __FILE__)
19
+ sample_path = File.expand_path("../../../sample/multisync.rb", __FILE__)
23
20
  raise RuntimeError.new, "No catalog found at #{@path}. Copy sample from #{sample_path} to #{@path} and adjust to your needs."
24
21
  end
25
-
22
+
26
23
  def self.default_catalog_path
27
- '~/.multisync.rb'
24
+ "~/.multisync.rb"
28
25
  end
29
26
  end
data/lib/multisync/cli.rb CHANGED
@@ -1,100 +1,72 @@
1
+ require "thor"
2
+ require "rainbow"
3
+ require "terminal-table"
1
4
 
2
- require 'optparse'
3
- require 'rainbow/ext/string'
4
- require 'terminal-table'
5
+ class Multisync::Cli < Thor
6
+ include Thor::Actions
7
+ include Multisync::Colors
5
8
 
6
- class Multisync::Cli
7
-
8
- def self.start
9
- new.start
10
- end
11
-
12
- # Given sets to run or empty
13
- attr_reader :sets
14
-
15
- def parser
16
- OptionParser.new do |o|
17
- o.banner = "\nRun rsync jobs defined in the catalog file '#{options[:file]}'.\n\n" +
18
- "Usage: #{File.basename $0} [options] [SET] [...]\n\n" +
19
- " SET selects a section from the catalog (see option -l)\n" +
20
- " use / as a group/task separator.\n" +
21
- " e.g. #{File.basename $0} nas/userdata"
22
- o.separator ''
23
- o.on('-l', '--list', "List the catalog") do
24
- options[:list] = true
25
- end
26
- o.on('-p', '--print', "Print the commands without executing them") do
27
- options[:print] = true
28
- end
29
- o.on('-q', '--quiet', "Show only rsync summary") do
30
- options[:quiet] = true
31
- end
32
- o.on('--catalog FILE', "Specify a catalog", "Default is #{options[:file]}") do |file|
33
- options[:file] = file
34
- end
35
- o.on('--timeout SECS', Integer, "Timeout for rsync job", "Default is #{options[:timeout]}") do |timeout|
36
- options[:timeout] = timeout
37
- end
38
- o.on('-n', '--dryrun', "Run rsync in dry-run mode") do
39
- options[:dryrun] = true
40
- end
41
- o.separator ''
42
- end
9
+ def self.exit_on_failure? = true
10
+
11
+ class_option :catalog, aliases: "-f", default: Multisync::Catalog.default_catalog_path, desc: "Specify a custom catalog file"
12
+
13
+ desc "list [QUERY ...]", "List catalog tasks"
14
+ long_desc <<~LONGDESC
15
+ List catalog tasks
16
+
17
+ QUERY selects a section from the catalog (see list)
18
+ \x05use / as a group/task separator.
19
+ \x05e.g. #{File.basename $0} nas/userdata"
20
+ LONGDESC
21
+ method_option :all, aliases: "-a", type: :boolean, desc: "List all tasks"
22
+ def list *queries
23
+ queries = ["."] if options[:all]
24
+ tasks = Multisync::Selector.new(catalog, queries).tasks(parents: true)
25
+
26
+ puts "Catalog file: #{as_main(options[:catalog])}"
27
+ puts Multisync::List.new(tasks)
43
28
  end
44
-
45
- def start
46
- parser.parse!
47
- options[:quiet] = false if options[:print]
48
-
49
- @sets = ARGV
50
29
 
51
- if options[:list]
52
- # List tasks
53
- puts "Catalog: #{options[:file].color(:cyan)}"
54
- puts
55
- puts Multisync::List.new catalog
30
+ desc "start [QUERY ...]", "Run catalog tasks"
31
+ long_desc <<~LONGDESC
32
+ Run catalog tasks
56
33
 
57
- else
58
- # Run tasks
59
- return if tasks.empty?
60
- begin
61
- tasks.each do |task|
62
- runtime.run task
63
- end
64
- rescue Interrupt => e
65
- $stderr.puts "\nAborted!".color(:red)
66
- end
67
- unless options[:print]
68
- puts
69
- puts
70
- puts Multisync::Summary.new tasks
71
- end
72
- end
34
+ QUERY selects a section from the catalog (see list)
35
+ \x05use / as a group/task separator.
36
+ \x05e.g. #{File.basename $0} nas/userdata"
37
+ LONGDESC
38
+ method_option :timeout, aliases: "-t", type: :numeric, default: 31536000, desc: "Timeout for single rsync task" # 1 year
39
+ method_option :print, aliases: "-p", type: :boolean, desc: "Print commands without executing"
40
+ method_option :quiet, aliases: "-q", type: :boolean, desc: "Run tasks quietly"
41
+ method_option :summary, type: :boolean, default: true, desc: "Show summary"
42
+ method_option :dryrun, aliases: "-n", type: :boolean, desc: "Run jobs in dry-run, don't make any changes"
43
+ def start *queries
44
+ tasks = Multisync::Selector.new(catalog, queries)
45
+ .tasks
46
+ # only leafs of the task tree can be executed
47
+ .select(&:executeable?)
73
48
 
74
- puts
49
+ # Run
50
+ tasks.each { runtime.run _1 }
51
+ rescue Interrupt
52
+ warn as_fail("\nAborted!")
53
+ ensure
54
+ # Summarize
55
+ puts Multisync::Summary.new(tasks) if options[:summary] && !options[:print]
75
56
  end
76
-
77
- def tasks
78
- @_tasks ||= Multisync::Selector.new(catalog, sets).tasks
79
- # @_tasks ||= catalog.filter sets
57
+
58
+ desc "version", "Print version information"
59
+ def version
60
+ puts "multisync v#{Multisync::VERSION}"
80
61
  end
81
-
62
+
63
+ private
64
+
82
65
  def catalog
83
- @_catalog ||= Multisync::Catalog.new options[:file]
66
+ @catalog ||= Multisync::Catalog.new options[:catalog]
84
67
  end
85
-
68
+
86
69
  def runtime
87
- @_runtime ||= Multisync::Runtime.new(options)
88
- end
89
-
90
- def options
91
- @_options ||= {
92
- list: false,
93
- print: false,
94
- dryrun: false,
95
- quiet: false,
96
- file: Multisync::Catalog.default_catalog_path,
97
- timeout: 31536000, # 1 year
98
- }
70
+ @runtime ||= Multisync::Runtime.new(options)
99
71
  end
100
- end
72
+ end
@@ -0,0 +1,23 @@
1
+ require "rainbow"
2
+
3
+ module Multisync::Colors
4
+ def as_note x
5
+ Rainbow(x).faint
6
+ end
7
+
8
+ def as_main x
9
+ Rainbow(x).color(:aqua)
10
+ end
11
+
12
+ def as_skipped x
13
+ Rainbow(x).color(:yellow)
14
+ end
15
+
16
+ def as_success x
17
+ Rainbow(x).color(:green)
18
+ end
19
+
20
+ def as_fail x
21
+ Rainbow(x).color(:red)
22
+ end
23
+ end
@@ -1,61 +1,59 @@
1
-
2
1
  module Multisync::Definition::Dsl
3
-
4
2
  # The DSL methods
5
3
  def group name, &block
6
4
  Multisync::Definition::Entity.new self, name, &block
7
5
  end
8
-
6
+
9
7
  def sync name, &block
10
8
  Multisync::Definition::Entity.new self, name, &block
11
9
  end
12
-
10
+
13
11
  def template name, &block
14
12
  Multisync::Definition::Template.new name, &block
15
13
  end
16
-
14
+
17
15
  def include name
18
16
  template = Multisync::Definition::Template.lookup name
19
- instance_eval &template.block
17
+ instance_eval(&template.block)
20
18
  end
21
-
22
- def from value, options={}
19
+
20
+ def from value, options = {}
23
21
  @from_value = value
24
22
  # Check source's host or path before sync
25
23
  @from_check = options[:check]
26
24
  @from_description = options[:description]
27
25
  end
28
-
29
- def to value, options={}
26
+
27
+ def to value, options = {}
30
28
  @to_value = value
31
29
  # Check destination's host or path before sync
32
30
  @to_check = options[:check]
33
31
  @to_description = options[:description]
34
32
  end
35
-
36
- def options rsync_options, mode=:append
33
+
34
+ def options rsync_options, mode = :append
37
35
  @rsync_options_mode = mode
38
36
  @rsync_options = Array(rsync_options)
39
37
  end
40
-
38
+
41
39
  def default
42
40
  @default = true
43
41
  end
44
-
42
+
45
43
  # Defines a check, that should pass in order to invoke the sync
46
- def only_if cmd, options={}
47
- @check = { cmd: cmd, message: options.fetch(:message, cmd) }
44
+ def only_if cmd, options = {}
45
+ @check = {cmd: cmd, message: options.fetch(:message, cmd)}
48
46
  end
49
-
47
+
50
48
  # Check source's host or path before sync
51
49
  # can also be set as option of "from"
52
- def check_from flag=true
50
+ def check_from flag = true
53
51
  @from_check = flag
54
52
  end
55
-
53
+
56
54
  # Check destination's host or path before sync
57
55
  # can also be set as option of "to"
58
- def check_to flag=true
56
+ def check_to flag = true
59
57
  @to_check = flag
60
58
  end
61
- end
59
+ end
@@ -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 group
4
+ # The parent of the entity
7
5
  attr_reader :parent
8
-
9
- # The name of the group
6
+
7
+ # The name of the entity
10
8
  attr_reader :name
11
-
12
- # All members (groups or syncs) of this group
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 results after run as Hash
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.to_s
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, level=0
57
- visitor.visit self, level
39
+ def accept visitor
40
+ visitor.visit self
58
41
  members.map do |member|
59
- member.accept visitor, level+1
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