smp_tool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +13 -0
  3. data/.vscode/launch.json +21 -0
  4. data/CHANGELOG.md +3 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +109 -0
  7. data/Rakefile +16 -0
  8. data/lib/smp_tool/autoloader.rb +24 -0
  9. data/lib/smp_tool/basic_10.rb +95 -0
  10. data/lib/smp_tool/basic_20.rb +96 -0
  11. data/lib/smp_tool/filename.rb +71 -0
  12. data/lib/smp_tool/version.rb +5 -0
  13. data/lib/smp_tool/virtual_volume/data_entry.rb +47 -0
  14. data/lib/smp_tool/virtual_volume/data_entry_header.rb +85 -0
  15. data/lib/smp_tool/virtual_volume/file_interface.rb +33 -0
  16. data/lib/smp_tool/virtual_volume/utils/converter_from_volume_io.rb +81 -0
  17. data/lib/smp_tool/virtual_volume/utils/converter_to_volume_io.rb +125 -0
  18. data/lib/smp_tool/virtual_volume/utils/empty_vol_data_initializer.rb +32 -0
  19. data/lib/smp_tool/virtual_volume/utils/file_converter.rb +55 -0
  20. data/lib/smp_tool/virtual_volume/utils/file_extracter.rb +90 -0
  21. data/lib/smp_tool/virtual_volume/utils/volume_params_validator.rb +20 -0
  22. data/lib/smp_tool/virtual_volume/utils.rb +10 -0
  23. data/lib/smp_tool/virtual_volume/volume.rb +249 -0
  24. data/lib/smp_tool/virtual_volume/volume_data.rb +216 -0
  25. data/lib/smp_tool/virtual_volume/volume_params.rb +70 -0
  26. data/lib/smp_tool/virtual_volume/volume_params_contract.rb +53 -0
  27. data/lib/smp_tool/virtual_volume.rb +8 -0
  28. data/lib/smp_tool/volume_io/bootloader.rb +18 -0
  29. data/lib/smp_tool/volume_io/data.rb +14 -0
  30. data/lib/smp_tool/volume_io/dir_entry.rb +45 -0
  31. data/lib/smp_tool/volume_io/dir_seg.rb +33 -0
  32. data/lib/smp_tool/volume_io/dir_seg_header.rb +23 -0
  33. data/lib/smp_tool/volume_io/directory.rb +26 -0
  34. data/lib/smp_tool/volume_io/file_content.rb +13 -0
  35. data/lib/smp_tool/volume_io/home_block.rb +18 -0
  36. data/lib/smp_tool/volume_io/volume_io.rb +53 -0
  37. data/lib/smp_tool/volume_io.rb +10 -0
  38. data/lib/smp_tool.rb +54 -0
  39. data/sig/smp_tool.rbs +4 -0
  40. data/smp_tool.gemspec +40 -0
  41. metadata +206 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3e06db4465fdd886637351ffbcd5d2247a24b9b2a8adf46cd1fcc9180af36b20
4
+ data.tar.gz: acbaf0a5b49e27eadbef2394cc14b775b8d239f4d0e0786210a24e7da9b0be01
5
+ SHA512:
6
+ metadata.gz: 4c05f7d6b03a2c0e4cbb9660bc5bbd9d59c33ffdb0b2a11c3e0710dfd87cd464fdfbf1f79a5575c38e81ea763ddf20f31c797ae4aa0c0e190a741940ba79320e
7
+ data.tar.gz: 57d4c3726fd390c55d8e4c997a0a80c879cca2b52eb2a347fcfda306c6ae7d73dac19f7edf874b993c6ce9c5c09b094f5878badea44e5bb78155778cdda666e0
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0.0
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
@@ -0,0 +1,21 @@
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "type": "rdbg",
9
+ "name": "Debug current file with rdbg",
10
+ "request": "launch",
11
+ "script": "${file}",
12
+ "args": [],
13
+ "askParameters": true
14
+ },
15
+ {
16
+ "type": "rdbg",
17
+ "name": "Attach with rdbg",
18
+ "request": "attach"
19
+ }
20
+ ]
21
+ }
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## [0.1.0] - 2024-03-14
2
+
3
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 8bit-m8
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # smp_tool
2
+
3
+ A Ruby library to work with the Elektronika MK90 volume images. There's a command-line interface: [smp_tool-cli](https://github.com/8bit-mate/smp_tool-cli.rb).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application"s Gemfile:
8
+
9
+ ```ruby
10
+ gem "smp_tool"
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install smp_tool
20
+
21
+ ## Usage
22
+
23
+ Create a new empty volume:
24
+
25
+ ```Ruby
26
+ require "smp_tool"
27
+
28
+ params = SMPTool::VirtualVolume::VolumeParams.new(
29
+ n_clusters_allocated: 20,
30
+ n_extra_bytes_per_entry: 0,
31
+ n_dir_segs: 1,
32
+ n_clusters_per_dir_seg: 2,
33
+ extra_word: 0
34
+ )
35
+
36
+ volume = SMPTool::VirtualVolume::Volume.new(
37
+ bootloader: SMPTool::Basic10::DEFAULT_BOOTLOADER,
38
+ home_block: SMPTool::Basic10::HOME_BLOCK,
39
+ volume_params: params
40
+ )
41
+ ```
42
+
43
+ Or read an existing volume from a file:
44
+
45
+ ```Ruby
46
+ io = File.read("/path/to/volume/smp0.bin")
47
+ volume = SMPTool::VirtualVolume::Volume.read_io(io)
48
+ ```
49
+
50
+ Now you can perform operations on the `volume` object, e.g.:
51
+
52
+ ```Ruby
53
+ # Push a text file to the volume:
54
+ volume.f_push(
55
+ {
56
+ filename: "hello.bas",
57
+ data: ["10 PRINT \"Hello, world\"", "20 GOTO 10"]
58
+ }
59
+ )
60
+
61
+ # Extract a file by its filename:
62
+ volume.f_extract_txt("hello.bas")
63
+
64
+ # Delete a file from the volume:
65
+ volume.f_delete("hello.bas")
66
+
67
+ # Consolidate all free space at the end of the volume:
68
+ volume.squeeze
69
+
70
+ # Rename a file on the volume:
71
+ volume.f_rename("old.bas", "new.bas")
72
+
73
+ # Allocate more free clusters to the volume:
74
+ volume.resize(10)
75
+
76
+ # ...or trim some free clusters:
77
+ volume.resize(-5)
78
+ ```
79
+
80
+ When done, you can write modified volume back to a binary file:
81
+
82
+ ```Ruby
83
+ data = volume.to_binary_s
84
+ File.binwrite("/path/to/volume/smp0_edited.bin", data)
85
+ ```
86
+
87
+ ## Development
88
+
89
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
90
+
91
+ 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).
92
+
93
+ ## Contributing
94
+
95
+ Bug reports and pull requests are welcome on GitHub at https://github.com/8bit-mate/smp_tool.rb.
96
+
97
+ ## Special thanks to
98
+
99
+ - **[Piotr Piatek](http://www.pisi.com.pl/piotr433/index.htm)**: the indisputable master of the MK90 who developed lots of great software tools and hardware devices for the machine;
100
+
101
+ - **[azya52](https://github.com/azya52/)**: the developer of the PIMP cartridge. This device made possible to load large volumes on a real MK90;
102
+
103
+ - **[flint-1979](https://phantom.sannata.org/memberlist.php?mode=viewprofile&u=6909)**: testing on the real machines with both BASIC v.1.0 and v.2.0;
104
+
105
+ - **[BitSavers project](http://www.bitsavers.org/)**: the largest source of the DEC PDP-11 / RT-11 and other legacy systems documentation.
106
+
107
+ ## License
108
+
109
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module SMPTool
6
+ #
7
+ # Gem's autoloader.
8
+ #
9
+ class Autoloader
10
+ class << self
11
+ def setup
12
+ loader = Zeitwerk::Loader.new
13
+ loader.push_dir(Pathname(__dir__).join("../")) # lib
14
+ loader.inflector.inflect(
15
+ "smp_tool" => "SMPTool",
16
+ "volume_io" => "VolumeIO",
17
+ "converter_from_volume_io" => "ConverterFromVolumeIO",
18
+ "converter_to_volume_io" => "ConverterToVolumeIO"
19
+ )
20
+ loader.setup
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ #
5
+ # Namespace for the MK90 BASIC v.1.0 constants.
6
+ #
7
+ module Basic10
8
+ # BASIC v.1.0 default boot-loader.
9
+ DEFAULT_BOOTLOADER = [
10
+ 0xA0, 0x00, 0xDF, 0x09, 0xC0, 0xD7, 0xDF, 0x09, 0x5E, 0xA1, 0xDF, 0x17, 0x12, 0x38, 0x00, 0xE8,
11
+ 0xDF, 0x15, 0xC6, 0x88, 0x02, 0xE8, 0x37, 0x08, 0x18, 0x00, 0x0E, 0x0D, 0x0A, 0x20, 0x73, 0x6D,
12
+ 0x70, 0x20, 0x42, 0x45, 0x5A, 0x20, 0x5A, 0x41, 0x47, 0x52, 0x55, 0x5A, 0x5E, 0x49, 0x4B, 0x41,
13
+ 0x00, 0xFF, 0x02, 0x94, 0xFF, 0x03, 0xDF, 0x09, 0x62, 0x9D, 0xFB, 0x01
14
+ ].freeze
15
+
16
+ # BASIC v1.0 file auto-loader by Piotr Piatek.
17
+ # from: http://web.archive.org/web/20160302042532/http://www.pisi.com.pl/piotr433/mk90au10.zip
18
+ AUTO_BOOTLOADER = [
19
+ 0xA0, 0x00, 0xC5, 0x45, 0xFE, 0xFF, 0xC5, 0x65, 0x30, 0x00, 0xC1, 0x15, 0xA0, 0x01, 0x40, 0x94,
20
+ 0x77, 0x03, 0xC0, 0xA5, 0x78, 0x00, 0xFB, 0x02, 0x61, 0x91, 0xF9, 0x01, 0x00, 0x00, 0x00, 0x00,
21
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
22
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
23
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
24
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
25
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
26
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
27
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
28
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
29
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
30
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
31
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
32
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
33
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
34
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
35
+ 0xC2, 0x15, 0x00, 0x01, 0x22, 0x0A, 0xE2, 0x15, 0x06, 0x80, 0xC2, 0x0B, 0xFB, 0x02, 0xD2, 0x15,
36
+ 0x5F, 0x00, 0xD2, 0x15, 0x12, 0x9D, 0xDF, 0x15, 0x6A, 0x80, 0x1C, 0x00, 0xDF, 0x09, 0xC0, 0xD7,
37
+ 0x6C, 0x89, 0xC1, 0x15, 0x3E, 0x2F, 0x46, 0x10, 0x5F, 0x10, 0x10, 0x38, 0xC5, 0x15, 0x00, 0x01,
38
+ 0x5F, 0x11, 0xEC, 0x38, 0xD5, 0x95, 0x0A, 0x00, 0x1F, 0x0A, 0xFA, 0x38, 0x1F, 0x0A, 0xEE, 0x38,
39
+ 0xC3, 0x15, 0x98, 0x38, 0xC2, 0x15, 0xA0, 0x01, 0xC1, 0x10, 0x93, 0x94, 0xFE, 0x02, 0x5F, 0x00,
40
+ 0x7E, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
41
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
42
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
44
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
45
+ 0x4C, 0x4F, 0x41, 0x44, 0x22, 0x53, 0x4D, 0x78, 0x3A, 0x41, 0x55, 0x54, 0x4F, 0x22, 0x2C, 0x52,
46
+ 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
47
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
48
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
49
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
50
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
51
+ ].freeze
52
+
53
+ # BASIC v.1.0 home block.
54
+ HOME_BLOCK = [
55
+ 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
56
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
57
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
58
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
59
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
60
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
61
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
62
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
63
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
64
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
65
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
66
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
67
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
68
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
69
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
70
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
71
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
72
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
73
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
74
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
75
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
76
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
77
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
78
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
79
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
80
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
81
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
82
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
83
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
84
+ 0x20, 0x20, 0x01, 0x00, 0x06, 0x00, 0x4F, 0x8E, 0x42, 0x41, 0x53, 0x49, 0x43, 0x20, 0x20, 0x20,
85
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
86
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20
87
+ ].freeze
88
+
89
+ # BASIC v.1.0 doesn't use extra bytes in the directory entries.
90
+ N_EXTRA_BYTES_PER_ENTRY = 0
91
+
92
+ # Extra word will not be added.
93
+ ENTRY_EXTRA_WORD = 0
94
+ end
95
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ #
5
+ # Namespace for the MK90 BASIC v.2.0 constants.
6
+ #
7
+ module Basic20
8
+ # BASIC v.2.0 default boot-loader.
9
+ DEFAULT_BOOTLOADER = [
10
+ 0xA0, 0x00, 0xDF, 0x09, 0x6C, 0xCA, 0xDF, 0x09, 0xD4, 0xA4, 0xDF, 0x17, 0x02, 0x0D, 0x00, 0xE8,
11
+ 0xDF, 0x15, 0xC6, 0x88, 0x02, 0xE8, 0x37, 0x08, 0x18, 0x00, 0x0E, 0x0D, 0x0A, 0x20, 0x73, 0x6D,
12
+ 0x70, 0x20, 0x42, 0x45, 0x5A, 0x20, 0x5A, 0x41, 0x47, 0x52, 0x55, 0x5A, 0x5E, 0x49, 0x4B, 0x41,
13
+ 0x00, 0xFF, 0x02, 0x94, 0xFF, 0x03, 0xDF, 0x09, 0x1C, 0xA0, 0xFB, 0x01
14
+ ].freeze
15
+
16
+ # BASIC v2.0 file auto-loader by Piotr Piatek.
17
+ # from: http://web.archive.org/web/20160302042532/http://www.pisi.com.pl/piotr433/mk90au20.zip
18
+ # + a fix: LOAD"SMx:AUTO",A,R instead of just LOAD"SMx:AUTO",R
19
+ AUTO_BOOTLOADER = [
20
+ 0xA0, 0x00, 0xC5, 0x45, 0xFE, 0xFF, 0xC5, 0x65, 0x30, 0x00, 0xC1, 0x15, 0xA0, 0x01, 0x40, 0x94,
21
+ 0x77, 0x03, 0xC0, 0xA5, 0x78, 0x00, 0xFB, 0x02, 0x61, 0x91, 0xDF, 0x15, 0x00, 0x10, 0x1A, 0xE8,
22
+ 0xF6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
23
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
24
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
25
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
26
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
27
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
28
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
29
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
30
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
31
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
32
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
33
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
34
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
35
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
36
+ 0xC2, 0x15, 0x00, 0x01, 0x22, 0x0A, 0xE2, 0x15, 0x00, 0x80, 0xC2, 0x0B, 0xFB, 0x02, 0xD2, 0x15,
37
+ 0x5F, 0x00, 0xD2, 0x15, 0xCE, 0x9F, 0xDF, 0x15, 0x8A, 0x80, 0x1C, 0x00, 0xDF, 0x09, 0x6C, 0xCA,
38
+ 0x6C, 0x89, 0xC1, 0x15, 0x3E, 0x3F, 0x46, 0x10, 0x5F, 0x10, 0x00, 0x0D, 0xC1, 0x15, 0x58, 0x0F,
39
+ 0x5F, 0x10, 0xF2, 0x0E, 0x45, 0x10, 0xD5, 0x95, 0x0A, 0x00, 0x1F, 0x0A, 0x00, 0x0F, 0x1F, 0x0A,
40
+ 0xF4, 0x0E, 0x1F, 0x0A, 0x20, 0x0F, 0xDF, 0x09, 0x96, 0xAA, 0x1F, 0x0A, 0x38, 0x0F, 0x1F, 0x0A,
41
+ 0x1A, 0x0F, 0x1F, 0x0A, 0x8E, 0x0D, 0x1F, 0x0A, 0x0E, 0x0F, 0xC3, 0x15, 0x9E, 0x0E, 0xC2, 0x15,
42
+ 0xA0, 0x01, 0xC1, 0x10, 0x93, 0x94, 0xFE, 0x02, 0x5F, 0x00, 0x1C, 0x87, 0x00, 0x00, 0x00, 0x00,
43
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
44
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
45
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
46
+ 0x4C, 0x4F, 0x41, 0x44, 0x22, 0x53, 0x4D, 0x78, 0x3A, 0x41, 0x55, 0x54, 0x4F, 0x22, 0x2C, 0x41,
47
+ 0x2C, 0x52, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
48
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
49
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
50
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
51
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
52
+ ].freeze
53
+
54
+ # BASIC v.2.0 home block.
55
+ HOME_BLOCK = [
56
+ 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
57
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
58
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
59
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
60
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
61
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
62
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
63
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
64
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
65
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
66
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
67
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
68
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
69
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
70
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
71
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
72
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
73
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
74
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
75
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
76
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
77
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
78
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
79
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
80
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
81
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
82
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
83
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
84
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
85
+ 0x20, 0x20, 0x01, 0x00, 0x06, 0x00, 0x50, 0x8E, 0x42, 0x41, 0x53, 0x49, 0x43, 0x20, 0x20, 0x20,
86
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
87
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20
88
+ ].freeze
89
+
90
+ # BASIC v.2.0. reserves two bytes for an extra word in each directory entry.
91
+ N_EXTRA_BYTES_PER_ENTRY = 2
92
+
93
+ # BASIC v.2.0 directory entry extra word.
94
+ ENTRY_EXTRA_WORD = 0x00A0
95
+ end
96
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ #
5
+ # Converts RADIX-50 <-> ASCII filenames.
6
+ #
7
+ class Filename
8
+ attr_reader :radix50, :ascii
9
+
10
+ FN_BASE_LENGTH = 6
11
+ FN_EXT_LENGTH = 3
12
+
13
+ ASCII_DOT_POS = 6 # Dot position in the printable ASCII filename.
14
+ DEF_SEP_CHR = "." # Separation character.
15
+
16
+ def initialize(options = {})
17
+ _validate_options(options)
18
+
19
+ if options.key?(:radix50)
20
+ _handle_radix_input(options)
21
+ elsif options.key?(:ascii)
22
+ _handle_ascii_input(options)
23
+ else
24
+ raise ArgumentError, "Either :radix50 or :ascii must be provided"
25
+ end
26
+
27
+ _make_ascii_name
28
+ end
29
+
30
+ #
31
+ # ASCII filename with a dot to separate extension.
32
+ #
33
+ def print_ascii(sep_chr = DEF_SEP_CHR)
34
+ @ascii.insert(ASCII_DOT_POS, sep_chr)
35
+ end
36
+
37
+ private
38
+
39
+ def _validate_options(options)
40
+ return unless options.key?(:radix50) && options.key?(:ascii)
41
+
42
+ raise ArgumentError, ":radix50 and :ascii are mutually exclusive"
43
+ end
44
+
45
+ def _make_ascii_name
46
+ @ascii = DECRadix50.decode(DECRadix50::MK90_CHARSET, @radix50)
47
+ end
48
+
49
+ def _handle_radix_input(options)
50
+ @radix50 = _enforce_radix(options[:radix50])
51
+ end
52
+
53
+ def _handle_ascii_input(options)
54
+ @radix50 = DECRadix50.encode(
55
+ DECRadix50::MK90_CHARSET, _enforce_ascii(options[:ascii])
56
+ )
57
+ end
58
+
59
+ def _enforce_ascii(str)
60
+ parts = str.split(".")
61
+ filename = (parts[0] || "").slice(0, FN_BASE_LENGTH).ljust(FN_BASE_LENGTH).upcase
62
+ ext = (parts[1] || "").slice(0, FN_EXT_LENGTH).ljust(FN_EXT_LENGTH).upcase
63
+
64
+ filename + ext
65
+ end
66
+
67
+ def _enforce_radix(arr)
68
+ arr.slice(0, RAD50_FN_SIZE).fill(0, arr.length...RAD50_FN_SIZE)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module VirtualVolume
5
+ #
6
+ # Volume data entry: permanent file or empty entry.
7
+ #
8
+ class DataEntry
9
+ extend Forwardable
10
+
11
+ def_delegators :@header, :status, :filename, :n_clusters, :ch_job, :date, :extra_word,
12
+ :rename, :permanent_entry?, :empty_entry?, :snapshot, :print_ascii_filename
13
+
14
+ attr_reader :header, :data
15
+
16
+ def initialize(header:, data:)
17
+ @header = header
18
+ @data = data
19
+ end
20
+
21
+ #
22
+ # Resize data string to a new size.
23
+ #
24
+ # @param [Integer] new_size
25
+ #
26
+ # @return [DataEntry] self
27
+ #
28
+ def resize(new_size)
29
+ @header.resize(new_size)
30
+
31
+ @data = @data.slice(0, new_size).ljust(new_size, PAD_CHR)
32
+
33
+ self
34
+ end
35
+
36
+ #
37
+ # Turn `self` into an empty entry.
38
+ #
39
+ def clean
40
+ @header.clean
41
+ @data = @data.gsub(/./m, PAD_CHR)
42
+
43
+ self
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module VirtualVolume
5
+ #
6
+ # Header of a 'virtual' data entry.
7
+ #
8
+ class DataEntryHeader
9
+ attr_reader :filename, :status, :n_clusters, :ch_job, :date, :extra_word
10
+
11
+ def initialize(params)
12
+ @status = params[:status]
13
+ @filename = params[:filename]
14
+ @n_clusters = params[:n_clusters]
15
+ @ch_job = params[:ch_job]
16
+ @date = params[:date]
17
+ @extra_word = params[:extra_word] || Basic10::ENTRY_EXTRA_WORD
18
+ end
19
+
20
+ def print_ascii_filename
21
+ Filename.new(radix50: @filename).print_ascii
22
+ end
23
+
24
+ def snapshot
25
+ {
26
+ status: _status_snapshot,
27
+ filename: print_ascii_filename,
28
+ n_clusters: @n_clusters,
29
+ ch_job: @ch_job,
30
+ date: @date,
31
+ extra_word: @extra_word
32
+ }
33
+ end
34
+
35
+ def resize(new_size)
36
+ @n_clusters = new_size
37
+ end
38
+
39
+ def permanent_entry?
40
+ @status == PERM_ENTRY
41
+ end
42
+
43
+ def empty_entry?
44
+ @status == EMPTY_ENTRY
45
+ end
46
+
47
+ def make_permanent
48
+ _set_status(PERM_ENTRY)
49
+ end
50
+
51
+ def make_empty
52
+ _set_status(EMPTY_ENTRY)
53
+ end
54
+
55
+ def clean
56
+ make_empty
57
+
58
+ rename(
59
+ [PAD_WORD, PAD_WORD, PAD_WORD]
60
+ )
61
+ end
62
+
63
+ def rename(new_radix_id)
64
+ @filename = new_radix_id
65
+ end
66
+
67
+ private
68
+
69
+ def _status_snapshot
70
+ case @status
71
+ when EMPTY_ENTRY
72
+ "empty"
73
+ when PERM_ENTRY
74
+ "file"
75
+ else
76
+ "unknown"
77
+ end
78
+ end
79
+
80
+ def _set_status(new_status)
81
+ @status = new_status
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMPTool
4
+ module VirtualVolume
5
+ #
6
+ # Interface to add/retrieve files to/from a volume.
7
+ #
8
+ class FileInterface
9
+ attr_accessor :filename, :data
10
+
11
+ def initialize(filename:, data:)
12
+ @filename = filename
13
+ @data = data
14
+ end
15
+
16
+ # Hash-like interface to access keys in the +[]+ syntax.
17
+ def [](key) = send(key)
18
+
19
+ # Hash-like interface to access keys in the +[]=+ syntax.
20
+ def []=(key, value)
21
+ send("#{key}=", value)
22
+ end
23
+
24
+ # Return `self` as a hash.
25
+ def to_h
26
+ {
27
+ filename: @filename,
28
+ data: @data
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end