smp_tool-cli 0.1.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.
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