smp_tool-cli 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +16 -0
  3. data/.vscode/launch.json +21 -0
  4. data/CHANGELOG.md +3 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +83 -0
  7. data/Rakefile +16 -0
  8. data/exe/smp_tool +16 -0
  9. data/lib/smp_tool/cli/autoloader.rb +24 -0
  10. data/lib/smp_tool/cli/commands/base_command.rb +26 -0
  11. data/lib/smp_tool/cli/commands/delete.rb +28 -0
  12. data/lib/smp_tool/cli/commands/extract.rb +35 -0
  13. data/lib/smp_tool/cli/commands/extract_all.rb +28 -0
  14. data/lib/smp_tool/cli/commands/info.rb +22 -0
  15. data/lib/smp_tool/cli/commands/input_command.rb +17 -0
  16. data/lib/smp_tool/cli/commands/new.rb +80 -0
  17. data/lib/smp_tool/cli/commands/push.rb +29 -0
  18. data/lib/smp_tool/cli/commands/rename.rb +34 -0
  19. data/lib/smp_tool/cli/commands/resize.rb +28 -0
  20. data/lib/smp_tool/cli/commands/squeeze.rb +22 -0
  21. data/lib/smp_tool/cli/commands/version.rb +19 -0
  22. data/lib/smp_tool/cli/commands/volume_operation.rb +23 -0
  23. data/lib/smp_tool/cli/commands.rb +25 -0
  24. data/lib/smp_tool/cli/executor/base.rb +65 -0
  25. data/lib/smp_tool/cli/executor/bin_write_mixin.rb +56 -0
  26. data/lib/smp_tool/cli/executor/creator.rb +30 -0
  27. data/lib/smp_tool/cli/executor/deleter.rb +16 -0
  28. data/lib/smp_tool/cli/executor/extracter_base.rb +41 -0
  29. data/lib/smp_tool/cli/executor/extracter_txt.rb +22 -0
  30. data/lib/smp_tool/cli/executor/extracter_txt_all.rb +20 -0
  31. data/lib/smp_tool/cli/executor/informer.rb +78 -0
  32. data/lib/smp_tool/cli/executor/operator.rb +19 -0
  33. data/lib/smp_tool/cli/executor/pusher.rb +39 -0
  34. data/lib/smp_tool/cli/executor/renamer.rb +16 -0
  35. data/lib/smp_tool/cli/executor/resizer.rb +28 -0
  36. data/lib/smp_tool/cli/executor/squeezer.rb +16 -0
  37. data/lib/smp_tool/cli/executor/vol_read_operator.rb +69 -0
  38. data/lib/smp_tool/cli/executor/vol_read_write_operator.rb +25 -0
  39. data/lib/smp_tool/cli/executor.rb +10 -0
  40. data/lib/smp_tool/cli/logger.rb +67 -0
  41. data/lib/smp_tool/cli/version.rb +7 -0
  42. data/lib/smp_tool/cli/volume_specs_interface.rb +63 -0
  43. data/lib/smp_tool/cli.rb +30 -0
  44. data/sig/smp_tool/cli.rbs +6 -0
  45. data/smp_tool-cli.gemspec +39 -0
  46. metadata +172 -0
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ module Executor
6
+ #
7
+ # Base class all executors inherit from.
8
+ #
9
+ class Base
10
+ def initialize(logger:, **)
11
+ @logger = logger
12
+ end
13
+
14
+ def call; end
15
+
16
+ private
17
+
18
+ #
19
+ # Save volume to a binary file.
20
+ #
21
+ # @param [String] path
22
+ # Path to the destination file.
23
+ #
24
+ # @param [SMPTool::VirtualVolume::Volume] volume
25
+ #
26
+ # @param [Hash{ Symbol => Object }] **_options
27
+ #
28
+ def _save_volume(path:, volume:, **_options)
29
+ b_str = _volume_to_binary(volume)
30
+ @logger.debug "Converted to binary string."
31
+
32
+ _write_bin_file(
33
+ path,
34
+ b_str
35
+ )
36
+
37
+ @logger.info "Changes saved to the file: '#{path}'"
38
+ end
39
+
40
+ #
41
+ # Convert virtual volume to binary string.
42
+ #
43
+ # @param [SMPTool::VirtualVolume::Volume] volume
44
+ #
45
+ # @return [String]
46
+ #
47
+ def _volume_to_binary(volume)
48
+ volume.to_binary_s
49
+ end
50
+
51
+ #
52
+ # Write data to a binary file.
53
+ #
54
+ # @param [String] path
55
+ #
56
+ # @param [String] data
57
+ # Binary string with the file's content.
58
+ #
59
+ def _write_bin_file(path, data)
60
+ Dry::Files.new.write(path, data)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ module Executor
6
+ module BinWriteMixin
7
+ private
8
+
9
+ #
10
+ # Save volume to a binary file.
11
+ #
12
+ # @param [String] path
13
+ # Path to the destination file.
14
+ #
15
+ # @param [SMPTool::VirtualVolume::Volume] volume
16
+ #
17
+ # @param [Hash{ Symbol => Object }] **_options
18
+ #
19
+ def _save_volume(path:, volume:, **_options)
20
+ b_str = _volume_to_binary(volume)
21
+ @logger.debug "Converted to binary string"
22
+
23
+ _write_bin_file(
24
+ path,
25
+ b_str
26
+ )
27
+
28
+ @logger.es_info "Changes saved to the file: '#{path}'"
29
+ end
30
+
31
+ #
32
+ # Convert virtual volume to binary string.
33
+ #
34
+ # @param [SMPTool::VirtualVolume::Volume] volume
35
+ #
36
+ # @return [String]
37
+ #
38
+ def _volume_to_binary(volume)
39
+ volume.to_binary_s
40
+ end
41
+
42
+ #
43
+ # Write data to a binary file.
44
+ #
45
+ # @param [String] path
46
+ #
47
+ # @param [String] data
48
+ # Binary string with the file's content.
49
+ #
50
+ def _write_bin_file(path, data)
51
+ Dry::Files.new.write(path, data)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ module Executor
6
+ class Creator
7
+ include BinWriteMixin
8
+
9
+ def initialize(output:, volume_specs:, logger:, **options)
10
+ @logger = logger
11
+ @output = output
12
+ @volume_specs = volume_specs
13
+ @options = options
14
+ end
15
+
16
+ def call
17
+ vol = SMPTool::VirtualVolume::Volume.new(
18
+ bootloader: @volume_specs.bootloader,
19
+ home_block: @volume_specs.home_block,
20
+ volume_params: @volume_specs.volume_params
21
+ )
22
+
23
+ @logger.debug "Virtual volume created"
24
+
25
+ _save_volume(path: @output, volume: vol, **@options)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ module Executor
6
+ class Deleter < VolReadWriteOperator
7
+ def call
8
+ fn = @volume.f_delete(@options[:filename])
9
+ @logger.es_info "File '#{fn}' was deleted from the volume"
10
+
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ module Executor
6
+ class ExtracterBase < VolReadOperator
7
+ def call
8
+ files = _extract_files
9
+ @logger.debug("Files were extracted from the volume")
10
+ _output(files)
11
+ end
12
+
13
+ private
14
+
15
+ def _extract_files; end
16
+
17
+ def _output(f_arr)
18
+ dry_files = Dry::Files.new
19
+
20
+ f_arr.each do |f|
21
+ path = dry_files.join(@options[:dir], _filter_filename(f.filename))
22
+ _write_file(path, f)
23
+ @logger.info "File '#{path}' created"
24
+ end
25
+
26
+ @logger.es_info "#{f_arr.length} files extracted"
27
+ end
28
+
29
+ # Remove trailing spaces from the base filename and ext.
30
+ def _filter_filename(filename)
31
+ base = File.basename(filename, ".*").rstrip
32
+ ext = File.extname(filename).rstrip
33
+
34
+ base + ext
35
+ end
36
+
37
+ def _write_file(path, _file_obj); end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ module Executor
6
+ class ExtracterTxt < ExtracterBase
7
+ private
8
+
9
+ def _extract_files
10
+ @options[:f_list].map do |fn|
11
+ @volume.f_extract_txt(fn)
12
+ end
13
+ end
14
+
15
+ def _write_file(path, file_obj)
16
+ dry_files = Dry::Files.new
17
+ dry_files.write(path, file_obj.data.join("\n"))
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ module Executor
6
+ class ExtracterTxtAll < ExtracterBase
7
+ private
8
+
9
+ def _extract_files
10
+ @volume.f_extract_txt_all
11
+ end
12
+
13
+ def _write_file(path, file_obj)
14
+ dry_files = Dry::Files.new
15
+ dry_files.write(path, file_obj.data.join("\n"))
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ module Executor
6
+ #
7
+ # Prints info about the volume.
8
+ #
9
+ class Informer < VolReadOperator
10
+ VOL_PAR_L = 24
11
+
12
+ F_NUM_L = 5
13
+ F_STATUS_L = 10
14
+ F_NAME_L = 13
15
+ F_SIZE_L = 4
16
+
17
+ def call
18
+ snapshot = @volume.snapshot
19
+
20
+ _list_files(snapshot[:volume_data])
21
+ puts ""
22
+ puts "--- Volume information: ---"
23
+ _print_n_free_clusters(snapshot[:n_free_clusters])
24
+ _print_vol_params(snapshot[:volume_params])
25
+
26
+ super
27
+ end
28
+
29
+ def _choose_basic(extra_word)
30
+ case extra_word
31
+ when SMPTool::Basic10::ENTRY_EXTRA_WORD
32
+ "BASIC v.1.0"
33
+ when SMPTool::Basic20::ENTRY_EXTRA_WORD
34
+ "BASIC v.2.0"
35
+ else
36
+ "unknown"
37
+ end
38
+ end
39
+
40
+ def _print_n_free_clusters(n_free_clusters)
41
+ puts "N. free clusters:".ljust(VOL_PAR_L) + n_free_clusters.to_s
42
+ end
43
+
44
+ def _print_vol_params(vol_params)
45
+ puts "N. clusters allocated:".ljust(VOL_PAR_L) << vol_params[:n_clusters_allocated].to_s
46
+ puts "N. dir. segments:".ljust(VOL_PAR_L) << vol_params[:n_dir_segs].to_s
47
+ puts "Dir. seg. size:".ljust(VOL_PAR_L) << vol_params[:n_clusters_per_dir_seg].to_s
48
+ puts "Directory capacity:".ljust(VOL_PAR_L) << vol_params[:n_max_entries].to_s
49
+ puts "Volume type:".ljust(VOL_PAR_L) << _choose_basic(vol_params[:extra_word])
50
+ end
51
+
52
+ def _list_files(vol_data)
53
+ _print_file_list_legend
54
+ vol_data.each_with_index { |e, i| _print_file_entry(i, e) }
55
+ end
56
+
57
+ def _print_file_list_legend
58
+ legend = [
59
+ "#".ljust(F_NUM_L),
60
+ "Status".ljust(F_STATUS_L),
61
+ "Filename".ljust(F_NAME_L),
62
+ "Size".to_s.ljust(F_SIZE_L)
63
+ ].join("")
64
+
65
+ puts legend
66
+ puts legend.gsub(/[^ ]/, "-")
67
+ end
68
+
69
+ def _print_file_entry(idx, entry)
70
+ puts (idx + 1).to_s.ljust(F_NUM_L) +
71
+ entry[:status].ljust(F_STATUS_L) +
72
+ entry[:filename].ljust(F_NAME_L) +
73
+ entry[:n_clusters].to_s.ljust(F_SIZE_L)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ module Executor
6
+ #
7
+ # Base class for the executors that do some operation.
8
+ #
9
+ class Operator
10
+ def initialize(logger:, **options)
11
+ @logger = logger
12
+ @options = options
13
+ end
14
+
15
+ def call; end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ module Executor
6
+ class Pusher < VolReadWriteOperator
7
+ def call
8
+ files = _read_files(@options[:f_list])
9
+
10
+ files.each do |f|
11
+ fn = @volume.f_push(f)
12
+
13
+ @logger.info "File '#{fn}' pushed to the volume"
14
+ end
15
+
16
+ @logger.es_info "#{files.length} files were pushed to the volume"
17
+
18
+ super
19
+ end
20
+
21
+ private
22
+
23
+ def _read_files(f_list)
24
+ f_list.map do |path|
25
+ {
26
+ filename: File.basename(path),
27
+ data: _read_file(path).split(/\n/)
28
+ }
29
+ end
30
+ end
31
+
32
+ def _read_file(path)
33
+ @logger.debug "Reading the file: '#{path}'"
34
+ Dry::Files.new.read(path)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ module Executor
6
+ class Renamer < VolReadWriteOperator
7
+ def call
8
+ fns = @volume.f_rename(@options[:old_fn], @options[:new_fn])
9
+ @logger.es_info "File '#{fns.first}' was renamed to '#{fns.last}'"
10
+
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ module Executor
6
+ class Resizer < VolReadWriteOperator
7
+ def call
8
+ n_clusters_changed = @volume.resize(@options[:n_clusters].to_i)
9
+ @logger.es_info _msg(n_clusters_changed)
10
+
11
+ super
12
+ end
13
+
14
+ private
15
+
16
+ def _msg(n_clusters)
17
+ if n_clusters.negative?
18
+ "#{n_clusters.abs} clusters were trimmed from the volume"
19
+ elsif n_clusters.positive?
20
+ "#{n_clusters} clusters were added to the volume"
21
+ else
22
+ "No changes were made"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ module Executor
6
+ class Squeezer < VolReadWriteOperator
7
+ def call
8
+ n_free_clusters = @volume.squeeze
9
+ @logger.es_info "#{n_free_clusters} clusters were joined into one section at the end of the volume"
10
+
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ module Executor
6
+ #
7
+ # Base class for the executors that read a volume and
8
+ # do an operation on it (without saving back).
9
+ #
10
+ class VolReadOperator
11
+ def initialize(input:, logger:, **options)
12
+ @logger = logger
13
+ @input = input
14
+ @volume = _load_volume(path: input, **options)
15
+ @options = options
16
+ end
17
+
18
+ def call; end
19
+
20
+ private
21
+
22
+ #
23
+ # Read and parse volume out of a binary file.
24
+ #
25
+ # @param [String] path
26
+ # Path to the source file.
27
+ #
28
+ # @param [Hash{ Symbol => Object }] **_options
29
+ #
30
+ # @return [SMPTool::VirtualVolume::Volume]
31
+ #
32
+ def _load_volume(path:, **_options)
33
+ b_str = _read_bin_file(path)
34
+ @logger.info "Read data from the file: '#{path}'"
35
+
36
+ vol = _parse_volume(b_str)
37
+ @logger.debug "Volume parsed"
38
+
39
+ vol
40
+ end
41
+
42
+ #
43
+ # Parse binary string to create a volume obj.
44
+ #
45
+ # @param [String] b_str
46
+ #
47
+ # @return [SMPTool::VirtualVolume::Volume]
48
+ #
49
+ def _parse_volume(b_str)
50
+ SMPTool::VirtualVolume::Volume.read_io(
51
+ b_str
52
+ )
53
+ end
54
+
55
+ #
56
+ # Read a binary file.
57
+ #
58
+ # @param [String] path
59
+ #
60
+ # @return [String]
61
+ # Binary string with the file's content.
62
+ #
63
+ def _read_bin_file(path)
64
+ Dry::Files.new.read(path)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ module Executor
6
+ #
7
+ # Base class for the executors that read a volume and
8
+ # do an operation on it, then save the state back to a file.
9
+ #
10
+ class VolReadWriteOperator < VolReadOperator
11
+ include BinWriteMixin
12
+
13
+ def call
14
+ _save_volume(path: @options[:output], volume: @volume, **@options) if @options.key?(:output)
15
+ _save_volume(path: @input, volume: @volume, **@options) if @options[:apply]
16
+
17
+ return if @options.key?(:output) || @options[:apply]
18
+
19
+ @logger.warning "Changes were not saved since you didn't specify the output file " \
20
+ "or the `-a` option to apply changes to the input file"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ #
6
+ # Namespace for classes that execute commands.
7
+ #
8
+ module Executor; end
9
+ end
10
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ QUIET_MODE = 1
6
+ BRIEF_MODE = 2
7
+ VERBOSE_MODE = 3
8
+ DEBUG_MODE = 4
9
+
10
+ LEVELS = {
11
+ "quiet" => QUIET_MODE,
12
+ "brief" => BRIEF_MODE,
13
+ "verbose" => VERBOSE_MODE,
14
+ "debug" => DEBUG_MODE
15
+ }.freeze
16
+
17
+ #
18
+ # Simple logger.
19
+ #
20
+ class Logger
21
+ def initialize(level: "brief")
22
+ @level = _choose_level(level)
23
+ end
24
+
25
+ # Low-priority info.
26
+ def info(msg)
27
+ _print_msg(msg, "Info", VERBOSE_MODE) { |s| puts s }
28
+ end
29
+
30
+ # Essential info.
31
+ def es_info(msg)
32
+ _print_msg(msg, "Info", BRIEF_MODE) { |s| puts s }
33
+ end
34
+
35
+ # Warning.
36
+ def warning(msg)
37
+ _print_msg(msg, "Warn", QUIET_MODE) { |s| warn s }
38
+ end
39
+
40
+ # Error.
41
+ def error(msg)
42
+ _print_msg(msg, "Error", QUIET_MODE) { |s| warn s }
43
+ end
44
+
45
+ # Debug message.
46
+ def debug(msg)
47
+ _print_msg(msg, "Debug", DEBUG_MODE) { |s| puts s }
48
+ end
49
+
50
+ private
51
+
52
+ def _print_msg(msg, type, mode)
53
+ return unless mode <= @level
54
+
55
+ msg = "[#{type}]: #{msg}"
56
+
57
+ yield(msg)
58
+ end
59
+
60
+ def _choose_level(key)
61
+ raise ArgumentError, "Wrong verbosity level: #{key}" unless LEVELS.key?(key.downcase)
62
+
63
+ LEVELS[key.downcase]
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module CLI
5
+ VERSION = "0.1.0"
6
+ end
7
+ end