WIP: feature/1-add-support-for-fs-crud #47

Draft
glenux wants to merge 53 commits from feature/1-add-support-for-fs-crud into develop
62 changed files with 888 additions and 172 deletions

View file

@ -7,16 +7,21 @@
# - "path/to/repo2" # - "path/to/repo2"
# List of patterns to ignore during preloading # List of patterns to ignore during preloading
ignore_list: exclude_list:
- ^\.git/
- ^lib.*
- ^doc/
- ^bin/ - ^bin/
- ^\.code_preloader.yml
- ^doc/
- ^\.drone.yml
- ^\.git/
- ^\.gitattributes
- ^\.gitignore
- ^lib.*
- ^LICENSES/
- ^_prompts/ - ^_prompts/
- ^\.reuse/ - ^\.reuse/
- ^LICENSES/
- ^\.vagrant/
- ^scripts/ - ^scripts/
- ^\.tool-versions
- ^\.vagrant/
# Path to the output file (if null, output to STDOUT) # Path to the output file (if null, output to STDOUT)
output_path: null output_path: null

2
.gitignore vendored
View file

@ -7,3 +7,5 @@
.vagrant .vagrant
bin bin
lib lib
.aider*
.env

View file

@ -11,7 +11,7 @@ prepare:
shards install shards install
build: build:
shards build --error-trace -Dpreview_mt shards build --progress --error-trace -Dpreview_mt
@echo SUCCESS @echo SUCCESS
watch: watch:
@ -21,10 +21,13 @@ spec: test
test: test:
crystal spec --error-trace crystal spec --error-trace
format:
crystal tool format
install: install:
install \ install \
-m 755 \ -m 755 \
bin/code-preloader \ bin/mfm \
$(PREFIX)/bin $(PREFIX)/bin
.PHONY: spec test build all prepare install .PHONY: spec test build all prepare install

View file

@ -14,6 +14,8 @@
> version of our project, please visit our primary repository at: > version of our project, please visit our primary repository at:
> <https://code.apps.glenux.net/glenux/mfm>. > <https://code.apps.glenux.net/glenux/mfm>.
<!-- hello -->
# Minimalist Fuse Manager (MFM) # 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. 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: For Debian/Ubuntu you can use the following command:
```shell-session ```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 ## Installation
### 1. From Source ### 1. From Source
1. Clone or download the source code. To get started with MFM, ensure that you have the prerequisites installed on your system (see above).
2. Navigate to the source directory.
3. Run `shards install` to fetch dependencies. Then follow these steps to install:
4. Compile using `shards build`.
5. The compiled binary will be in the `bin` directory. 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 ### 2. Binary Download

5
Vagrantfile vendored
View file

@ -1,3 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
# frozen_string_literal: true # frozen_string_literal: true
# -*- mode: ruby -*- # -*- mode: ruby -*-

View file

@ -4,6 +4,10 @@ shards:
git: https://github.com/crystal-ameba/ameba.git git: https://github.com/crystal-ameba/ameba.git
version: 1.6.1 version: 1.6.1
baked_file_system:
git: https://github.com/schovi/baked_file_system.git
version: 0.10.0
crinja: crinja:
git: https://github.com/straight-shoota/crinja.git git: https://github.com/straight-shoota/crinja.git
version: 0.8.1 version: 0.8.1

View file

@ -26,6 +26,9 @@ dependencies:
github: hugopl/version_from_shard github: hugopl/version_from_shard
tablo: tablo:
github: hutou/tablo github: hutou/tablo
baked_file_system:
github: schovi/baked_file_system
version: 0.10.0
development_dependencies: development_dependencies:
ameba: ameba:

View file

@ -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

View file

@ -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

View file

@ -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

View file

View file

@ -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

View file

View file

View file

@ -37,10 +37,10 @@ describe GX::Utils::BreadCrumbs do
b1.to_s.should eq("") b1.to_s.should eq("")
b2 = b1 + "test1" b2 = b1 + "test1"
b2.to_a.should eq("test1") b2.to_s.should eq("test1")
b3 = b2 + "test2" b3 = b2 + "test2"
b3.to_a.should eq("test1 test2") b3.to_s.should eq("test1 test2")
end end
end end
end end

View file

@ -30,13 +30,22 @@ module GX
Parsers::RootParser.new.build(parser, breadcrumbs, @config) Parsers::RootParser.new.build(parser, breadcrumbs, @config)
end end
pparser.parse(args) pparser.parse(args)
rescue e : OptionParser::MissingOption
STDERR.puts "ERROR: #{e.message}".colorize(:red)
exit(1)
end end
def run def run
command = CommandFactory.create_command(@config, @config.mode) command = CommandFactory.create_command(@config, @config.mode)
abort("ERROR: unknown command for mode #{@config.mode}") if command.nil? 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 end
end end

View file

@ -1,3 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./commands" require "./commands"
module GX module GX

View file

@ -1 +1,6 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./commands/*" require "./commands/*"

View file

@ -1,3 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "../config" require "../config"
module GX::Commands module GX::Commands

View file

@ -1,11 +1,51 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./abstract_command" require "./abstract_command"
require "../file_storage"
module GX::Commands module GX::Commands
class ConfigInit < AbstractCommand class ConfigInit < AbstractCommand
def initialize(config : GX::Config) # FIXME def initialize(@config : GX::Config)
end end
def execute 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 end
def self.handles_mode def self.handles_mode

View file

@ -1,3 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./abstract_command" require "./abstract_command"
module GX::Commands module GX::Commands
@ -6,10 +11,12 @@ module GX::Commands
end end
def execute def execute
puts "FIXME: detect option (either zsh or bash)"
puts "FIXME: output the right file from embedded data"
end end
def self.handles_mode def self.handles_mode
GX::Types::Mode::GlobalConfig GX::Types::Mode::GlobalCompletion
end end
end end
end end

View file

@ -1,8 +1,13 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./abstract_command" require "./abstract_command"
module GX::Commands module GX::Commands
class GlobalConfig < AbstractCommand class GlobalConfig < AbstractCommand
def initialize(config : GX::Config) # FIXME def initialize(config : GX::Config)
end end
def execute def execute

View file

@ -1,8 +1,13 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./abstract_command" require "./abstract_command"
module GX::Commands module GX::Commands
class GlobalHelp < AbstractCommand class GlobalHelp < AbstractCommand
def initialize(@config : GX::Config) # FIXME def initialize(@config : GX::Config)
end end
def execute def execute

View file

@ -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

View file

@ -1,9 +1,14 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./abstract_command" require "./abstract_command"
require "../file_system_manager" require "../file_system_manager"
module GX::Commands module GX::Commands
class GlobalTui < AbstractCommand class GlobalTui < AbstractCommand
@file_system_manager : FileSystemManager # @file_system_manager : FileSystemManager
def initialize(@config : GX::Config) def initialize(@config : GX::Config)
@config.load_from_env @config.load_from_env
@ -15,7 +20,7 @@ module GX::Commands
filesystem = @file_system_manager.choose_filesystem filesystem = @file_system_manager.choose_filesystem
raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil? raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil?
@file_system_manager.mount_or_umount(filesystem) @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 end
def self.handles_mode def self.handles_mode

View file

@ -1,9 +1,14 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./abstract_command" require "./abstract_command"
require "../config" require "../config"
module GX::Commands module GX::Commands
class GlobalVersion < AbstractCommand class GlobalVersion < AbstractCommand
def initialize(config : GX::Config) # FIXME def initialize(config : GX::Config)
end end
def execute def execute

View file

@ -1,16 +1,57 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./abstract_command" require "./abstract_command"
require "../models/filesystem_factory"
module GX::Commands module GX::Commands
class MappingCreate < AbstractCommand 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 end
def execute 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 end
def self.handles_mode def self.handles_mode
GX::Types::Mode::MappingCreate GX::Types::Mode::MappingCreate
end 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
end end

View file

@ -1,12 +1,17 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./abstract_command" require "./abstract_command"
module GX::Commands module GX::Commands
class MappingDelete < AbstractCommand class MappingDelete < AbstractCommand
def initialize(config : GX::Config) # FIXME def initialize(config : GX::Config)
end end
def execute def execute
# FIXME: implement # TODO: implement
end end
def self.handles_mode def self.handles_mode

View file

@ -1,12 +1,17 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./abstract_command" require "./abstract_command"
module GX::Commands module GX::Commands
class MappingEdit < AbstractCommand class MappingEdit < AbstractCommand
def initialize(config : GX::Config) # FIXME def initialize(config : GX::Config)
end end
def execute def execute
# FIXME: implement # TODO: implement
end end
def self.handles_mode def self.handles_mode

View file

@ -1,3 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./abstract_command" require "./abstract_command"
require "../file_system_manager" require "../file_system_manager"
require "tablo" require "tablo"
@ -7,7 +12,6 @@ module GX::Commands
def initialize(@config : GX::Config) def initialize(@config : GX::Config)
@config.load_from_env @config.load_from_env
@config.load_from_file @config.load_from_file
@file_system_manager = FileSystemManager.new(@config)
end end
def execute def execute

View file

@ -1,26 +1,41 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./abstract_command" require "./abstract_command"
require "../file_system_manager" require "../file_system_manager"
module GX::Commands module GX::Commands
class MappingMount < AbstractCommand 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_env
@config.load_from_file @config.load_from_file
@file_system_manager = FileSystemManager.new(@config) @file_system_manager = FileSystemManager.new(@config)
end end
def execute def execute
filesystem = @file_system_manager.choose_filesystem # get filesystem from config options
raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil? # filesystem = @config.mapping_mount_options.filesystem
# @file_system_manager.mount_or_umount(filesystem)
filesystem.mount # raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil?
@file_system_manager.auto_open(filesystem) if filesystem.mounted? && @config.auto_open # filesystem.mount
# @file_system_manager.auto_open(filesystem) if filesystem.mounted? && @config.auto_open?
end end
def self.handles_mode def self.handles_mode
GX::Types::Mode::MappingMount GX::Types::Mode::MappingMount
end 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
end end

View file

@ -1,24 +1,40 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./abstract_command" require "./abstract_command"
require "../file_system_manager" require "../file_system_manager"
module GX::Commands module GX::Commands
class MappingUmount < AbstractCommand class MappingUmount < AbstractCommand
@file_system_manager : FileSystemManager def initialize(@config : GX::Config)
def initialize(@config : GX::Config) # FIXME
@config.load_from_env @config.load_from_env
@config.load_from_file @config.load_from_file
@file_system_manager = FileSystemManager.new(@config)
end end
def execute def execute
filesystem = @file_system_manager.choose_filesystem # root = @config.root
raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil? # raise "Missing root config" if root.nil?
filesystem.umount
# filesystem = root.file_system_manager.choose_filesystem
# raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil?
# filesystem.umount
end end
def self.handles_mode def self.handles_mode
GX::Types::Mode::MappingUmount GX::Types::Mode::MappingUmount
end 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
end end

View file

@ -10,6 +10,10 @@ require "./types/modes"
require "./parsers/options/help_options" require "./parsers/options/help_options"
require "./parsers/options/config_options" require "./parsers/options/config_options"
require "./parsers/options/config_init_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" require "./commands/abstract_command"
module GX module GX
@ -23,20 +27,21 @@ module GX
record AddArgs, name : String, path : String record AddArgs, name : String, path : String
record DelArgs, name : String record DelArgs, name : String
# getter filesystems : Array(Models::AbstractFilesystemConfig)
getter home_dir : String getter home_dir : String
getter root : Models::RootConfig? getter root : Models::RootConfig?
property verbose : Bool property? verbose : Bool
property mode : Types::Mode property mode : Types::Mode
property path : String? property path : String?
property args : AddArgs.class | DelArgs.class | NoArgs.class property args : AddArgs.class | DelArgs.class | NoArgs.class
property auto_open : Bool property? auto_open : Bool
# FIXME: refactor and remove these parts from here # TODO: refactor and remove these parts from here
property help_options : Parsers::Options::HelpOptions?
property config_init_options : Parsers::Options::ConfigInitOptions? property config_init_options : Parsers::Options::ConfigInitOptions?
property config_options : Parsers::Options::ConfigOptions? 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 def initialize
raise Models::InvalidEnvironmentError.new("Home directory not found") if !ENV["HOME"]? raise Models::InvalidEnvironmentError.new("Home directory not found") if !ENV["HOME"]?
@ -97,7 +102,12 @@ module GX
file_data = File.read(config_path) file_data = File.read(config_path)
file_patched = Crinja.render(file_data, {"env" => ENV.to_h}) 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 mount_point_base_safe = root.global.mount_point_base
raise Models::InvalidMountpointError.new("Invalid global mount point") if mount_point_base_safe.nil? raise Models::InvalidMountpointError.new("Invalid global mount point") if mount_point_base_safe.nil?
@ -110,5 +120,15 @@ module GX
end end
@root = root @root = root
end 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
end end

12
src/file_storage.cr Normal file
View file

@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "baked_file_system"
class FileStorage
extend BakedFileSystem
bake_folder "../static"
end

View file

@ -1,4 +1,9 @@
# require "./models/abstract_filesystem_config" # SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./models/abstract_filesystem_config"
require "./utils/fzf" require "./utils/fzf"
module GX module GX
@ -8,25 +13,6 @@ module GX
def initialize(@config : Config) def initialize(@config : Config)
end 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) def mount_or_umount(selected_filesystem)
if !selected_filesystem.mounted? if !selected_filesystem.mounted?
@ -37,15 +23,15 @@ module GX
end end
def auto_open(filesystem) def auto_open(filesystem)
# FIXME: detect xdg-open and use it if possible # TODO: detect xdg-open presence and use it if possible
# FIXME: detect mailcap and use it if no xdg-open found # TODO: detect mailcap and use it if no xdg-open found
# FIXME: support user-defined command in configuration # TODO: support user-defined command in configuration
# FIXME: detect graphical environment # TODO: detect graphical environment
mount_point_safe = filesystem.mount_point mount_point_safe = filesystem.mount_point
raise Models::InvalidMountpointError.new("Invalid filesystem") if mount_point_safe.nil? raise Models::InvalidMountpointError.new("Invalid filesystem") if mount_point_safe.nil?
if graphical_environment? if _graphical_environment?
process = Process.new( process = Process.new(
"xdg-open", # # FIXME: make configurable "xdg-open", # # FIXME: make configurable
[mount_point_safe], [mount_point_safe],
@ -88,24 +74,68 @@ module GX
config_root.filesystems config_root.filesystems
end end
def choose_filesystem # Get filesystem by name
names_display = {} of String => NamedTuple(filesystem: Models::AbstractFilesystemConfig, ansi_name: String) 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 config_root = @config.root
return if config_root.nil? return {} of String => FilesystemTableItem if config_root.nil?
config_root.filesystems.each do |filesystem| config_root.filesystems.each do |filesystem|
fs_str = filesystem.type.ljust(12, ' ') result_name = _fzf_plain_name(filesystem)
ansi_name = _fzf_ansi_name(filesystem)
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
names_display[result_name] = { names_display[result_name] = {
filesystem: filesystem, filesystem: filesystem,
@ -113,30 +143,7 @@ module GX
} }
end end
# # FIXME: feat: allow to sort by name or by filesystem names_display
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
end end
end end
end end

View file

@ -15,6 +15,15 @@ module GX::Models
abstract class AbstractFilesystemConfig abstract class AbstractFilesystemConfig
include YAML::Serializable include YAML::Serializable
# include YAML::Serializable::Strict # include YAML::Serializable::Strict
@@subs = [] of AbstractFilesystemConfig.class
macro inherited
@@subs << {{@type.name.id}}
end
def self.subs
@@subs
end
use_yaml_discriminator "type", { use_yaml_discriminator "type", {
gocryptfs: GoCryptFSConfig, gocryptfs: GoCryptFSConfig,

View file

@ -1,3 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
module GX::Models::Concerns module GX::Models::Concerns
module Base module Base
def mounted? : Bool def mounted? : Bool
@ -31,7 +36,7 @@ module GX::Models::Concerns
end end
end end
def _mount_wrapper(&block) : Nil def _mount_wrapper(&) : Nil
mount_point_safe = mount_point mount_point_safe = mount_point
return if mount_point_safe.nil? return if mount_point_safe.nil?
@ -46,7 +51,7 @@ module GX::Models::Concerns
if result_status.success? if result_status.success?
puts "Models #{name} is now available on #{mount_point_safe}".colorize(:green) puts "Models #{name} is now available on #{mount_point_safe}".colorize(:green)
else else
puts "Error mounting the vault".colorize(:red) puts "Error mounting the mapping".colorize(:red)
return return
end end
end end

View file

@ -0,0 +1,21 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
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

View file

@ -13,6 +13,11 @@ module GX::Models
include Concerns::Base 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 def _mounted_prefix
"#{encrypted_path}" "#{encrypted_path}"
end end
@ -32,7 +37,8 @@ module GX::Models
output: STDOUT, output: STDOUT,
error: STDERR error: STDERR
) )
return process.wait process.wait
end end
def self.name ; "gocryptfs" ; end
end end
end end

View file

@ -13,6 +13,11 @@ module GX::Models
include Concerns::Base include Concerns::Base
def initialize(create_options)
@name = create_options.name.as(String)
@url = create_options.url.as(String)
end
def _mounted_prefix def _mounted_prefix
"httpdirfs" "httpdirfs"
end end
@ -32,7 +37,8 @@ module GX::Models
output: STDOUT, output: STDOUT,
error: STDERR error: STDERR
) )
return process.wait process.wait
end end
def self.name ; "httpdirfs" ; end
end end
end end

View file

@ -36,5 +36,12 @@ module GX::Models
@[YAML::Field(key: "filesystems")] @[YAML::Field(key: "filesystems")]
getter filesystems : Array(AbstractFilesystemConfig) 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
end end

View file

@ -16,6 +16,14 @@ module GX::Models
include Concerns::Base 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 def _mounted_prefix
"#{@remote_user}@#{@remote_host}:#{@remote_path}" "#{@remote_user}@#{@remote_host}:#{@remote_path}"
end end
@ -39,7 +47,8 @@ module GX::Models
output: STDOUT, output: STDOUT,
error: STDERR error: STDERR
) )
return process.wait process.wait
end end
def self.name ; "sshfs" ; end
end end
end end

View file

@ -1,3 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
module GX::Parsers module GX::Parsers
abstract class AbstractParser abstract class AbstractParser
abstract def build(parser : OptionParser, ancestors : BreadCrumbs, config : Config) abstract def build(parser : OptionParser, ancestors : BreadCrumbs, config : Config)

View file

@ -1,3 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./base.cr" require "./base.cr"
module GX::Parsers module GX::Parsers
@ -12,11 +17,11 @@ module GX::Parsers
) )
parser.separator("\nCompletion commands:") parser.separator("\nCompletion commands:")
parser.on("--bash", "Generate bash completion") do |flag| parser.on("--bash", "Generate bash completion") do |_|
Log.info { "Set bash completion" } Log.info { "Set bash completion" }
end end
parser.on("--zsh", "Generate zsh completion") do |flag| parser.on("--zsh", "Generate zsh completion") do |_|
Log.info { "Set zsh completion" } Log.info { "Set zsh completion" }
end end

View file

@ -1,3 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./options/config_options" require "./options/config_options"
require "./options/config_init_options" require "./options/config_init_options"
require "./base" require "./base"
@ -23,7 +28,7 @@ module GX::Parsers
parser.banner = Utils.usage_line(breadcrumbs + "init", "Create initial mfm configuration") parser.banner = Utils.usage_line(breadcrumbs + "init", "Create initial mfm configuration")
parser.separator("\nInit options") 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| config.config_init_options.try do |opts|
opts.path = path opts.path = path
end end

View file

@ -1,3 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./base.cr" require "./base.cr"
require "../utils/parser_lines" require "../utils/parser_lines"
@ -5,8 +10,9 @@ module GX::Parsers
class MappingParser < AbstractParser class MappingParser < AbstractParser
def build(parser, ancestors, config) def build(parser, ancestors, config)
breadcrumbs = ancestors + "mapping" breadcrumbs = ancestors + "mapping"
add_args = {name: "", path: ""}
delete_args = {name: ""} delete_args = {name: ""}
mount_args = {name: ""}
umount_args = {name: ""}
parser.banner = Utils.usage_line( parser.banner = Utils.usage_line(
breadcrumbs, breadcrumbs,
@ -18,50 +24,118 @@ module GX::Parsers
parser.on("list", "List mappings") do parser.on("list", "List mappings") do
config.mode = Types::Mode::MappingList config.mode = Types::Mode::MappingList
parser.separator(Utils.help_line(breadcrumbs + "list")) parser.separator(Utils.help_line(breadcrumbs + "list"))
# abort("FIXME: Not implemented")
end end
parser.on("create", "Create mapping") do parser.on("create", "Create mapping") do
config.mode = Types::Mode::MappingCreate 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.banner = Utils.usage_line(breadcrumbs + "create", "Create mapping", true)
parser.separator("\nCreate options") parser.separator("\nCreate options")
parser.on("-n", "--name", "Set vault name") do |name| parser.on("-t", "--type TYPE", "Set filesystem type") do |type|
add_args = add_args.merge({name: name}) config.mapping_create_options.try do |opts|
opts.type = type
end
end end
parser.on("-p", "--path", "Set vault encrypted path") do |path| parser.on("-n", "--name NAME", "Set mapping name") do |name|
add_args = add_args.merge({path: path}) config.mapping_create_options.try do |opts|
opts.name = name
end
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")) parser.separator(Utils.help_line(breadcrumbs + "create"))
end end
parser.on("edit", "Edit configuration") do |flag| parser.on("edit", "Edit configuration") do |_|
config.mode = Types::Mode::MappingEdit 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")) parser.separator(Utils.help_line(breadcrumbs + "edit"))
# abort("FIXME: Not implemented") # abort("FIXME: Not implemented")
end end
parser.on("mount", "Mount mapping") do |flag| parser.on("mount", "Mount mapping") do |_|
config.mode = Types::Mode::MappingMount 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")) parser.separator(Utils.help_line(breadcrumbs + "mount"))
# abort("FIXME: Not implemented")
end end
parser.on("umount", "Umount mapping") do |flag| parser.on("umount", "Umount mapping") do |_|
config.mode = Types::Mode::MappingUmount 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")) parser.separator(Utils.help_line(breadcrumbs + "umount"))
# abort("FIXME: Not implemented")
end end
parser.on("delete", "Delete mapping") do parser.on("delete", "Delete mapping") do
config.mode = Types::Mode::MappingDelete config.mode = Types::Mode::MappingDelete
parser.banner = Utils.usage_line(breadcrumbs + "delete", "Delete mapping", true) parser.banner = Utils.usage_line(breadcrumbs + "delete", "delete mapping", true)
parser.separator("\nDelete options") 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}) delete_args = delete_args.merge({name: name})
end end
parser.separator(Utils.help_line(breadcrumbs + "delete")) parser.separator(Utils.help_line(breadcrumbs + "delete"))

View file

@ -1,3 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "option_parser" require "option_parser"
module GX::Parsers::Options module GX::Parsers::Options

View file

@ -1,3 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "option_parser" require "option_parser"
module GX::Parsers::Options module GX::Parsers::Options

View file

@ -1,3 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "option_parser" require "option_parser"
module GX::Parsers::Options module GX::Parsers::Options

View file

@ -0,0 +1,19 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
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

View file

@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "option_parser"
module GX::Parsers::Options
class MappingDeleteOptions
# Add your options here
end
end

View file

@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "option_parser"
module GX::Parsers::Options
class MappingMountOptions
# Add your options here
end
end

View file

@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "option_parser"
module GX::Parsers::Options
class MappingUmountOptions
# Add your options here
end
end

View file

@ -1,3 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./base" require "./base"
require "./config_parser" require "./config_parser"
require "./mapping_parser" require "./mapping_parser"
@ -21,24 +26,24 @@ module GX::Parsers
config.path = path config.path = path
end end
parser.on("-v", "--verbose", "Set more verbosity") do |flag| parser.on("-v", "--verbose", "Set more verbosity") do |_|
Log.info { "Verbosity enabled" } Log.info { "Verbosity enabled" }
config.verbose = true config.verbose = true
end 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" } Log.info { "Auto-open enabled" }
config.auto_open = true config.auto_open = true
end end
parser.on("--version", "Show version") do |flag| parser.on("--version", "Show version") do |_|
config.mode = Types::Mode::GlobalVersion config.mode = Types::Mode::GlobalVersion
end end
parser.on("-h", "--help", "Show this help") do |flag| parser.on("-h", "--help", "Show this help") do |_|
config.mode = Types::Mode::GlobalHelp config.mode = Types::Mode::GlobalHelp
config.help_options = Parsers::Options::HelpOptions.new 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 end
parser.separator("\nGlobal commands:") parser.separator("\nGlobal commands:")
@ -46,7 +51,7 @@ module GX::Parsers
parser.on("config", "Manage configuration file") do parser.on("config", "Manage configuration file") do
config.mode = Types::Mode::GlobalHelp config.mode = Types::Mode::GlobalHelp
config.help_options = Parsers::Options::HelpOptions.new 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) # config.command = Commands::Config.new(config)
Parsers::ConfigParser.new.build(parser, breadcrumbs, config) Parsers::ConfigParser.new.build(parser, breadcrumbs, config)
@ -59,15 +64,11 @@ module GX::Parsers
parser.on("mapping", "Manage mappings") do parser.on("mapping", "Manage mappings") do
config.mode = Types::Mode::GlobalHelp config.mode = Types::Mode::GlobalHelp
config.help_options = Parsers::Options::HelpOptions.new 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) Parsers::MappingParser.new.build(parser, breadcrumbs, config)
end end
# parser.on("interactive", "Interactive mapping mount/umount") do
# abort("FIXME: Not implemented")
# end
parser.on("completion", "Manage completion") do parser.on("completion", "Manage completion") do
config.mode = Types::Mode::GlobalCompletion config.mode = Types::Mode::GlobalCompletion
Parsers::CompletionParser.new.build(parser, breadcrumbs, config) Parsers::CompletionParser.new.build(parser, breadcrumbs, config)

View file

@ -1,3 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
module GX::Types module GX::Types
enum Mode enum Mode
None None
@ -7,7 +12,7 @@ module GX::Types
GlobalCompletion GlobalCompletion
GlobalTui GlobalTui
GlobalConfig GlobalConfig
GlobalMapping # GlobalMapping
ConfigInit ConfigInit

View file

@ -1,15 +1,20 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
module GX::Utils module GX::Utils
class BreadCrumbs class BreadCrumbs
def initialize(base : Array(String)) def initialize(base : Array(String))
@ancestors = base @ancestors = base
end end
def +(elem : String) def +(other : String)
b = BreadCrumbs.new(@ancestors + [elem]) BreadCrumbs.new(@ancestors + [other])
end end
def to_s def to_s(io : IO)
@ancestors.join(" ") io << @ancestors.join(" ")
end end
def to_a def to_a

View file

@ -28,7 +28,8 @@ module GX::Utils
exit(1) exit(1)
end end
result = output.to_s.strip # .split.first? # result
output.to_s.strip
end end
end end
end end

View file

@ -1,9 +1,14 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "./breadcrumbs" require "./breadcrumbs"
module GX::Utils module GX::Utils
def self.usage_line(breadcrumbs : BreadCrumbs, description : String, has_commands : Bool = false) 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, description,
"", "",
@ -12,6 +17,6 @@ module GX::Utils
end end
def self.help_line(breadcrumbs : BreadCrumbs) 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
end end

View file

@ -1,3 +1,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
require "version_from_shard" require "version_from_shard"
module GX module GX

View file

@ -1,4 +1,8 @@
#!/bin/bash #!/bin/bash
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>
# mfm Bash completion script # mfm Bash completion script

6
static/completion.zsh Normal file
View file

@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2024 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2024 Glenn Y. Rolland <glenux@glenux.net>

32
static/sample.mfm.yaml Normal file
View file

@ -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"
#