filedepot 0.2.5 → 0.3.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: d53822cb0793186be40e3532598732de7294227c353dd20932b36b97a439a020
4
- data.tar.gz: 4433043fce6deeca6b44f8de0a0f1fdf921759bf4af1eb64f828ae65facd2660
3
+ metadata.gz: 2e47c70e2249f31321d1bcebfdf547d828ea1fa2414abe5cae782cfb1cc494bb
4
+ data.tar.gz: 3707898672533edd8e377b3f0a4d8c6e7fbc60b7c001626784ecefa16b4b9ec2
5
5
  SHA512:
6
- metadata.gz: b77aabe8883cc7f675e4156f2160c03229837666036b1ad9de2d7d2ecd913dfdf1cbaea31ac543788419c0f296116e6651af7a23ed28dd274716fd0f8bbc521b
7
- data.tar.gz: 5fe65c7e2840f7eaf8550d1434222647b19742cfdf01391d737ed6fcefcd20e551b8e20ca6d5922571ab36fcadd0bccf8d51bda7a3e9e849eafeefa53b56a56b
6
+ metadata.gz: 6758ed8235980592480dc3ed51316e7ff87ba6f3d9fe6198adde995f3488177103adcf860afb39cac131f49b08e5888ef7dcf1433c195021c3d9408afcc8ca09
7
+ data.tar.gz: ff7decf067599ee4b36c6855f354bbd09efa78055ea206559adf10f2ff48a5162abf79039abfae6454b3aa03ce658ac103dacdab2d2d92368b982fa529361862
data/README.md CHANGED
@@ -20,36 +20,38 @@ gem "filedepot"
20
20
 
21
21
  Config file: `$HOME/.filedepot/config.yml`
22
22
 
23
- On first run, a default config is created:
23
+ Run `filedepot setup` to create the config. On first run of any command, setup is automatically invoked if no config exists.
24
24
 
25
25
  ```yaml
26
- default_source: test
27
- sources:
26
+ stores:
28
27
  - name: test
29
- ssh: ssh
28
+ type: ssh
30
29
  host: 127.0.0.1
31
- username:
30
+ username: user
32
31
  base_path: /Users/user/filedepot
32
+ default_store: test
33
33
  ```
34
34
 
35
- Optional `public_base_path` for public URLs (shown in info and after push):
35
+ Optional `public_base_url` for public URLs (shown in info and after push):
36
36
 
37
37
  ```yaml
38
- sources:
38
+ stores:
39
39
  - name: test
40
- ssh: ssh
40
+ type: ssh
41
41
  host: 127.0.0.1
42
42
  base_path: /data/filedepot
43
- public_base_path: https://example.com/files
43
+ public_base_url: https://example.com/files
44
+ default_store: test
44
45
  ```
45
46
 
46
- When `default_source` does not match any source name, the first source is used.
47
+ When `default_store` does not match any store name, the first store is used.
47
48
 
48
49
  ## Commands
49
50
 
50
51
  | Command | Description |
51
52
  |---------|-------------|
52
- | `filedepot` | Show current source and available commands |
53
+ | `filedepot` | Show current store and available commands |
54
+ | `filedepot setup` | Create or reconfigure config (interactive) |
53
55
  | `filedepot config` | Open config file with $EDITOR |
54
56
  | `filedepot push HANDLE FILE` | Send file to current storage |
55
57
  | `filedepot pull HANDLE [--path PATH] [--version N]` | Get file from storage |
@@ -64,7 +66,7 @@ When `default_source` does not match any source name, the first source is used.
64
66
  filedepot push test test.txt
65
67
  ```
66
68
 
67
- Sends `test.txt` to storage with handle `test`. Each push creates a new version. When `public_base_path` is configured, the URL is shown after upload.
69
+ Sends `test.txt` to storage with handle `test`. Each push creates a new version. When `public_base_url` is configured, the URL is shown after upload.
68
70
 
69
71
  ### Pull
70
72
 
@@ -83,7 +85,7 @@ Lists versions in descending order with creation datetime. Shows at most 10, wit
83
85
 
84
86
  ### Info
85
87
 
86
- Shows handle, remote base path, current version, updated-at datetime, and latest version URL (when `public_base_path` is set).
88
+ Shows handle, remote base path, current version, updated-at datetime, and latest version URL (when `public_base_url` is set).
87
89
 
88
90
  ## Testing
89
91
 
data/bin/filedepot CHANGED
@@ -3,6 +3,12 @@
3
3
 
4
4
  require "filedepot"
5
5
 
6
+ # Run setup if config does not exist (first run)
7
+ if ARGV[0] != "setup" && !Filedepot::Config.exists?
8
+ puts "~/.filedepot/config.yml not found, let's config it"
9
+ Filedepot::CLI.start(["setup"])
10
+ end
11
+
6
12
  # Handle no-args: Thor by default shows help, we want our custom default output
7
13
  if ARGV.empty?
8
14
  Filedepot::CLI.new.invoke(:default)
data/lib/filedepot/cli.rb CHANGED
@@ -14,6 +14,12 @@ module Filedepot
14
14
 
15
15
  Opens the config file ($HOME/.filedepot/config.yml) using $EDITOR.
16
16
  HELP
17
+ setup: <<~HELP,
18
+ Usage:
19
+ filedepot setup
20
+
21
+ Create or reconfigure the config file. Prompts for store name, type, host, username, and base path.
22
+ HELP
17
23
  push: <<~HELP,
18
24
  Usage:
19
25
  filedepot push HANDLE FILE
@@ -38,15 +44,114 @@ module Filedepot
38
44
  versions: "Usage: filedepot versions HANDLE\n\nList all versions of a handle. Each version has an integer ID from 1 to n.",
39
45
  delete: "Usage: filedepot delete HANDLE [VERSION]\n\nAfter confirmation, deletes all versions of a file.\nIf VERSION is specified, deletes only that specific version.",
40
46
  info: "Usage: filedepot info HANDLE\n\nShow info for a handle: remote base path and current version.",
41
- handles: "Usage: filedepot handles\n\nList all handles in storage."
47
+ handles: "Usage: filedepot handles\n\nList all handles in storage.",
48
+ test: "Usage: filedepot test\n\nRun end-to-end test: push, pull, delete a temporary file."
42
49
  }.freeze
43
50
 
51
+ desc "test", "Run end-to-end test (push, pull, delete)"
52
+ def test
53
+ store = check_config
54
+ return unless store
55
+
56
+ timestamp = Time.now.to_i
57
+ test_handle = timestamp.to_s
58
+ test_filename = "#{timestamp}.txt"
59
+
60
+ File.write(test_filename, Time.now.to_s)
61
+ invoke :push, [test_handle, test_filename]
62
+
63
+ File.delete(test_filename)
64
+
65
+ invoke :pull, [test_handle]
66
+
67
+ invoke :delete, [test_handle], yes: true
68
+
69
+ if File.exist?(test_filename)
70
+ File.delete(test_filename)
71
+ puts "Test is OK"
72
+ else
73
+ puts "Test is KO, see the outputs for errors"
74
+ end
75
+ rescue RuntimeError => e
76
+ puts "Test is KO, see the outputs for errors"
77
+ puts "Error: #{e.message}"
78
+ end
79
+
44
80
  desc "config", "Open the config file using $EDITOR"
45
81
  def config
46
82
  config_path = Config::CONFIG_PATH
47
- Config.ensure_config!
83
+ unless Config.exists?
84
+ puts "Error: No config file. Run 'filedepot setup' first."
85
+ return
86
+ end
48
87
  editor = ENV["EDITOR"] || "vim"
49
- exec(editor, config_path)
88
+ system(editor, config_path)
89
+ return unless confirm?("Run a test? [y/N]")
90
+
91
+ invoke :test
92
+ end
93
+
94
+ desc "setup", "Create or reconfigure the config file"
95
+ def setup
96
+ store_types = Storage::Base.store_types
97
+ first_type = store_types.keys.first
98
+ defaults = store_types[first_type][:config].transform_keys(&:to_s)
99
+
100
+ if Config.exists?
101
+ config = Config.load
102
+ stores = config["stores"] || []
103
+ first_store = stores.first
104
+ if first_store
105
+ defaults["name"] = (first_store["name"] || first_store[:name]).to_s
106
+ defaults["host"] = (first_store["host"] || first_store[:host]).to_s
107
+ defaults["username"] = (first_store["username"] || first_store[:username]).to_s
108
+ defaults["base_path"] = (first_store["base_path"] || first_store[:base_path]).to_s
109
+ defaults["public_base_url"] = (first_store["public_base_url"] || first_store[:public_base_url]).to_s
110
+ end
111
+ end
112
+
113
+ puts "Configure storage store (press Enter to accept default)"
114
+ puts ""
115
+
116
+ name = prompt_with_default("store name", defaults["name"])
117
+ type = prompt_with_default("Type (#{store_types.keys.join(', ')})", first_type.to_s)
118
+ host = prompt_with_default("Host", defaults["host"])
119
+ username = prompt_with_default("Username", defaults["username"])
120
+ base_path = prompt_with_default("Base path", defaults["base_path"])
121
+ public_base_url = prompt_with_default("Public base URL (optional)", defaults["public_base_url"])
122
+
123
+ puts ""
124
+ puts "Store: #{name}"
125
+ puts " Type: #{type}"
126
+ puts " Host: #{host}"
127
+ puts " Username: #{username}"
128
+ puts " Base path: #{base_path}"
129
+ puts " Public base URL: #{public_base_url}" unless public_base_url.empty?
130
+ puts ""
131
+
132
+ return unless confirm?("Write config? [y/N]")
133
+
134
+ store_hash = {
135
+ "name" => name,
136
+ "type" => type,
137
+ "host" => host,
138
+ "username" => username,
139
+ "base_path" => base_path,
140
+ "public_base_url" => (public_base_url.empty? ? nil : public_base_url)
141
+ }.compact
142
+
143
+ config = {
144
+ "default_store" => name,
145
+ "stores" => [store_hash]
146
+ }
147
+
148
+ FileUtils.mkdir_p(Config::CONFIG_DIR)
149
+ File.write(Config::CONFIG_PATH, config.to_yaml)
150
+ puts "Config written to #{Config::CONFIG_PATH}"
151
+
152
+ return unless confirm?("Run a test? [y/N]")
153
+
154
+ invoke :test
50
155
  end
51
156
 
52
157
  desc "push HANDLE FILE", "Send a file to the current storage with a specific handle"
@@ -65,13 +170,10 @@ module Filedepot
65
170
  return
66
171
  end
67
172
 
68
- source = Config.current_source
69
- if source.nil?
70
- puts "Error: No storage source configured. Run 'filedepot config' to set up."
71
- return
72
- end
173
+ store = check_config
174
+ return unless store
73
175
 
74
- storage = Storage::Base.for(source)
176
+ storage = Storage::Base.for(store)
75
177
  storage.push(handle, path)
76
178
  version = storage.current_version(handle)
77
179
  puts "Pushed #{file_path} as #{handle} (version #{version})"
@@ -90,16 +192,13 @@ module Filedepot
90
192
  return
91
193
  end
92
194
 
93
- source = Config.current_source
94
- if source.nil?
95
- puts "Error: No storage source configured. Run 'filedepot config' to set up."
96
- return
97
- end
195
+ store = check_config
196
+ return unless store
98
197
 
99
198
  local_path = options[:path]
100
199
  version = (options[:version].nil? || options[:version].empty?) ? nil : options[:version].to_i
101
200
 
102
- storage = Storage::Base.for(source)
201
+ storage = Storage::Base.for(store)
103
202
  info = storage.pull_info(handle, version, local_path)
104
203
  target_path = info[:target_path]
105
204
 
@@ -128,13 +227,10 @@ module Filedepot
128
227
  return
129
228
  end
130
229
 
131
- source = Config.current_source
132
- if source.nil?
133
- puts "Error: No storage source configured. Run 'filedepot config' to set up."
134
- return
135
- end
230
+ store = check_config
231
+ return unless store
136
232
 
137
- storage = Storage::Base.for(source)
233
+ storage = Storage::Base.for(store)
138
234
  versions_list = storage.versions(handle)
139
235
  if versions_list.empty?
140
236
  puts "Error: Handle '#{handle}' not found."
@@ -156,13 +252,10 @@ module Filedepot
156
252
  return
157
253
  end
158
254
 
159
- source = Config.current_source
160
- if source.nil?
161
- puts "Error: No storage source configured. Run 'filedepot config' to set up."
162
- return
163
- end
255
+ store = check_config
256
+ return unless store
164
257
 
165
- storage = Storage::Base.for(source)
258
+ storage = Storage::Base.for(store)
166
259
  versions_list = storage.versions(handle)
167
260
  if versions_list.empty?
168
261
  puts "Error: Handle '#{handle}' not found."
@@ -180,13 +273,10 @@ module Filedepot
180
273
 
181
274
  desc "handles", "List all handles in storage"
182
275
  def handles
183
- source = Config.current_source
184
- if source.nil?
185
- puts "Error: No storage source configured. Run 'filedepot config' to set up."
186
- return
187
- end
276
+ store = check_config
277
+ return unless store
188
278
 
189
- storage = Storage::Base.for(source)
279
+ storage = Storage::Base.for(store)
190
280
  handles = storage.ls
191
281
  if handles.empty?
192
282
  puts "No handles found."
@@ -196,19 +286,17 @@ module Filedepot
196
286
  end
197
287
 
198
288
  desc "delete HANDLE [VERSION]", "After confirmation, delete all versions of a file; or only a specific version if specified"
289
+ method_option :yes, type: :boolean, aliases: "-y", desc: "Skip confirmation (for scripts)"
199
290
  def delete(handle = nil, version = nil)
200
291
  if handle.nil?
201
292
  puts COMMAND_HELP[:delete]
202
293
  return
203
294
  end
204
295
 
205
- source = Config.current_source
206
- if source.nil?
207
- puts "Error: No storage source configured. Run 'filedepot config' to set up."
208
- return
209
- end
296
+ store = check_config
297
+ return unless store
210
298
 
211
- storage = Storage::Base.for(source)
299
+ storage = Storage::Base.for(store)
212
300
  versions_list = storage.versions(handle)
213
301
  if versions_list.empty?
214
302
  puts "Error: Handle '#{handle}' not found."
@@ -222,44 +310,53 @@ module Filedepot
222
310
  end
223
311
  end
224
312
 
225
- version_str = version ? " version #{version}" : " all versions"
226
- puts "This will delete '#{handle}'#{version_str}."
227
- begin
228
- print "Type the handle name to confirm: "
229
- input = $stdin.gets&.strip
230
- rescue Interrupt
231
- puts
232
- return
233
- end
313
+ unless options[:yes]
314
+ version_str = version ? " version #{version}" : " all versions"
315
+ puts "This will delete '#{handle}'#{version_str}."
316
+ begin
317
+ print "Type the handle name to confirm: "
318
+ input = $stdin.gets&.strip
319
+ rescue Interrupt
320
+ puts
321
+ return
322
+ end
234
323
 
235
- unless input == handle
236
- puts "Aborted (handle name did not match)."
237
- return
324
+ unless input == handle
325
+ puts "Aborted (handle name did not match)."
326
+ return
327
+ end
238
328
  end
239
329
 
240
330
  storage.delete(handle, version)
241
- puts "Deleted handle#{handle}#{version ? " version #{version}" : ""}."
331
+ puts "Deleted handle '#{handle}'#{version ? " version #{version}" : ""}."
242
332
  rescue RuntimeError => e
243
333
  puts "Error: #{e.message}"
244
334
  end
245
335
 
246
336
  default_task :default
247
337
 
248
- desc "default", "Show current source and list of available commands"
338
+ desc "default", "Show current store and list of available commands"
249
339
  def default
250
- source = Config.current_source
251
340
  config = Config.load
252
- default_name = config["default_source"]
341
+ if config.nil?
342
+ puts "Error: No storage store configured. Run 'filedepot setup' to set up."
343
+ return
344
+ end
345
+
346
+ store = Config.current_store
347
+ default_name = config["default_store"]
253
348
 
254
349
  puts "filedepot"
255
350
  puts "--------"
256
- puts "Current source: #{default_name}"
257
- if source
258
- puts " Type: #{source['ssh']} (#{source['host']})"
259
- puts " Base path: #{source['base_path']}"
351
+ puts "Current store: #{default_name}"
352
+ if store
353
+ type_str = store["type"] ? store["type"] : "unknown"
354
+ puts " Type: #{type_str} (#{store['host']})"
355
+ puts " Base path: #{store['base_path']}"
260
356
  end
261
357
  puts ""
262
358
  puts "Available commands:"
359
+ puts " filedepot setup Create or reconfigure config"
263
360
  puts " filedepot config Open config file using $EDITOR"
264
361
  puts " filedepot info HANDLE Show info for a handle"
265
362
  puts " filedepot handles List all handles in storage"
@@ -267,6 +364,7 @@ module Filedepot
267
364
  puts " filedepot push HANDLE FILE Send file to current storage"
268
365
  puts " filedepot pull HANDLE [--path PATH] [--version N] Get file from storage"
269
366
  puts " filedepot delete HANDLE [VER] Delete file(s) after confirmation"
367
+ puts " filedepot test Run end-to-end test"
270
368
  puts ""
271
369
  puts "Use 'filedepot help COMMAND' for more information on a command."
272
370
  end
@@ -277,6 +375,26 @@ module Filedepot
277
375
 
278
376
  private
279
377
 
378
+ def prompt_with_default(label, default)
379
+ default_str = default.to_s
380
+ prompt = default_str.empty? ? "#{label}: " : "#{label} [#{default_str}]: "
381
+ print prompt
382
+ input = $stdin.gets&.strip
383
+ input.empty? ? default_str : input
384
+ rescue Interrupt
385
+ puts
386
+ exit 1
387
+ end
388
+
389
+ def check_config
390
+ store = Config.current_store
391
+ if store.nil?
392
+ puts "Error: No storage store configured. Run 'filedepot setup' to set up."
393
+ return nil
394
+ end
395
+ store
396
+ end
397
+
280
398
  def confirm?(prompt)
281
399
  print "#{prompt} "
282
400
  input = $stdin.gets&.strip&.downcase
@@ -8,38 +8,27 @@ module Filedepot
8
8
  CONFIG_DIR = File.expand_path("~/.filedepot")
9
9
  CONFIG_PATH = File.join(CONFIG_DIR, "config.yml")
10
10
 
11
- DEFAULT_CONFIG = <<~YAML
12
- default_source: test
13
- sources:
14
- - name: test
15
- ssh: ssh
16
- host: 127.0.0.1
17
- username:
18
- base_path: %<base_path>s
19
- YAML
20
-
21
11
  class << self
22
- def ensure_config!
23
- return if File.exist?(CONFIG_PATH)
24
-
25
- FileUtils.mkdir_p(CONFIG_DIR)
26
- base_path = File.join(File.expand_path("~"), "filedepot")
27
- File.write(CONFIG_PATH, format(DEFAULT_CONFIG, base_path: base_path))
12
+ def exists?
13
+ File.exist?(CONFIG_PATH)
28
14
  end
29
15
 
30
16
  def load
31
- ensure_config!
17
+ return nil unless exists?
18
+
32
19
  YAML.load_file(CONFIG_PATH)
33
20
  end
34
21
 
35
- def current_source
22
+ def current_store
36
23
  config = load
37
- default = config["default_source"]
38
- sources = config["sources"] || []
39
- return nil if sources.empty?
24
+ return nil if config.nil?
25
+
26
+ default = config["default_store"]
27
+ stores = config["stores"] || []
28
+ return nil if stores.empty?
40
29
 
41
- source = sources.find { |s| (s["name"] || s[:name]) == default }
42
- source || sources.first
30
+ store = stores.find { |s| (s["name"] || s[:name]) == default }
31
+ store || stores.first
43
32
  end
44
33
  end
45
34
  end
@@ -3,16 +3,33 @@
3
3
  module Filedepot
4
4
  module Storage
5
5
  class Base
6
- def self.for(source)
7
- if source["ssh"]
8
- Ssh.new(source)
6
+ STORE_TYPES = {
7
+ ssh: {
8
+ config: {
9
+ "name" => "test",
10
+ "type" => "ssh",
11
+ "host" => "127.0.0.1",
12
+ "username" => ENV["USER"].to_s.empty? ? "username" : ENV["USER"],
13
+ "base_path" => File.join(File.expand_path("~"), "filedepot"),
14
+ "public_base_url" => nil
15
+ }
16
+ }
17
+ }.freeze
18
+
19
+ def self.store_types
20
+ STORE_TYPES
21
+ end
22
+
23
+ def self.for(store)
24
+ if store["type"] == "ssh"
25
+ Ssh.new(store)
9
26
  else
10
- raise ArgumentError, "Unknown storage type for source: #{source["name"]}"
27
+ raise ArgumentError, "Unknown storage type for store: #{store["name"]}"
11
28
  end
12
29
  end
13
30
 
14
- def initialize(source)
15
- @source = source
31
+ def initialize(store)
32
+ @store = store
16
33
  end
17
34
 
18
35
  def current_version(handle)
@@ -74,7 +91,7 @@ module Filedepot
74
91
  end
75
92
 
76
93
  def url(handle, version, filename)
77
- base = @source["public_base_path"].to_s.sub(%r{/+$}, "")
94
+ base = @store["public_base_url"].to_s.sub(%r{/+$}, "")
78
95
  return nil if base.empty? || filename.nil? || filename.empty?
79
96
 
80
97
  path = [handle, version, filename].join("/")
@@ -84,7 +101,7 @@ module Filedepot
84
101
  protected
85
102
 
86
103
  def remote_base_path
87
- @source["base_path"] || "/tmp/filedepot"
104
+ @store["base_path"] || "/tmp/filedepot"
88
105
  end
89
106
 
90
107
  def remote_handle_path(handle)
@@ -140,8 +140,8 @@ module Filedepot
140
140
  end
141
141
 
142
142
  def ssh_session
143
- host = @source["host"] || "localhost"
144
- user = @source["username"].to_s.strip
143
+ host = @store["host"] || "localhost"
144
+ user = @store["username"].to_s.strip
145
145
  user = ENV["USER"] if user.empty?
146
146
 
147
147
  Net::SSH.start(host, user) do |ssh|
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Filedepot
4
- VERSION = "0.2.5"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: filedepot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Filedepot