diff --git a/.code_preloader.yml b/.code_preloader.yml index 97920cc..bcfda9b 100644 --- a/.code_preloader.yml +++ b/.code_preloader.yml @@ -7,16 +7,21 @@ # - "path/to/repo2" # List of patterns to ignore during preloading -ignore_list: - - ^\.git/ - - ^lib.* - - ^doc/ +exclude_list: - ^bin/ + - ^\.code_preloader.yml + - ^doc/ + - ^\.drone.yml + - ^\.git/ + - ^\.gitattributes + - ^\.gitignore + - ^lib.* + - ^LICENSES/ - ^_prompts/ - ^\.reuse/ - - ^LICENSES/ - - ^\.vagrant/ - ^scripts/ + - ^\.tool-versions + - ^\.vagrant/ # Path to the output file (if null, output to STDOUT) output_path: null diff --git a/.gitignore b/.gitignore index ccd3fc2..33ba6d4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ .vagrant bin lib +.aider* +.env diff --git a/Makefile b/Makefile index f0c5bb8..fb3baac 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ prepare: shards install build: - shards build --error-trace -Dpreview_mt + shards build --progress --error-trace -Dpreview_mt @echo SUCCESS watch: @@ -21,10 +21,13 @@ spec: test test: crystal spec --error-trace +format: + crystal tool format + install: install \ -m 755 \ - bin/code-preloader \ + bin/mfm \ $(PREFIX)/bin .PHONY: spec test build all prepare install diff --git a/README.md b/README.md index 254cb91..4186a45 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ > version of our project, please visit our primary repository at: > . + + # Minimalist Fuse Manager (MFM) MFM is a Crystal-lang CLI designed to streamline the management of various FUSE filesystems, such as sshfs, gocryptfs, httpdirfs, and more. Through its user-friendly interface, users can effortlessly mount and unmount filesystems, get real-time filesystem status, and handle errors proficiently. @@ -44,18 +46,23 @@ To build from source, you'll also need: For Debian/Ubuntu you can use the following command: ```shell-session -$ sudo apt-get update && sudo apt-get install libpcre3-dev libevent-2.1-dev +$ sudo apt-get update && sudo apt-get install libpcre3-dev libevent-2.1-dev make ``` ## Installation ### 1. From Source -1. Clone or download the source code. -2. Navigate to the source directory. -3. Run `shards install` to fetch dependencies. -4. Compile using `shards build`. -5. The compiled binary will be in the `bin` directory. +To get started with MFM, ensure that you have the prerequisites installed on your system (see above). + +Then follow these steps to install: + + git clone https://code.apps.glenux.net/glenux/mfm + cd mfm + make prepare + make build + sudo make install # either to install system-wide + make install PREFIX=$HOME/.local # or to install as a user ### 2. Binary Download diff --git a/Vagrantfile b/Vagrantfile index 6ea27b7..9d805c0 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,3 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + # frozen_string_literal: true # -*- mode: ruby -*- diff --git a/shard.lock b/shard.lock index e19d81c..216e1db 100644 --- a/shard.lock +++ b/shard.lock @@ -4,6 +4,10 @@ shards: git: https://github.com/crystal-ameba/ameba.git version: 1.6.1 + baked_file_system: + git: https://github.com/schovi/baked_file_system.git + version: 0.10.0 + crinja: git: https://github.com/straight-shoota/crinja.git version: 0.8.1 diff --git a/shard.yml b/shard.yml index 3b29118..d65fad5 100644 --- a/shard.yml +++ b/shard.yml @@ -26,6 +26,9 @@ dependencies: github: hugopl/version_from_shard tablo: github: hutou/tablo + baked_file_system: + github: schovi/baked_file_system + version: 0.10.0 development_dependencies: ameba: diff --git a/spec/commands/mapping_create_spec.cr b/spec/commands/mapping_create_spec.cr new file mode 100644 index 0000000..e03e191 --- /dev/null +++ b/spec/commands/mapping_create_spec.cr @@ -0,0 +1,13 @@ +require "../spec_helper" +require "../../src/commands/mapping_create" + +describe GX::Commands::MappingCreate do + context "Initialization" do + it "initializes with a mock FileSystemManager and RootConfig" do + config = GX::Config.new + root_config = GX::Models::RootConfig.new + command = GX::Commands::MappingCreate.new(config) + command.should be_a(GX::Commands::MappingCreate) + end + end +end diff --git a/spec/commands/mapping_edit_spec.cr b/spec/commands/mapping_edit_spec.cr new file mode 100644 index 0000000..2bedf71 --- /dev/null +++ b/spec/commands/mapping_edit_spec.cr @@ -0,0 +1,12 @@ +require "../spec_helper" +require "../../src/commands/mapping_edit" + +describe GX::Commands::MappingEdit do + context "Initialization" do + it "initializes with a mock FileSystemManager" do + config = GX::Config.new + command = GX::Commands::MappingEdit.new(config) + command.should be_a(GX::Commands::MappingEdit) + end + end +end diff --git a/spec/commands/mapping_list_spec.cr b/spec/commands/mapping_list_spec.cr new file mode 100644 index 0000000..a0f5890 --- /dev/null +++ b/spec/commands/mapping_list_spec.cr @@ -0,0 +1,108 @@ + +require "../spec_helper" +require "../../src/commands/mapping_list" +require "../../src/models/gocryptfs_config" +require "../../src/models/sshfs_config" +require "../../src/models/httpdirfs_config" + +describe GX::Commands::MappingList do + context "Initialization" do + it "initializes with a mock FileSystemManager and RootConfig" do + config = GX::Config.new + root_config = GX::Models::RootConfig.new + command = GX::Commands::MappingList.new(config) + command.should be_a(GX::Commands::MappingList) + end + end + + context "Functioning" do + it "lists mappings when there are no filesystems" do + config = GX::Config.new + root_config = GX::Models::RootConfig.new + command = GX::Commands::MappingList.new(config) + + output = capture_output do + command.execute + end + + output.should include("TYPE") + output.should include("NAME") + output.should include("MOUNTED") + end + + it "lists mappings when there are multiple filesystems" do + config = GX::Config.new + root_config = GX::Models::RootConfig.new + + gocryptfs_config = GX::Models::GoCryptFSConfig.new( + GX::Parsers::Options::MappingCreateOptions.new( + type: "gocryptfs", + name: "test_gocryptfs", + encrypted_path: "/encrypted/path" + ) + ) + sshfs_config = GX::Models::SshFSConfig.new( + GX::Parsers::Options::MappingCreateOptions.new( + type: "sshfs", + name: "test_sshfs", + remote_user: "user", + remote_host: "host", + remote_path: "/remote/path" + ) + ) + httpdirfs_config = GX::Models::HttpDirFSConfig.new( + GX::Parsers::Options::MappingCreateOptions.new( + type: "httpdirfs", + name: "test_httpdirfs", + url: "http://example.com" + ) + ) + + root_config.add_filesystem(gocryptfs_config) + root_config.add_filesystem(sshfs_config) + root_config.add_filesystem(httpdirfs_config) + + command = GX::Commands::MappingList.new(config) + + output = capture_output do + command.execute + end + + output.should include("gocryptfs") + output.should include("test_gocryptfs") + output.should include("false") + + output.should include("sshfs") + output.should include("test_sshfs") + output.should include("false") + + output.should include("httpdirfs") + output.should include("test_httpdirfs") + output.should include("false") + end + + it "ensures the output format is correct" do + config = GX::Config.new + root_config = GX::Models::RootConfig.new + config.instance_variable_set("@root", root_config) + + gocryptfs_config = GX::Models::GoCryptFSConfig.new( + GX::Parsers::Options::MappingCreateOptions.new( + type: "gocryptfs", + name: "test_gocryptfs", + encrypted_path: "/encrypted/path" + ) + ) + root_config.add_filesystem(gocryptfs_config) + + command = GX::Commands::MappingList.new(config) + + output = capture_output do + command.execute + end + + output.should match(/TYPE\s+NAME\s+MOUNTED/) + output.should match(/gocryptfs\s+test_gocryptfs\s+false/) + end + end +end diff --git a/spec/parsers/completion_parser_spec.cr b/spec/parsers/completion_parser_spec.cr new file mode 100644 index 0000000..e69de29 diff --git a/spec/parsers/config_parser_spec.cr b/spec/parsers/config_parser_spec.cr new file mode 100644 index 0000000..ac9deee --- /dev/null +++ b/spec/parsers/config_parser_spec.cr @@ -0,0 +1,68 @@ +require "../spec_helper" +require "../../src/parsers/config_parser" + +describe GX::Parsers::ConfigParser do + context "Initialization" do + it "can initialize" do + GX::Parsers::ConfigParser.new.should be_a(GX::Parsers::ConfigParser) + end + end + + context "Functioning" do + it "can parse 'init' subcommand" do + config = GX::Config.new + parser = OptionParser.new + breadcrumbs = GX::Utils::BreadCrumbs.new(["mfm"]) + + GX::Parsers::ConfigParser.new.build(parser, breadcrumbs, config) + + # Test 'init' subcommand recognition + config.mode.should eq(GX::Types::Mode::GlobalTui) # default + parser.parse(["init"]) + config.mode.should eq(GX::Types::Mode::ConfigInit) + + # Test ConfigInitOptions instantiation + config.config_init_options.should be_a(GX::Parsers::Options::ConfigInitOptions) + + # Test banner update + # FIXME: parser.banner.should include("Create initial mfm configuration") + + # Test separator presence + # FIXME: parser.banner.should include("Init options") + end + + it "can parse '-p' / '--path' option for 'init' subcommand" do + config = GX::Config.new + parser = OptionParser.new + breadcrumbs = GX::Utils::BreadCrumbs.new(["mfm"]) + + GX::Parsers::ConfigParser.new.build(parser, breadcrumbs, config) + parser.parse(["init", "-p", "/test/path"]) + pp config + config.config_init_options.try do |opts| + opts.path.should eq("/test/path") + end + + config = GX::Config.new + parser = OptionParser.new + breadcrumbs = GX::Utils::BreadCrumbs.new(["mfm"]) + + GX::Parsers::ConfigParser.new.build(parser, breadcrumbs, config) + parser.parse(["init", "--path", "/test/path/2"]) + config.config_init_options.try do |opts| + opts.path.should eq("/test/path/2") + end + end + + it "should include help line for 'init' subcommand" do + config = GX::Config.new + parser = OptionParser.new + breadcrumbs = GX::Utils::BreadCrumbs.new(["mfm"]) + + GX::Parsers::ConfigParser.new.build(parser, breadcrumbs, config) + + # Test help line presence + # FIXME: parser.banner.should include("Run 'mfm config init --help' for more information on a command.") + end + end +end diff --git a/spec/parsers/mapping_parser_spec.cr b/spec/parsers/mapping_parser_spec.cr new file mode 100644 index 0000000..e69de29 diff --git a/spec/parsers/root_parser_spec.cr b/spec/parsers/root_parser_spec.cr new file mode 100644 index 0000000..e69de29 diff --git a/spec/utils/breadcrumbs_spec.cr b/spec/utils/breadcrumbs_spec.cr index deec943..1d97594 100644 --- a/spec/utils/breadcrumbs_spec.cr +++ b/spec/utils/breadcrumbs_spec.cr @@ -37,10 +37,10 @@ describe GX::Utils::BreadCrumbs do b1.to_s.should eq("") b2 = b1 + "test1" - b2.to_a.should eq("test1") + b2.to_s.should eq("test1") b3 = b2 + "test2" - b3.to_a.should eq("test1 test2") + b3.to_s.should eq("test1 test2") end end end diff --git a/src/cli.cr b/src/cli.cr index 4366d07..2498ee3 100644 --- a/src/cli.cr +++ b/src/cli.cr @@ -30,13 +30,22 @@ module GX Parsers::RootParser.new.build(parser, breadcrumbs, @config) end pparser.parse(args) + rescue e : OptionParser::MissingOption + STDERR.puts "ERROR: #{e.message}".colorize(:red) + exit(1) end def run command = CommandFactory.create_command(@config, @config.mode) abort("ERROR: unknown command for mode #{@config.mode}") if command.nil? - command.try &.execute + command.execute + rescue e : ArgumentError + STDERR.puts "ERROR: #{e.message}".colorize(:red) + exit(1) + rescue e : Exception + STDERR.puts "ERROR: #{e.message}".colorize(:red) + exit(1) end end end diff --git a/src/command_factory.cr b/src/command_factory.cr index c2d7e22..58ef721 100644 --- a/src/command_factory.cr +++ b/src/command_factory.cr @@ -1,3 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./commands" module GX diff --git a/src/commands.cr b/src/commands.cr index 034a066..646088a 100644 --- a/src/commands.cr +++ b/src/commands.cr @@ -1 +1,6 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./commands/*" diff --git a/src/commands/abstract_command.cr b/src/commands/abstract_command.cr index 7591f15..c6f8296 100644 --- a/src/commands/abstract_command.cr +++ b/src/commands/abstract_command.cr @@ -1,3 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "../config" module GX::Commands diff --git a/src/commands/config_init.cr b/src/commands/config_init.cr index b9407d3..2899954 100644 --- a/src/commands/config_init.cr +++ b/src/commands/config_init.cr @@ -1,11 +1,51 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./abstract_command" +require "../file_storage" module GX::Commands class ConfigInit < AbstractCommand - def initialize(config : GX::Config) # FIXME + def initialize(@config : GX::Config) end def execute + config_dir = File.join(@config.home_dir, ".config", "mfm") + config_file_path = File.join(config_dir, "config.yml") + + # Override the configuration path if provided + puts "Configuration file path: #{config_file_path}" + puts "Configuration file path: #{@config.path}" + # pp @config + @config.path.try do |path| + config_file_path = path + config_dir = File.dirname(path) + end + exit 1 + + # Guard condition to exit if the configuration file already exists + if File.exists?(config_file_path) + puts "Configuration file already exists at #{config_file_path}. No action taken." + return + end + + puts "Creating initial configuration file at #{config_file_path}" + + # Ensure the configuration directory exists + FileUtils.mkdir_p(config_dir) + + # Read the default configuration content from the baked file storage + default_config_content = FileStorage.get("sample.mfm.yaml") + + # Write the default configuration to the target path + File.write(config_file_path, default_config_content) + + puts "Configuration file created successfully." + rescue ex + STDERR.puts "Error creating the configuration file: #{ex.message}" + exit(1) end def self.handles_mode diff --git a/src/commands/global_completion.cr b/src/commands/global_completion.cr index d1be696..7451b49 100644 --- a/src/commands/global_completion.cr +++ b/src/commands/global_completion.cr @@ -1,3 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./abstract_command" module GX::Commands @@ -6,10 +11,12 @@ module GX::Commands end def execute + puts "FIXME: detect option (either zsh or bash)" + puts "FIXME: output the right file from embedded data" end def self.handles_mode - GX::Types::Mode::GlobalConfig + GX::Types::Mode::GlobalCompletion end end end diff --git a/src/commands/global_config.cr b/src/commands/global_config.cr index c369556..4e617d3 100644 --- a/src/commands/global_config.cr +++ b/src/commands/global_config.cr @@ -1,8 +1,13 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./abstract_command" module GX::Commands class GlobalConfig < AbstractCommand - def initialize(config : GX::Config) # FIXME + def initialize(config : GX::Config) end def execute diff --git a/src/commands/global_help.cr b/src/commands/global_help.cr index 78f1616..36b87da 100644 --- a/src/commands/global_help.cr +++ b/src/commands/global_help.cr @@ -1,8 +1,13 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./abstract_command" module GX::Commands class GlobalHelp < AbstractCommand - def initialize(@config : GX::Config) # FIXME + def initialize(@config : GX::Config) end def execute diff --git a/src/commands/global_mapping.cr b/src/commands/global_mapping.cr deleted file mode 100644 index 0c61b95..0000000 --- a/src/commands/global_mapping.cr +++ /dev/null @@ -1,16 +0,0 @@ -require "./abstract_command" - -module GX::Commands - class GlobalMapping < AbstractCommand - def initialize(config : GX::Config) # FIXME - end - - def execute - # FIXME: implement - end - - def self.handles_mode - GX::Types::Mode::GlobalMapping - end - end -end diff --git a/src/commands/global_tui.cr b/src/commands/global_tui.cr index 58f22ed..64f1d4d 100644 --- a/src/commands/global_tui.cr +++ b/src/commands/global_tui.cr @@ -1,9 +1,14 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./abstract_command" require "../file_system_manager" module GX::Commands class GlobalTui < AbstractCommand - @file_system_manager : FileSystemManager + # @file_system_manager : FileSystemManager def initialize(@config : GX::Config) @config.load_from_env @@ -15,7 +20,7 @@ module GX::Commands filesystem = @file_system_manager.choose_filesystem raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil? @file_system_manager.mount_or_umount(filesystem) - @file_system_manager.auto_open(filesystem) if filesystem.mounted? && @config.auto_open + @file_system_manager.auto_open(filesystem) if filesystem.mounted? && @config.auto_open? end def self.handles_mode diff --git a/src/commands/global_version.cr b/src/commands/global_version.cr index 9cd6757..5ab5fb3 100644 --- a/src/commands/global_version.cr +++ b/src/commands/global_version.cr @@ -1,9 +1,14 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./abstract_command" require "../config" module GX::Commands class GlobalVersion < AbstractCommand - def initialize(config : GX::Config) # FIXME + def initialize(config : GX::Config) end def execute diff --git a/src/commands/mapping_create.cr b/src/commands/mapping_create.cr index 1e3ec6f..6dab79e 100644 --- a/src/commands/mapping_create.cr +++ b/src/commands/mapping_create.cr @@ -1,16 +1,57 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./abstract_command" +require "../models/filesystem_factory" module GX::Commands class MappingCreate < AbstractCommand - def initialize(config : GX::Config) # FIXME + def initialize(@config : GX::Config) + @config.load_from_env + @config.load_from_file + @config.save_to_file end def execute - # FIXME: implement + # Assuming mapping_create_options is passed to this command with necessary details + create_options = @config.mapping_create_options + + # Validate required arguments + if create_options.nil? + raise ArgumentError.new("Mapping create options are required") + end + if create_options.name.nil? || create_options.name.try &.empty? + raise ArgumentError.new("Name is required to create a mapping.") + end + if create_options.type.nil? || create_options.type.try &.empty? + raise ArgumentError.new("Type is required to create a mapping.") + end + + # Create the appropriate filesystem config based on the type + filesystem_config = GX::Models::FilesystemFactory.build(create_options) + + # Append the new filesystem config to the root config + @config.root.try do |root| + root.filesystems ||= [] of GX::Models::AbstractFilesystemConfig + root.filesystems << filesystem_config + end + + puts "Mapping '#{create_options.name}' created and added to configuration successfully." end def self.handles_mode GX::Types::Mode::MappingCreate end + + + # validate create_options.PARAMETER and display error with description if + # missing + macro option_check(create_options, parameter, description) + if create_options.{{ parameter.id }}.nil? || create_options.{{ parameter.id }}.try &.empty? + raise ArgumentError.new("Parameter for " + {{description}} + " is required") + end + end end end diff --git a/src/commands/mapping_delete.cr b/src/commands/mapping_delete.cr index bed0534..60a86e6 100644 --- a/src/commands/mapping_delete.cr +++ b/src/commands/mapping_delete.cr @@ -1,12 +1,17 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./abstract_command" module GX::Commands class MappingDelete < AbstractCommand - def initialize(config : GX::Config) # FIXME + def initialize(config : GX::Config) end def execute - # FIXME: implement + # TODO: implement end def self.handles_mode diff --git a/src/commands/mapping_edit.cr b/src/commands/mapping_edit.cr index b0ef961..2169173 100644 --- a/src/commands/mapping_edit.cr +++ b/src/commands/mapping_edit.cr @@ -1,12 +1,17 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./abstract_command" module GX::Commands class MappingEdit < AbstractCommand - def initialize(config : GX::Config) # FIXME + def initialize(config : GX::Config) end def execute - # FIXME: implement + # TODO: implement end def self.handles_mode diff --git a/src/commands/mapping_list.cr b/src/commands/mapping_list.cr index 35cd299..a3a6286 100644 --- a/src/commands/mapping_list.cr +++ b/src/commands/mapping_list.cr @@ -1,3 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./abstract_command" require "../file_system_manager" require "tablo" @@ -7,7 +12,6 @@ module GX::Commands def initialize(@config : GX::Config) @config.load_from_env @config.load_from_file - @file_system_manager = FileSystemManager.new(@config) end def execute diff --git a/src/commands/mapping_mount.cr b/src/commands/mapping_mount.cr index db81351..e8ab080 100644 --- a/src/commands/mapping_mount.cr +++ b/src/commands/mapping_mount.cr @@ -1,26 +1,41 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./abstract_command" require "../file_system_manager" module GX::Commands class MappingMount < AbstractCommand - @file_system_manager : FileSystemManager - def initialize(@config : GX::Config) # FIXME + def initialize(@config : GX::Config) @config.load_from_env @config.load_from_file @file_system_manager = FileSystemManager.new(@config) end def execute - filesystem = @file_system_manager.choose_filesystem - raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil? - # @file_system_manager.mount_or_umount(filesystem) - filesystem.mount - @file_system_manager.auto_open(filesystem) if filesystem.mounted? && @config.auto_open + # get filesystem from config options + # filesystem = @config.mapping_mount_options.filesystem + + # raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil? + # filesystem.mount + # @file_system_manager.auto_open(filesystem) if filesystem.mounted? && @config.auto_open? + end def self.handles_mode GX::Types::Mode::MappingMount end + + private def _mount_filesystem(filesystem : Models::AbstractFilesystemConfig) + raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil? + if filesystem.mounted? + Log.info { "Filesystem already mounted." } + return + end + filesystem.mount + end end end diff --git a/src/commands/mapping_umount.cr b/src/commands/mapping_umount.cr index dc61f1d..137e25a 100644 --- a/src/commands/mapping_umount.cr +++ b/src/commands/mapping_umount.cr @@ -1,24 +1,40 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./abstract_command" require "../file_system_manager" module GX::Commands class MappingUmount < AbstractCommand - @file_system_manager : FileSystemManager - - def initialize(@config : GX::Config) # FIXME + def initialize(@config : GX::Config) @config.load_from_env @config.load_from_file - @file_system_manager = FileSystemManager.new(@config) end def execute - filesystem = @file_system_manager.choose_filesystem - raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil? - filesystem.umount + # root = @config.root + # raise "Missing root config" if root.nil? + + # filesystem = root.file_system_manager.choose_filesystem + # raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil? + + # filesystem.umount end def self.handles_mode GX::Types::Mode::MappingUmount end + + # OBSOLETE: + private def umount_filesystem(filesystem : Models::AbstractFilesystemConfig) + raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil? + unless filesystem.mounted? + Log.info { "Filesystem is not mounted." } + return + end + filesystem.umount + end end end diff --git a/src/config.cr b/src/config.cr index d375cce..182c186 100644 --- a/src/config.cr +++ b/src/config.cr @@ -10,6 +10,10 @@ require "./types/modes" require "./parsers/options/help_options" require "./parsers/options/config_options" require "./parsers/options/config_init_options" +require "./parsers/options/mapping_create_options" +require "./parsers/options/mapping_delete_options" +require "./parsers/options/mapping_mount_options" +require "./parsers/options/mapping_umount_options" require "./commands/abstract_command" module GX @@ -23,20 +27,21 @@ module GX record AddArgs, name : String, path : String record DelArgs, name : String - # getter filesystems : Array(Models::AbstractFilesystemConfig) getter home_dir : String getter root : Models::RootConfig? - property verbose : Bool + property? verbose : Bool property mode : Types::Mode property path : String? property args : AddArgs.class | DelArgs.class | NoArgs.class - property auto_open : Bool + property? auto_open : Bool - # FIXME: refactor and remove these parts from here - property help_options : Parsers::Options::HelpOptions? + # TODO: refactor and remove these parts from here property config_init_options : Parsers::Options::ConfigInitOptions? property config_options : Parsers::Options::ConfigOptions? + property help_options : Parsers::Options::HelpOptions? + property mapping_create_options : Parsers::Options::MappingCreateOptions? + property mapping_create_options : Parsers::Options::MappingCreateOptions? def initialize raise Models::InvalidEnvironmentError.new("Home directory not found") if !ENV["HOME"]? @@ -97,7 +102,12 @@ module GX file_data = File.read(config_path) file_patched = Crinja.render(file_data, {"env" => ENV.to_h}) - root = Models::RootConfig.from_yaml(file_patched) + begin + root = Models::RootConfig.from_yaml(file_patched) + rescue ex : YAML::ParseException + STDERR.puts "Error parsing configuration file: #{ex.message}".colorize(:red) + exit(1) + end mount_point_base_safe = root.global.mount_point_base raise Models::InvalidMountpointError.new("Invalid global mount point") if mount_point_base_safe.nil? @@ -110,5 +120,15 @@ module GX end @root = root end + + def save_to_file + return if @path.nil? + if @path + File.write(@path.to_s, @root.to_yaml) + else + Log.error { "Configuration path is nil, cannot save configuration." } + end + Log.info { "Configuration saved to #{@path}" } + end end end diff --git a/src/file_storage.cr b/src/file_storage.cr new file mode 100644 index 0000000..3726461 --- /dev/null +++ b/src/file_storage.cr @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + +require "baked_file_system" + +class FileStorage + extend BakedFileSystem + + bake_folder "../static" +end diff --git a/src/file_system_manager.cr b/src/file_system_manager.cr index 91dc1a8..a0d8093 100644 --- a/src/file_system_manager.cr +++ b/src/file_system_manager.cr @@ -1,4 +1,9 @@ -# require "./models/abstract_filesystem_config" +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + +require "./models/abstract_filesystem_config" require "./utils/fzf" module GX @@ -8,25 +13,6 @@ module GX def initialize(@config : Config) end - # OBSOLETE: - # def mount_filesystem(filesystem : Models::AbstractFilesystemConfig) - # raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil? - # if filesystem.mounted? - # Log.info { "Filesystem already mounted." } - # return - # end - # filesystem.mount - # end - - # OBSOLETE: - # def umount_filesystem(filesystem : Models::AbstractFilesystemConfig) - # raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil? - # unless filesystem.mounted? - # Log.info { "Filesystem is not mounted." } - # return - # end - # filesystem.umount - # end def mount_or_umount(selected_filesystem) if !selected_filesystem.mounted? @@ -37,15 +23,15 @@ module GX end def auto_open(filesystem) - # FIXME: detect xdg-open and use it if possible - # FIXME: detect mailcap and use it if no xdg-open found - # FIXME: support user-defined command in configuration - # FIXME: detect graphical environment + # TODO: detect xdg-open presence and use it if possible + # TODO: detect mailcap and use it if no xdg-open found + # TODO: support user-defined command in configuration + # TODO: detect graphical environment mount_point_safe = filesystem.mount_point raise Models::InvalidMountpointError.new("Invalid filesystem") if mount_point_safe.nil? - if graphical_environment? + if _graphical_environment? process = Process.new( "xdg-open", # # FIXME: make configurable [mount_point_safe], @@ -88,24 +74,68 @@ module GX config_root.filesystems end - def choose_filesystem - names_display = {} of String => NamedTuple(filesystem: Models::AbstractFilesystemConfig, ansi_name: String) + # Get filesystem by name + def detect_filesystem(filesystem_name : String) : GX::Models::AbstractFilesystemConfig? + end + + # Choose filesystem with fzf + def choose_filesystem : GX::Models::AbstractFilesystemConfig? + names_display = _filesystem_table + + # FIXME: feat: allow to sort by name or by filesystem + sorted_values = names_display.values.sort_by!(&.[:filesystem].name) + result_filesystem_name = Utils::Fzf.run(sorted_values.map(&.[:ansi_name])).strip + selected_filesystem = names_display[result_filesystem_name][:filesystem] + puts ">> #{selected_filesystem.name}".colorize(:yellow) + + if !selected_filesystem + STDERR.puts "Mapping not found: #{selected_filesystem}.".colorize(:red) + return + end + selected_filesystem + end + + private def _fzf_plain_name(filesystem : Models::AbstractFilesystemConfig) : String + fs_str = filesystem.type.ljust(12, ' ') + suffix = filesystem.mounted? ? "[open]" : "" + "#{fs_str} #{filesystem.name} #{suffix}".strip + end + + private def _fzf_ansi_name(filesystem : Models::AbstractFilesystemConfig) : String + fs_str = filesystem.type.ljust(12, ' ').colorize(:dark_gray) + suffix = filesystem.mounted? ? "[#{"open".colorize(:green)}]" : "" + "#{fs_str} #{filesystem.name} #{suffix}".strip + end + + private def _graphical_environment? + if ENV["DISPLAY"]? || ENV["WAYLAND_DISPLAY"]? + return true + end + false + end + + + alias FilesystemTableItem = + NamedTuple( + filesystem: Models::AbstractFilesystemConfig, + ansi_name: String + ) + + alias FilesystemTable = + Hash( + String, + FilesystemTableItem + ) + + private def _filesystem_table : FilesystemTable + names_display = {} of String => FilesystemTableItem config_root = @config.root - return if config_root.nil? + return {} of String => FilesystemTableItem if config_root.nil? config_root.filesystems.each do |filesystem| - fs_str = filesystem.type.ljust(12, ' ') - - suffix = "" - suffix_ansi = "" - if filesystem.mounted? - suffix = "[open]" - suffix_ansi = "[#{"open".colorize(:green)}]" - end - - result_name = "#{fs_str} #{filesystem.name} #{suffix}".strip - ansi_name = "#{fs_str.colorize(:dark_gray)} #{filesystem.name} #{suffix_ansi}".strip + result_name = _fzf_plain_name(filesystem) + ansi_name = _fzf_ansi_name(filesystem) names_display[result_name] = { filesystem: filesystem, @@ -113,30 +143,7 @@ module GX } end - # # FIXME: feat: allow to sort by name or by filesystem - sorted_values = names_display.values.sort_by { |item| item[:filesystem].name } - result_filesystem_name = Utils::Fzf.run(sorted_values.map(&.[:ansi_name])).strip - selected_filesystem = names_display[result_filesystem_name][:filesystem] - puts ">> #{selected_filesystem.name}".colorize(:yellow) - - if !selected_filesystem - STDERR.puts "Vault not found: #{selected_filesystem}.".colorize(:red) - return - end - return selected_filesystem - end - - private def generate_display_name(filesystem : Models::AbstractFilesystemConfig) : String - fs_str = filesystem.type.ljust(12, ' ') - suffix = filesystem.mounted? ? "[open]" : "" - "#{fs_str} #{filesystem.name} #{suffix}".strip - end - - private def graphical_environment? - if ENV["DISPLAY"]? || ENV["WAYLAND_DISPLAY"]? - return true - end - return false + names_display end end end diff --git a/src/models/abstract_filesystem_config.cr b/src/models/abstract_filesystem_config.cr index 928a0d7..66081ee 100644 --- a/src/models/abstract_filesystem_config.cr +++ b/src/models/abstract_filesystem_config.cr @@ -15,6 +15,15 @@ module GX::Models abstract class AbstractFilesystemConfig include YAML::Serializable # include YAML::Serializable::Strict + @@subs = [] of AbstractFilesystemConfig.class + + macro inherited + @@subs << {{@type.name.id}} + end + + def self.subs + @@subs + end use_yaml_discriminator "type", { gocryptfs: GoCryptFSConfig, diff --git a/src/models/concerns/base.cr b/src/models/concerns/base.cr index 90254de..99be687 100644 --- a/src/models/concerns/base.cr +++ b/src/models/concerns/base.cr @@ -1,3 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + module GX::Models::Concerns module Base def mounted? : Bool @@ -31,7 +36,7 @@ module GX::Models::Concerns end end - def _mount_wrapper(&block) : Nil + def _mount_wrapper(&) : Nil mount_point_safe = mount_point return if mount_point_safe.nil? @@ -46,7 +51,7 @@ module GX::Models::Concerns if result_status.success? puts "Models #{name} is now available on #{mount_point_safe}".colorize(:green) else - puts "Error mounting the vault".colorize(:red) + puts "Error mounting the mapping".colorize(:red) return end end diff --git a/src/models/filesystem_factory.cr b/src/models/filesystem_factory.cr new file mode 100644 index 0000000..9d43bf5 --- /dev/null +++ b/src/models/filesystem_factory.cr @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + +module GX::Models + class FilesystemFactory + def self.build(create_options) + case create_options.type + when "gocryptfs" + GoCryptFSConfig.new(create_options) + when "sshfs" + SshFSConfig.new(create_options) + when "httpdirfs" + HttpDirFSConfig.new(create_options) + else + raise ArgumentError.new("Unsupported mapping type: #{create_options.type}") + end + end + end +end diff --git a/src/models/gocryptfs_config.cr b/src/models/gocryptfs_config.cr index 9013bdf..6e3b497 100644 --- a/src/models/gocryptfs_config.cr +++ b/src/models/gocryptfs_config.cr @@ -13,6 +13,11 @@ module GX::Models include Concerns::Base + def initialize(create_options) + @name = create_options.name.as(String) + @encrypted_path = create_options.encrypted_path.as(String) + end + def _mounted_prefix "#{encrypted_path}" end @@ -32,7 +37,8 @@ module GX::Models output: STDOUT, error: STDERR ) - return process.wait + process.wait end + def self.name ; "gocryptfs" ; end end end diff --git a/src/models/httpdirfs_config.cr b/src/models/httpdirfs_config.cr index 30db9eb..9b3f603 100644 --- a/src/models/httpdirfs_config.cr +++ b/src/models/httpdirfs_config.cr @@ -13,6 +13,11 @@ module GX::Models include Concerns::Base + def initialize(create_options) + @name = create_options.name.as(String) + @url = create_options.url.as(String) + end + def _mounted_prefix "httpdirfs" end @@ -32,7 +37,8 @@ module GX::Models output: STDOUT, error: STDERR ) - return process.wait + process.wait end + def self.name ; "httpdirfs" ; end end end diff --git a/src/models/root_config.cr b/src/models/root_config.cr index d88dc61..2227ad6 100644 --- a/src/models/root_config.cr +++ b/src/models/root_config.cr @@ -36,5 +36,12 @@ module GX::Models @[YAML::Field(key: "filesystems")] getter filesystems : Array(AbstractFilesystemConfig) + setter filesystems + + def initialize(version = "1.0.0", global = GlobalConfig.new, filesystems = [] of AbstractFilesystemConfig) + @version = version + @global = global + @filesystems = filesystems + end end end diff --git a/src/models/sshfs_config.cr b/src/models/sshfs_config.cr index 99da441..08cf2b0 100644 --- a/src/models/sshfs_config.cr +++ b/src/models/sshfs_config.cr @@ -16,6 +16,14 @@ module GX::Models include Concerns::Base + def initialize(create_options) + @name = create_options.name.as(String) + @remote_user = create_options.remote_user.as(String) + @remote_host = create_options.remote_host.as(String) + @remote_path = create_options.remote_path.as(String) + @remote_port = create_options.remote_port.as(String) + end + def _mounted_prefix "#{@remote_user}@#{@remote_host}:#{@remote_path}" end @@ -39,7 +47,8 @@ module GX::Models output: STDOUT, error: STDERR ) - return process.wait + process.wait end + def self.name ; "sshfs" ; end end end diff --git a/src/parsers/base.cr b/src/parsers/base.cr index 8f3573b..e934b7c 100644 --- a/src/parsers/base.cr +++ b/src/parsers/base.cr @@ -1,3 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + module GX::Parsers abstract class AbstractParser abstract def build(parser : OptionParser, ancestors : BreadCrumbs, config : Config) diff --git a/src/parsers/completion_parser.cr b/src/parsers/completion_parser.cr index 1cdc4cc..4a29920 100644 --- a/src/parsers/completion_parser.cr +++ b/src/parsers/completion_parser.cr @@ -1,3 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./base.cr" module GX::Parsers @@ -12,11 +17,11 @@ module GX::Parsers ) parser.separator("\nCompletion commands:") - parser.on("--bash", "Generate bash completion") do |flag| + parser.on("--bash", "Generate bash completion") do |_| Log.info { "Set bash completion" } end - parser.on("--zsh", "Generate zsh completion") do |flag| + parser.on("--zsh", "Generate zsh completion") do |_| Log.info { "Set zsh completion" } end diff --git a/src/parsers/config_parser.cr b/src/parsers/config_parser.cr index b902cd5..1999e8b 100644 --- a/src/parsers/config_parser.cr +++ b/src/parsers/config_parser.cr @@ -1,3 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./options/config_options" require "./options/config_init_options" require "./base" @@ -23,7 +28,7 @@ module GX::Parsers parser.banner = Utils.usage_line(breadcrumbs + "init", "Create initial mfm configuration") parser.separator("\nInit options") - parser.on("-p", "--path", "Set vault encrypted path") do |path| + parser.on("-p", "--path PATH", "Set mapping encrypted path") do |path| config.config_init_options.try do |opts| opts.path = path end diff --git a/src/parsers/mapping_parser.cr b/src/parsers/mapping_parser.cr index fc972a9..8c4a1d1 100644 --- a/src/parsers/mapping_parser.cr +++ b/src/parsers/mapping_parser.cr @@ -1,3 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./base.cr" require "../utils/parser_lines" @@ -5,8 +10,9 @@ module GX::Parsers class MappingParser < AbstractParser def build(parser, ancestors, config) breadcrumbs = ancestors + "mapping" - add_args = {name: "", path: ""} delete_args = {name: ""} + mount_args = {name: ""} + umount_args = {name: ""} parser.banner = Utils.usage_line( breadcrumbs, @@ -18,50 +24,118 @@ module GX::Parsers parser.on("list", "List mappings") do config.mode = Types::Mode::MappingList parser.separator(Utils.help_line(breadcrumbs + "list")) - # abort("FIXME: Not implemented") end parser.on("create", "Create mapping") do config.mode = Types::Mode::MappingCreate + config.mode = Types::Mode::MappingCreate + config.mapping_create_options = Parsers::Options::MappingCreateOptions.new - pp parser parser.banner = Utils.usage_line(breadcrumbs + "create", "Create mapping", true) parser.separator("\nCreate options") - parser.on("-n", "--name", "Set vault name") do |name| - add_args = add_args.merge({name: name}) + parser.on("-t", "--type TYPE", "Set filesystem type") do |type| + config.mapping_create_options.try do |opts| + opts.type = type + end end - parser.on("-p", "--path", "Set vault encrypted path") do |path| - add_args = add_args.merge({path: path}) + parser.on("-n", "--name NAME", "Set mapping name") do |name| + config.mapping_create_options.try do |opts| + opts.name = name + end end + + # Filesystem specific + parser.on("--encrypted-path PATH", "Set encrypted path (for gocryptfs)") do |path| + config.mapping_create_options.try do |opts| + opts.encrypted_path = path + end + end + parser.on("--remote-user USER", "Set SSH user (for sshfs)") do |user| + config.mapping_create_options.try do |opts| + opts.remote_user = user + end + end + parser.on("--remote-host HOST", "Set SSH host (for sshfs)") do |host| + config.mapping_create_options.try do |opts| + opts.remote_host = host + end + end + parser.on("--source-path PATH", "Set remote path (for sshfs)") do |path| + config.mapping_create_options.try do |opts| + opts.remote_path = path + end + end + parser.on("--remote-port PORT", "Set SSH port (for sshfs)") do |port| + config.mapping_create_options.try do |opts| + opts.remote_port = port + end + end + parser.on("--url URL", "Set URL (for httpdirfs)") do |url| + config.mapping_create_options.try do |opts| + opts.url = url + end + end + parser.separator(Utils.help_line(breadcrumbs + "create")) end - parser.on("edit", "Edit configuration") do |flag| + parser.on("edit", "Edit configuration") do |_| config.mode = Types::Mode::MappingEdit + + parser.on("--remote-user USER", "Set SSH user") do |user| + config.mapping_create_options.try do |opts| + opts.remote_user = user + end + end + parser.on("--remote-host HOST", "Set SSH host") do |host| + config.mapping_create_options.try do |opts| + opts.remote_host = host + end + end + parser.on("--source-path PATH", "Set remote path") do |path| + config.mapping_create_options.try do |opts| + opts.remote_path = path + end + end + parser.separator(Utils.help_line(breadcrumbs + "edit")) # abort("FIXME: Not implemented") end - parser.on("mount", "Mount mapping") do |flag| + parser.on("mount", "Mount mapping") do |_| config.mode = Types::Mode::MappingMount + + parser.banner = Utils.usage_line(breadcrumbs + "mount", "mount mapping", true) + parser.separator("\nMount options") + + parser.on("-n", "--name", "Set mapping name") do |name| + mount_args = mount_args.merge({name: name}) + end + parser.separator(Utils.help_line(breadcrumbs + "mount")) - # abort("FIXME: Not implemented") end - parser.on("umount", "Umount mapping") do |flag| + parser.on("umount", "Umount mapping") do |_| config.mode = Types::Mode::MappingUmount + + parser.banner = Utils.usage_line(breadcrumbs + "umount", "umount mapping", true) + parser.separator("\nUmount options") + + parser.on("-n", "--name", "Set mapping name") do |name| + umount_args = umount_args.merge({name: name}) + end + parser.separator(Utils.help_line(breadcrumbs + "umount")) - # abort("FIXME: Not implemented") end parser.on("delete", "Delete mapping") do config.mode = Types::Mode::MappingDelete - parser.banner = Utils.usage_line(breadcrumbs + "delete", "Delete mapping", true) - parser.separator("\nDelete options") + parser.banner = Utils.usage_line(breadcrumbs + "delete", "delete mapping", true) + parser.separator("\ndelete options") - parser.on("-n", "--name", "Set vault name") do |name| + parser.on("-n", "--name", "Set mapping name") do |name| delete_args = delete_args.merge({name: name}) end parser.separator(Utils.help_line(breadcrumbs + "delete")) diff --git a/src/parsers/options/config_init_options.cr b/src/parsers/options/config_init_options.cr index 97a31a3..d28065c 100644 --- a/src/parsers/options/config_init_options.cr +++ b/src/parsers/options/config_init_options.cr @@ -1,3 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "option_parser" module GX::Parsers::Options diff --git a/src/parsers/options/config_options.cr b/src/parsers/options/config_options.cr index 6cacac0..37b29b8 100644 --- a/src/parsers/options/config_options.cr +++ b/src/parsers/options/config_options.cr @@ -1,3 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "option_parser" module GX::Parsers::Options diff --git a/src/parsers/options/help_options.cr b/src/parsers/options/help_options.cr index e81fa2e..0a2b865 100644 --- a/src/parsers/options/help_options.cr +++ b/src/parsers/options/help_options.cr @@ -1,3 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "option_parser" module GX::Parsers::Options diff --git a/src/parsers/options/mapping_create_options.cr b/src/parsers/options/mapping_create_options.cr new file mode 100644 index 0000000..1137777 --- /dev/null +++ b/src/parsers/options/mapping_create_options.cr @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + +require "option_parser" + +module GX::Parsers::Options + class MappingCreateOptions + property type : String? + property name : String? + property encrypted_path : String? + property remote_user : String? + property remote_host : String? + property remote_path : String? + property remote_port : String? + property url : String? + end +end diff --git a/src/parsers/options/mapping_delete_options.cr b/src/parsers/options/mapping_delete_options.cr new file mode 100644 index 0000000..c881f24 --- /dev/null +++ b/src/parsers/options/mapping_delete_options.cr @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + +require "option_parser" + +module GX::Parsers::Options + class MappingDeleteOptions + # Add your options here + end +end diff --git a/src/parsers/options/mapping_mount_options.cr b/src/parsers/options/mapping_mount_options.cr new file mode 100644 index 0000000..c5cf059 --- /dev/null +++ b/src/parsers/options/mapping_mount_options.cr @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + +require "option_parser" + +module GX::Parsers::Options + class MappingMountOptions + # Add your options here + end +end diff --git a/src/parsers/options/mapping_umount_options.cr b/src/parsers/options/mapping_umount_options.cr new file mode 100644 index 0000000..df948e9 --- /dev/null +++ b/src/parsers/options/mapping_umount_options.cr @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + +require "option_parser" + +module GX::Parsers::Options + class MappingUmountOptions + # Add your options here + end +end diff --git a/src/parsers/root_parser.cr b/src/parsers/root_parser.cr index 70ac5a5..7c9c908 100644 --- a/src/parsers/root_parser.cr +++ b/src/parsers/root_parser.cr @@ -1,3 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./base" require "./config_parser" require "./mapping_parser" @@ -21,24 +26,24 @@ module GX::Parsers config.path = path end - parser.on("-v", "--verbose", "Set more verbosity") do |flag| + parser.on("-v", "--verbose", "Set more verbosity") do |_| Log.info { "Verbosity enabled" } config.verbose = true end - parser.on("-o", "--open", "Automatically open directory after mount") do |flag| + parser.on("-o", "--open", "Automatically open directory after mount") do |_| Log.info { "Auto-open enabled" } config.auto_open = true end - parser.on("--version", "Show version") do |flag| + parser.on("--version", "Show version") do |_| config.mode = Types::Mode::GlobalVersion end - parser.on("-h", "--help", "Show this help") do |flag| + parser.on("-h", "--help", "Show this help") do |_| config.mode = Types::Mode::GlobalHelp config.help_options = Parsers::Options::HelpOptions.new - config.help_options.try { |opts| opts.parser_snapshot = parser.dup } + config.help_options.try(&.parser_snapshot=(parser.dup)) end parser.separator("\nGlobal commands:") @@ -46,7 +51,7 @@ module GX::Parsers parser.on("config", "Manage configuration file") do config.mode = Types::Mode::GlobalHelp config.help_options = Parsers::Options::HelpOptions.new - config.help_options.try { |opts| opts.parser_snapshot = parser.dup } + config.help_options.try(&.parser_snapshot=(parser.dup)) # config.command = Commands::Config.new(config) Parsers::ConfigParser.new.build(parser, breadcrumbs, config) @@ -59,15 +64,11 @@ module GX::Parsers parser.on("mapping", "Manage mappings") do config.mode = Types::Mode::GlobalHelp config.help_options = Parsers::Options::HelpOptions.new - config.help_options.try { |opts| opts.parser_snapshot = parser.dup } + config.help_options.try(&.parser_snapshot=(parser.dup)) Parsers::MappingParser.new.build(parser, breadcrumbs, config) end - # parser.on("interactive", "Interactive mapping mount/umount") do - # abort("FIXME: Not implemented") - # end - parser.on("completion", "Manage completion") do config.mode = Types::Mode::GlobalCompletion Parsers::CompletionParser.new.build(parser, breadcrumbs, config) diff --git a/src/types/modes.cr b/src/types/modes.cr index 6498f89..69dbdc8 100644 --- a/src/types/modes.cr +++ b/src/types/modes.cr @@ -1,3 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + module GX::Types enum Mode None @@ -7,7 +12,7 @@ module GX::Types GlobalCompletion GlobalTui GlobalConfig - GlobalMapping + # GlobalMapping ConfigInit diff --git a/src/utils/breadcrumbs.cr b/src/utils/breadcrumbs.cr index 89c54e9..01f5833 100644 --- a/src/utils/breadcrumbs.cr +++ b/src/utils/breadcrumbs.cr @@ -1,15 +1,20 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + module GX::Utils class BreadCrumbs def initialize(base : Array(String)) @ancestors = base end - def +(elem : String) - b = BreadCrumbs.new(@ancestors + [elem]) + def +(other : String) + BreadCrumbs.new(@ancestors + [other]) end - def to_s - @ancestors.join(" ") + def to_s(io : IO) + io << @ancestors.join(" ") end def to_a diff --git a/src/utils/fzf.cr b/src/utils/fzf.cr index ff626f3..d3d8670 100644 --- a/src/utils/fzf.cr +++ b/src/utils/fzf.cr @@ -28,7 +28,8 @@ module GX::Utils exit(1) end - result = output.to_s.strip # .split.first? + # result + output.to_s.strip end end end diff --git a/src/utils/parser_lines.cr b/src/utils/parser_lines.cr index dacd71f..adee77e 100644 --- a/src/utils/parser_lines.cr +++ b/src/utils/parser_lines.cr @@ -1,9 +1,14 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "./breadcrumbs" module GX::Utils def self.usage_line(breadcrumbs : BreadCrumbs, description : String, has_commands : Bool = false) [ - "Usage: #{breadcrumbs.to_s}#{has_commands ? " [commands]" : ""} [options]", + "Usage: #{breadcrumbs}#{has_commands ? " [commands]" : ""} [options]", "", description, "", @@ -12,6 +17,6 @@ module GX::Utils end def self.help_line(breadcrumbs : BreadCrumbs) - "\nRun '#{breadcrumbs.to_s} COMMAND --help' for more information on a command." + "\nRun '#{breadcrumbs} COMMAND --help' for more information on a command." end end diff --git a/src/version.cr b/src/version.cr index 0fd98ff..4adcdb2 100644 --- a/src/version.cr +++ b/src/version.cr @@ -1,3 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + require "version_from_shard" module GX diff --git a/static/completion.bash b/static/completion.bash index d990879..cbede6a 100644 --- a/static/completion.bash +++ b/static/completion.bash @@ -1,4 +1,8 @@ #!/bin/bash +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland # mfm Bash completion script diff --git a/static/completion.zsh b/static/completion.zsh new file mode 100644 index 0000000..f57dc9d --- /dev/null +++ b/static/completion.zsh @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland +# Copyright © 2024 Glenn Y. Rolland + + diff --git a/static/sample.mfm.yaml b/static/sample.mfm.yaml new file mode 100644 index 0000000..8eaa2b5 --- /dev/null +++ b/static/sample.mfm.yaml @@ -0,0 +1,32 @@ +--- +version: 1 + +global: + mount_point_base: "{{env.HOME}}/mnt" + +filesystems: + ## + ## Sample configuration for encrypted vault (gocryptfs) + ## + # - type: gocryptfs + # name: "Credential Vault" + # encrypted_path: "{{env.HOME}}/Documents/Credential.Vault" + # + ## + ## Sample configuration remote SSH directory (sshfs) + ## + # - type: sshfs + # name: "Remote SSH server" + # remote_host: ssh.example.com + # remote_user: "{{env.USER}}" + # remote_path: "/home/{{env.USER}}" + # remote_port: 443 + # + ## + ## Sample configuration for remote HTTP directory (httpdirfs) + ## + - type: httpdirfs + name: "Debian Repository" + url: "http://ftp.debian.org/debian/" + # mount_point: "{{env.HOME}}/another.dir" +#