ruborg 0.1.0 → 0.2.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: 2e48935c72c444956ca0b7918efb14bd3d5b8a38273ec6843adec2fbb2da6968
4
- data.tar.gz: f200cb546d62431ec1ff84544da0b96b9f84cf7877aa29627608f8c43119d2b7
3
+ metadata.gz: ca72109e555872d29ba3b56a07b6ed10ea5b41fec351631232265c42216fd376
4
+ data.tar.gz: 2049a2ba62a07171f2fc62237243043be6dacf83200f0ffa4f10b6a05285d5b1
5
5
  SHA512:
6
- metadata.gz: 4614d7f78363f420ae4ce80355725fb84e068bfac0d94591f1cee56a6d14c7c6664dd415405462086bdf2fa7543187702f346645655b2bb32b5ffb62fea288a9
7
- data.tar.gz: f70898563085b03b78cf4cf140abb5efd9bd3bd6881918aa7a77c976823a15469688e3aeafd6814c4ff880b3e5c8f949264a1c03f9b1251f3467d409ebe1f302
6
+ metadata.gz: ce9e39076fbb118801cf910ca241e930d2139e0f15c8adaf139a62e36bf8dad5a7495d81618db5f82d45d7c1e6c0784c143258afbc0c4ad524945d13b91ca345
7
+ data.tar.gz: 94534dfd27da62827c5976bcbbe291d9c6906a7e3355905a970b6f9ea65bb3b7612ae50373445a249f2b57ad9283028ca06ab5db87d85f05e6ff76e3b6d34121
data/.rspec CHANGED
@@ -1,3 +1,3 @@
1
1
  --require spec_helper
2
2
  --color
3
- --format documentation
3
+ --format documentation
data/CHANGELOG.md CHANGED
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2025-10-05
11
+
12
+ ### Added
13
+ - `--remove-source` option to delete source files after successful backup
14
+ - Comprehensive logging system with daily rotation (default: `~/.ruborg/logs/ruborg.log`)
15
+ - Custom log file support via `--log` option for all commands
16
+ - `--path` option for restore command to extract single files/directories from archives
17
+ - Comprehensive RSpec test suite with mocked Passbolt and actual Borg integration tests
18
+ - Borg installation instructions for macOS and Ubuntu in README
19
+ - Support for environment variables to prevent interactive prompts (`BORG_RELOCATED_REPO_ACCESS_IS_OK`, etc.)
20
+ - Automatic destination directory creation for restore operations
21
+ - Test helpers and fixtures for easier testing
22
+
23
+ ### Fixed
24
+ - Passbolt CLI command corrected from `passbolt get <id>` to `passbolt get resource <id>`
25
+ - Borg commands now properly redirect stdin to prevent interactive passphrase prompts
26
+ - Improved error handling and logging throughout the application
27
+
28
+ ### Changed
29
+ - Refactored Passbolt class to use testable `execute_command` method
30
+ - Enhanced Repository and Backup classes to properly handle environment variables
31
+ - Improved CLI integration with better Passbolt mock support in tests
32
+
33
+ ## [0.1.0] - 2025-10-04
34
+
10
35
  ### Added
11
36
  - Initial gem structure
12
37
  - Borg repository initialization and management
@@ -14,8 +39,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
14
39
  - YAML configuration file support
15
40
  - Passbolt CLI integration for password management
16
41
  - Command-line interface with Thor
17
-
18
- ## [0.1.0] - 2025-10-04
19
-
20
- ### Added
21
- - Initial release
42
+ - Basic error handling
data/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # Ruborg
2
2
 
3
- > **⚠️ WARNING: This project is under heavy development and is not yet functional. Do not use in production.**
4
- >
5
3
  > This gem is being developed with the assistance of Claude AI.
6
4
 
7
5
  A friendly Ruby frontend for [Borg Backup](https://www.borgbackup.org/). Ruborg simplifies backup management by providing a YAML-based configuration system and seamless integration with Passbolt for encryption password management.
@@ -14,13 +12,35 @@ A friendly Ruby frontend for [Borg Backup](https://www.borgbackup.org/). Ruborg
14
12
  - 🔐 **Passbolt Integration** - Secure password management via Passbolt CLI
15
13
  - 🎯 **Pattern Exclusions** - Flexible file exclusion patterns
16
14
  - 🗜️ **Compression Options** - Support for multiple compression algorithms
15
+ - 🗂️ **Selective Restore** - Restore individual files or directories from archives
16
+ - 🧹 **Auto-cleanup** - Optionally remove source files after successful backup
17
+ - 📊 **Logging** - Comprehensive logging with daily rotation
18
+ - ✅ **Well-tested** - Comprehensive test suite with RSpec
17
19
 
18
20
  ## Prerequisites
19
21
 
20
- - Ruby >= 2.7.0
22
+ - Ruby >= 3.2.0
21
23
  - [Borg Backup](https://www.borgbackup.org/) installed and available in PATH
22
24
  - [Passbolt CLI](https://github.com/passbolt/go-passbolt-cli) (optional, for password management)
23
25
 
26
+ ### Installing Borg Backup
27
+
28
+ **macOS:**
29
+ ```bash
30
+ brew install borgbackup
31
+ ```
32
+
33
+ **Ubuntu/Debian:**
34
+ ```bash
35
+ sudo apt update
36
+ sudo apt install borgbackup
37
+ ```
38
+
39
+ **Verify installation:**
40
+ ```bash
41
+ borg --version
42
+ ```
43
+
24
44
  ## Installation
25
45
 
26
46
  Add this line to your application's Gemfile:
@@ -99,6 +119,9 @@ ruborg backup --config /path/to/config.yml
99
119
 
100
120
  # With custom archive name
101
121
  ruborg backup --name "my-backup-2025-10-04"
122
+
123
+ # Remove source files after successful backup
124
+ ruborg backup --remove-source
102
125
  ```
103
126
 
104
127
  ### List Archives
@@ -110,11 +133,14 @@ ruborg list
110
133
  ### Restore from Archive
111
134
 
112
135
  ```bash
113
- # Restore to current directory
136
+ # Restore entire archive to current directory
114
137
  ruborg restore archive-name
115
138
 
116
139
  # Restore to specific directory
117
140
  ruborg restore archive-name --destination /path/to/restore
141
+
142
+ # Restore a single file from archive
143
+ ruborg restore archive-name --path /path/to/file.txt --destination /new/location
118
144
  ```
119
145
 
120
146
  ### View Repository Information
@@ -123,13 +149,40 @@ ruborg restore archive-name --destination /path/to/restore
123
149
  ruborg info
124
150
  ```
125
151
 
152
+ ## Logging
153
+
154
+ Ruborg automatically logs all operations to `~/.ruborg/logs/ruborg.log` with daily rotation. You can specify a custom log file:
155
+
156
+ ```bash
157
+ ruborg backup --log /path/to/custom.log
158
+ ```
159
+
160
+ Logs include:
161
+ - Operation start/completion timestamps
162
+ - Paths being backed up
163
+ - Archive names created
164
+ - Success and error messages
165
+ - Source file removal actions
166
+
126
167
  ## Passbolt Integration
127
168
 
128
169
  Ruborg can retrieve encryption passphrases from Passbolt using the Passbolt CLI:
129
170
 
130
171
  1. Install and configure [Passbolt CLI](https://github.com/passbolt/go-passbolt-cli)
131
- 2. Store your Borg repository passphrase in Passbolt
132
- 3. Add the resource ID to your `ruborg.yml`:
172
+ 2. Configure Passbolt CLI with your server credentials:
173
+ ```bash
174
+ passbolt configure --serverAddress https://server.address \
175
+ --userPrivateKeyFile /path/to/private.key \
176
+ --userPassword YOUR_PASSWORD
177
+ ```
178
+ Or set environment variables:
179
+ ```bash
180
+ export PASSBOLT_SERVER_ADDRESS=https://server.address
181
+ export PASSBOLT_USER_PRIVATE_KEY_FILE=/path/to/private.key
182
+ export PASSBOLT_USER_PASSWORD=YOUR_PASSWORD
183
+ ```
184
+ 3. Store your Borg repository passphrase in Passbolt
185
+ 4. Add the resource ID to your `ruborg.yml`:
133
186
 
134
187
  ```yaml
135
188
  passbolt:
@@ -142,11 +195,16 @@ Ruborg will automatically retrieve the passphrase when performing backup operati
142
195
 
143
196
  | Command | Description | Options |
144
197
  |---------|-------------|---------|
145
- | `init REPOSITORY` | Initialize a new Borg repository | `--passphrase`, `--passbolt-id` |
146
- | `backup` | Create a backup using config file | `--config`, `--name` |
147
- | `list` | List all archives in repository | `--config` |
148
- | `restore ARCHIVE` | Restore files from archive | `--config`, `--destination` |
149
- | `info` | Show repository information | `--config` |
198
+ | `init REPOSITORY` | Initialize a new Borg repository | `--passphrase`, `--passbolt-id`, `--log` |
199
+ | `backup` | Create a backup using config file | `--config`, `--name`, `--remove-source`, `--log` |
200
+ | `list` | List all archives in repository | `--config`, `--log` |
201
+ | `restore ARCHIVE` | Restore files from archive | `--config`, `--destination`, `--path`, `--log` |
202
+ | `info` | Show repository information | `--config`, `--log` |
203
+
204
+ ### Global Options
205
+
206
+ - `--config`: Path to configuration file (default: `ruborg.yml`)
207
+ - `--log`: Path to log file (default: `~/.ruborg/logs/ruborg.log`)
150
208
 
151
209
  ## Development
152
210
 
@@ -169,9 +227,24 @@ bundle exec rake release
169
227
  Run the test suite:
170
228
 
171
229
  ```bash
230
+ # Run all tests
172
231
  bundle exec rspec
232
+
233
+ # Run only unit tests (no Borg required)
234
+ bundle exec rspec --tag ~borg
235
+
236
+ # Run only integration tests (requires Borg)
237
+ bundle exec rspec --tag borg
173
238
  ```
174
239
 
240
+ The test suite includes:
241
+ - Config loading and validation
242
+ - Repository management (with actual Borg integration)
243
+ - Backup and restore operations
244
+ - Passbolt integration (mocked)
245
+ - CLI commands
246
+ - Logging functionality
247
+
175
248
  ## Contributing
176
249
 
177
250
  Bug reports and pull requests are welcome on GitHub at https://github.com/mpantel/ruborg.
data/lib/ruborg/backup.rb CHANGED
@@ -8,22 +8,33 @@ module Ruborg
8
8
  @config = config
9
9
  end
10
10
 
11
- def create(name: nil)
11
+ def create(name: nil, remove_source: false)
12
12
  raise BorgError, "Repository does not exist" unless @repository.exists?
13
13
 
14
14
  archive_name = name || Time.now.strftime("%Y-%m-%d_%H-%M-%S")
15
15
  cmd = build_create_command(archive_name)
16
16
 
17
17
  execute_borg_command(cmd)
18
+
19
+ remove_source_files if remove_source
18
20
  end
19
21
 
20
- def extract(archive_name, destination: ".")
22
+ def extract(archive_name, destination: ".", path: nil)
21
23
  raise BorgError, "Repository does not exist" unless @repository.exists?
22
24
 
23
25
  cmd = ["borg", "extract", "#{@repository.path}::#{archive_name}"]
24
- cmd += ["--destination", destination] if destination != "."
25
-
26
- execute_borg_command(cmd)
26
+ cmd << path if path
27
+
28
+ # Change to destination directory if specified
29
+ if destination != "."
30
+ require "fileutils"
31
+ FileUtils.mkdir_p(destination) unless File.directory?(destination)
32
+ Dir.chdir(destination) do
33
+ execute_borg_command(cmd)
34
+ end
35
+ else
36
+ execute_borg_command(cmd)
37
+ end
27
38
  end
28
39
 
29
40
  def list_archives
@@ -52,10 +63,28 @@ module Ruborg
52
63
  end
53
64
 
54
65
  def execute_borg_command(cmd)
55
- result = system(*cmd)
66
+ env = {}
67
+ passphrase = @repository.instance_variable_get(:@passphrase)
68
+ env["BORG_PASSPHRASE"] = passphrase if passphrase
69
+ env["BORG_RELOCATED_REPO_ACCESS_IS_OK"] = "yes"
70
+ env["BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK"] = "yes"
71
+
72
+ result = system(env, *cmd, in: "/dev/null")
56
73
  raise BorgError, "Borg command failed: #{cmd.join(' ')}" unless result
57
74
 
58
75
  result
59
76
  end
77
+
78
+ def remove_source_files
79
+ require "fileutils"
80
+
81
+ @config.backup_paths.each do |path|
82
+ if File.directory?(path)
83
+ FileUtils.rm_rf(path)
84
+ elsif File.file?(path)
85
+ FileUtils.rm(path)
86
+ end
87
+ end
88
+ end
60
89
  end
61
90
  end
data/lib/ruborg/cli.rb CHANGED
@@ -6,68 +6,106 @@ module Ruborg
6
6
  # Command-line interface for ruborg
7
7
  class CLI < Thor
8
8
  class_option :config, type: :string, default: "ruborg.yml", desc: "Path to configuration file"
9
+ class_option :log, type: :string, desc: "Path to log file"
10
+
11
+ def initialize(*args)
12
+ super
13
+ @logger = RuborgLogger.new(log_file: options[:log])
14
+ end
9
15
 
10
16
  desc "init REPOSITORY", "Initialize a new Borg repository"
11
17
  option :passphrase, type: :string, desc: "Repository passphrase"
12
18
  option :passbolt_id, type: :string, desc: "Passbolt resource ID for passphrase"
13
19
  def init(repository_path)
20
+ @logger.info("Initializing repository at #{repository_path}")
14
21
  passphrase = get_passphrase(options[:passphrase], options[:passbolt_id])
15
22
  repo = Repository.new(repository_path, passphrase: passphrase)
16
23
  repo.create
24
+ @logger.info("Repository successfully initialized at #{repository_path}")
17
25
  puts "Repository initialized at #{repository_path}"
18
26
  rescue Error => e
27
+ @logger.error("Failed to initialize repository: #{e.message}")
19
28
  error_exit(e)
20
29
  end
21
30
 
22
31
  desc "backup", "Create a backup using configuration file"
23
32
  option :name, type: :string, desc: "Archive name"
33
+ option :remove_source, type: :boolean, default: false, desc: "Remove source files after successful backup"
24
34
  def backup
35
+ @logger.info("Starting backup operation with config: #{options[:config]}")
25
36
  config = Config.new(options[:config])
37
+ @logger.info("Backing up paths: #{config.backup_paths.join(', ')}")
26
38
  passphrase = fetch_passphrase_from_config(config)
27
39
 
28
40
  repo = Repository.new(config.repository, passphrase: passphrase)
29
41
  backup = Backup.new(repo, config: config)
30
42
 
31
- backup.create(name: options[:name])
43
+ archive_name = options[:name] || Time.now.strftime("%Y-%m-%d_%H-%M-%S")
44
+ @logger.info("Creating archive: #{archive_name}")
45
+ backup.create(name: options[:name], remove_source: options[:remove_source])
46
+ @logger.info("Backup created successfully: #{archive_name}")
47
+
48
+ if options[:remove_source]
49
+ @logger.info("Removed source files: #{config.backup_paths.join(', ')}")
50
+ end
51
+
32
52
  puts "Backup created successfully"
53
+ puts "Source files removed" if options[:remove_source]
33
54
  rescue Error => e
55
+ @logger.error("Backup failed: #{e.message}")
34
56
  error_exit(e)
35
57
  end
36
58
 
37
59
  desc "list", "List all archives in the repository"
38
60
  def list
61
+ @logger.info("Listing archives in repository")
39
62
  config = Config.new(options[:config])
40
63
  passphrase = fetch_passphrase_from_config(config)
41
64
 
42
65
  repo = Repository.new(config.repository, passphrase: passphrase)
43
66
  repo.list
67
+ @logger.info("Successfully listed archives")
44
68
  rescue Error => e
69
+ @logger.error("Failed to list archives: #{e.message}")
45
70
  error_exit(e)
46
71
  end
47
72
 
48
73
  desc "restore ARCHIVE", "Restore files from an archive"
49
74
  option :destination, type: :string, default: ".", desc: "Destination directory"
75
+ option :path, type: :string, desc: "Specific file or directory path to restore from archive"
50
76
  def restore(archive_name)
77
+ restore_target = options[:path] ? "#{options[:path]} from #{archive_name}" : archive_name
78
+ @logger.info("Restoring #{restore_target} to #{options[:destination]}")
51
79
  config = Config.new(options[:config])
52
80
  passphrase = fetch_passphrase_from_config(config)
53
81
 
54
82
  repo = Repository.new(config.repository, passphrase: passphrase)
55
83
  backup = Backup.new(repo, config: config)
56
84
 
57
- backup.extract(archive_name, destination: options[:destination])
58
- puts "Archive restored to #{options[:destination]}"
85
+ backup.extract(archive_name, destination: options[:destination], path: options[:path])
86
+ @logger.info("Successfully restored #{restore_target} to #{options[:destination]}")
87
+
88
+ if options[:path]
89
+ puts "Restored #{options[:path]} from #{archive_name} to #{options[:destination]}"
90
+ else
91
+ puts "Archive restored to #{options[:destination]}"
92
+ end
59
93
  rescue Error => e
94
+ @logger.error("Failed to restore archive: #{e.message}")
60
95
  error_exit(e)
61
96
  end
62
97
 
63
98
  desc "info", "Show repository information"
64
99
  def info
100
+ @logger.info("Retrieving repository information")
65
101
  config = Config.new(options[:config])
66
102
  passphrase = fetch_passphrase_from_config(config)
67
103
 
68
104
  repo = Repository.new(config.repository, passphrase: passphrase)
69
105
  repo.info
106
+ @logger.info("Successfully retrieved repository information")
70
107
  rescue Error => e
108
+ @logger.error("Failed to get repository info: #{e.message}")
71
109
  error_exit(e)
72
110
  end
73
111
 
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require "fileutils"
5
+
6
+ module Ruborg
7
+ # Logging functionality for ruborg
8
+ class RuborgLogger
9
+ attr_reader :logger
10
+
11
+ def initialize(log_file: nil)
12
+ @log_file = log_file || default_log_file
13
+ ensure_log_directory
14
+ @logger = Logger.new(@log_file, "daily")
15
+ @logger.level = Logger::INFO
16
+ @logger.formatter = proc do |severity, datetime, progname, msg|
17
+ "[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity}: #{msg}\n"
18
+ end
19
+ end
20
+
21
+ def info(message)
22
+ @logger.info(message)
23
+ end
24
+
25
+ def error(message)
26
+ @logger.error(message)
27
+ end
28
+
29
+ def warn(message)
30
+ @logger.warn(message)
31
+ end
32
+
33
+ def debug(message)
34
+ @logger.debug(message)
35
+ end
36
+
37
+ private
38
+
39
+ def default_log_file
40
+ File.join(log_directory, "ruborg.log")
41
+ end
42
+
43
+ def log_directory
44
+ dir = File.expand_path("~/.ruborg/logs")
45
+ dir
46
+ end
47
+
48
+ def ensure_log_directory
49
+ FileUtils.mkdir_p(File.dirname(@log_file)) unless File.directory?(File.dirname(@log_file))
50
+ end
51
+ end
52
+ end
@@ -13,10 +13,10 @@ module Ruborg
13
13
  def get_password
14
14
  raise PassboltError, "Resource ID not configured" unless @resource_id
15
15
 
16
- cmd = ["passbolt", "get", @resource_id, "--json"]
17
- output = `#{cmd.join(' ')}`
16
+ cmd = ["passbolt", "get", "resource", @resource_id, "--json"]
17
+ output, status = execute_command(cmd)
18
18
 
19
- raise PassboltError, "Failed to retrieve password from Passbolt" unless $?.success?
19
+ raise PassboltError, "Failed to retrieve password from Passbolt" unless status
20
20
 
21
21
  parse_password(output)
22
22
  end
@@ -29,6 +29,11 @@ module Ruborg
29
29
  end
30
30
  end
31
31
 
32
+ def execute_command(cmd)
33
+ output = `#{cmd.join(' ')}`
34
+ [output, $?.success?]
35
+ end
36
+
32
37
  def parse_password(json_output)
33
38
  data = JSON.parse(json_output)
34
39
  data["password"] || data["secret"]
@@ -40,8 +40,11 @@ module Ruborg
40
40
  def execute_borg_command(cmd)
41
41
  env = {}
42
42
  env["BORG_PASSPHRASE"] = @passphrase if @passphrase
43
+ env["BORG_RELOCATED_REPO_ACCESS_IS_OK"] = "yes"
44
+ env["BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK"] = "yes"
43
45
 
44
- result = system(env, *cmd)
46
+ # Redirect stdin from /dev/null to prevent interactive prompts
47
+ result = system(env, *cmd, in: "/dev/null")
45
48
  raise BorgError, "Borg command failed: #{cmd.join(' ')}" unless result
46
49
 
47
50
  result
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruborg
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/ruborg.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "ruborg/version"
4
+ require_relative "ruborg/logger"
4
5
  require_relative "ruborg/config"
5
6
  require_relative "ruborg/repository"
6
7
  require_relative "ruborg/backup"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruborg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michail Pantelelis
@@ -115,10 +115,10 @@ files:
115
115
  - lib/ruborg/backup.rb
116
116
  - lib/ruborg/cli.rb
117
117
  - lib/ruborg/config.rb
118
+ - lib/ruborg/logger.rb
118
119
  - lib/ruborg/passbolt.rb
119
120
  - lib/ruborg/repository.rb
120
121
  - lib/ruborg/version.rb
121
- - ruborg.gemspec
122
122
  - ruborg.yml.example
123
123
  homepage: https://github.com/mpantel/ruborg
124
124
  licenses:
data/ruborg.gemspec DELETED
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/ruborg/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "ruborg"
7
- spec.version = Ruborg::VERSION
8
- spec.authors = ["Michail Pantelelis"]
9
- spec.email = ["mpantel@aegean.gr"]
10
-
11
- spec.summary = "A friendly Ruby frontend for Borg backup"
12
- spec.description = "Ruborg is a Ruby gem that provides a user-friendly interface to Borg backup. It reads YAML configuration files and orchestrates backup operations, supporting repository creation, backup management, and integration with Passbolt for encryption password management."
13
- spec.homepage = "https://github.com/mpantel/ruborg"
14
- spec.license = "MIT"
15
- spec.required_ruby_version = ">= 3.2.0"
16
-
17
- spec.metadata["homepage_uri"] = spec.homepage
18
- spec.metadata["source_code_uri"] = "#{spec.homepage}.git"
19
- spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
-
21
- # Specify which files should be added to the gem when it is released.
22
- spec.files = Dir.chdir(__dir__) do
23
- `git ls-files -z`.split("\x0").reject do |f|
24
- (File.expand_path(f) == __FILE__) ||
25
- f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
26
- end
27
- end
28
- spec.bindir = "exe"
29
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
- spec.require_paths = ["lib"]
31
-
32
- # Dependencies
33
- spec.add_dependency "thor", "~> 1.3"
34
- spec.add_dependency "psych", "~> 5.0"
35
-
36
- # Development dependencies
37
- spec.add_development_dependency "bundler", "~> 2.0"
38
- spec.add_development_dependency "rake", "~> 13.0"
39
- spec.add_development_dependency "rspec", "~> 3.0"
40
- spec.add_development_dependency "rubocop", "~> 1.0"
41
- end