Compare commits

..

No commits in common. "36fd93832579d31814222bfe7dad9284497957e1" and "cbf39027c5ccc74d58e73d24c836fcca859a176a" have entirely different histories.

48 changed files with 259 additions and 931 deletions

View file

@ -8,15 +8,8 @@
# List of patterns to ignore during preloading # List of patterns to ignore during preloading
ignore_list: ignore_list:
- ^\.git/ - ^\.git/.*
- ^lib.* - ^lib.*
- ^doc/
- ^bin/
- ^_prompts/
- ^\.reuse/
- ^LICENSES/
- ^\.vagrant/
- ^scripts/
# 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

1
.gitignore vendored
View file

@ -3,7 +3,6 @@
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net> # SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net> # Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
/_*
.vagrant .vagrant
bin bin
lib lib

View file

@ -11,7 +11,7 @@ prepare:
shards install shards install
build: build:
shards build --error-trace -Dpreview_mt shards build --error-trace
@echo SUCCESS @echo SUCCESS
watch: watch:

View file

@ -66,8 +66,6 @@ version](https://code.apps.glenux.net/glenux/mfm/releases) of MFM.
### Command Line Options ### Command Line Options
Global
``` ```
Usage: mfm [options] Usage: mfm [options]
@ -79,43 +77,9 @@ Global options
-h, --help Show this help -h, --help Show this help
Commands (not implemented yet): Commands (not implemented yet):
config Manage configuration file create Add a new filesystem
mapping Manage filesystems delete Remove an existing filesystem
``` edit Modify the configuration
Config management
```
Usage: mfm filesystem [options]
Global options
-c, --config FILE Set configuration file
-v, --verbose Set more verbosity
-o, --open Automatically open directory after mount
--version Show version
-h, --help Show this help
Commands (not implemented yet):
init Create init file
```
Filesystem management
```
Usage: mfm mapping [options]
Global options
-c, --config FILE Set configuration file
-v, --verbose Set more verbosity
-o, --open Automatically open directory after mount
--version Show version
-h, --help Show this help
Commands (not implemented yet):
list List fuse mappings
create Create new fuse mapping
edit Edit fuse mapping
delete Create new fuse mapping
``` ```
### Demo ### Demo

View file

@ -1,9 +1,5 @@
version: 2.0 version: 2.0
shards: shards:
ameba:
git: https://github.com/crystal-ameba/ameba.git
version: 1.6.1
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
@ -12,10 +8,6 @@ shards:
git: https://github.com/sztheory/shellwords-crystal.git git: https://github.com/sztheory/shellwords-crystal.git
version: 0.1.0 version: 0.1.0
tablo:
git: https://github.com/hutou/tablo.git
version: 0.10.1
version_from_shard: version_from_shard:
git: https://github.com/hugopl/version_from_shard.git git: https://github.com/hugopl/version_from_shard.git
version: 1.2.5 version: 1.2.5

View file

@ -5,17 +5,17 @@
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net> # Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
name: Minimalist FUSE Manager name: Minimalist FUSE Manager
version: 0.2.0 version: 0.1.11
targets: targets:
mfm: mfm:
main: src/main.cr main: src/main.cr
authors: # authors:
- Glenn Y. Rolland <glenux@glenux.net> # - name <email@example.com>
description: | # description: |
FIXME. write description # Short description of gx-vault
dependencies: dependencies:
crinja: crinja:
@ -24,11 +24,14 @@ dependencies:
github: szTheory/shellwords-crystal github: szTheory/shellwords-crystal
version_from_shard: version_from_shard:
github: hugopl/version_from_shard github: hugopl/version_from_shard
tablo:
github: hutou/tablo
development_dependencies: # dependencies:
ameba: # pg:
github: crystal-ameba/ameba # github: will/crystal-pg
# version: "~> 0.5"
# development_dependencies:
# webmock:
# github: manastech/webmock.cr
license: GPL-3 license: GPL-3

View file

@ -1 +0,0 @@
require "spec"

View file

@ -1,46 +0,0 @@
require "../spec_helper"
require "../../src/utils/breadcrumbs"
describe GX::Utils::BreadCrumbs do
context "Initialization" do
it "can initialize from array" do
# empty string
b1 = GX::Utils::BreadCrumbs.new([] of String)
b1.to_a.should be_empty
# simple string
b2 = GX::Utils::BreadCrumbs.new(["test1"])
b2.to_a.should eq(["test1"])
# array
b3 = GX::Utils::BreadCrumbs.new(["test1", "test2"])
b3.to_a.should eq(["test1", "test2"])
end
end
context "Functioning" do
it "can add values" do
# empty string
b1 = GX::Utils::BreadCrumbs.new([] of String)
b1.to_a.should be_empty
# simple string
b2 = b1 + "test1"
b2.to_a.should eq(["test1"])
b3 = b2 + "test2"
b3.to_a.should eq(["test1", "test2"])
end
it "can become a string" do
b1 = GX::Utils::BreadCrumbs.new([] of String)
b1.to_s.should eq("")
b2 = b1 + "test1"
b2.to_a.should eq("test1")
b3 = b2 + "test2"
b3.to_a.should eq("test1 test2")
end
end
end

View file

@ -5,38 +5,194 @@
require "option_parser" require "option_parser"
require "./config" require "./config"
require "./fzf"
require "./version" require "./version"
require "./parsers/root_parser"
require "./utils/breadcrumbs"
require "./utils/fzf"
require "./file_system_manager"
require "./command_factory"
module GX module GX
class Cli class Cli
Log = ::Log.for("cli") Log = ::Log.for("cli")
@config : GX::Config @config : Config
def initialize def initialize()
# Main execution starts here # Main execution starts here
# # FIXME: add a method to verify that FZF is installed
@config = Config.new @config = Config.new
## FIXME: check that FZF is installed
end end
def parse_command_line(args) def parse_command_line(args)
# update
add_args = { name: "", path: "" }
delete_args = { name: "" }
pparser = OptionParser.new do |parser| pparser = OptionParser.new do |parser|
breadcrumbs = Utils::BreadCrumbs.new([] of String) parser.banner = "Usage: #{PROGRAM_NAME} [options]\n\nGlobal options"
Parsers::RootParser.new.build(parser, breadcrumbs, @config)
parser.on("-c", "--config FILE", "Set configuration file") do |path|
Log.info { "Configuration set to #{path}" }
@config.path = path
end
parser.on("-v", "--verbose", "Set more verbosity") do |flag|
Log.info { "Verbosity enabled" }
@config.verbose = true
end
parser.on("-o", "--open", "Automatically open directory after mount") do |flag|
Log.info { "Auto-open enabled" }
@config.auto_open = true
end
parser.on("--version", "Show version") do |flag|
@config.mode = Config::Mode::ShowVersion
end
parser.on("-h", "--help", "Show this help") do |flag|
STDOUT.puts parser
exit(0)
end
parser.separator("\nCommands")
parser.on("config", "Manage configuration") do
parser.banner = "Usage: #{PROGRAM_NAME} config [commands] [options]\n\nGlobal options"
parser.separator("\nCommands")
parser.on("create", "Create vault") do
@config.mode = Config::Mode::ConfigAdd
parser.banner = "Usage: #{PROGRAM_NAME} config create [commands] [options]\n\nGlobal options"
parser.separator("\nCommand options")
parser.on("-n", "--name", "Set vault name") do |name|
add_args = add_args.merge({ name: name })
end
parser.on("-p", "--path", "Set vault encrypted path") do |path|
add_args = add_args.merge({ path: path })
end
end
parser.on("delete", "Delete vault") do
@config.mode = Config::Mode::ConfigAdd
parser.banner = "Usage: #{PROGRAM_NAME} delete [options]\n\nGlobal options"
parser.separator("\nCommand options")
parser.on("-n", "--name", "Set vault name") do |name|
delete_args = delete_args.merge({ name: name })
end
end
parser.on("edit", "Edit configuration") do |flag|
@config.mode = Config::Mode::ConfigEdit
end
end
end end
pparser.parse(args) pparser.parse(args)
end end
def run def run()
command = CommandFactory.create_command(@config, @config.mode) case @config.mode
abort("ERROR: unknown command for mode #{@config.mode}") if command.nil? when Config::Mode::ShowVersion
STDOUT.puts "#{PROGRAM_NAME} #{VERSION}"
when Config::Mode::Mount
@config.load_from_env
@config.load_from_file
filesystem = choose_filesystem
raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil?
command.try &.execute mount_or_umount(filesystem)
auto_open(filesystem) if filesystem.mounted? && @config.auto_open
end
end
def auto_open(filesystem)
# FIXME: support xdg-open
# FIXME: support mailcap
# FIXME: support user-defined command
# FIXME: detect graphical environment
mount_point_safe = filesystem.mount_point
raise Models::InvalidMountpointError.new("Invalid filesystem") if mount_point_safe.nil?
if graphical_environment?
process = Process.new(
"xdg-open", ## FIXME: make configurable
[mount_point_safe],
input: STDIN,
output: STDOUT,
error: STDERR
)
unless process.wait.success?
puts "Error opening filesystem".colorize(:red)
return
end
else
process = Process.new(
"vifm", ## FIXME: make configurable
[mount_point_safe],
input: STDIN,
output: STDOUT,
error: STDERR
)
unless process.wait.success?
puts "Error opening filesystem".colorize(:red)
return
end
end
end
def graphical_environment?
if ENV["DISPLAY"]? || ENV["WAYLAND_DISPLAY"]?
return true
end
return false
end
def choose_filesystem()
names_display = {} of String => NamedTuple(filesystem: Models::AbstractFilesystemConfig, ansi_name: String)
config_root = @config.root
return 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
names_display[result_name] = {
filesystem: filesystem,
ansi_name: ansi_name
}
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 = 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
def mount_or_umount(selected_filesystem)
if !selected_filesystem.mounted?
selected_filesystem.mount()
else
selected_filesystem.umount()
end
end end
end end
end end

View file

@ -1,11 +0,0 @@
require "./commands"
module GX
class CommandFactory
def self.create_command(config : GX::Config, mode : GX::Types::Mode) : Commands::AbstractCommand?
classes = {{ Commands::AbstractCommand.all_subclasses }}
command_klass = classes.find { |klass| klass.handles_mode == mode }
command_klass.try &.new(config)
end
end
end

View file

@ -1 +0,0 @@
require "./commands/*"

View file

@ -1,13 +0,0 @@
require "../config"
module GX::Commands
abstract class AbstractCommand
abstract def initialize(config : GX::Config)
abstract def execute
def self.mode
Gx::Types::Mode::None
end
end
end

View file

@ -1,15 +0,0 @@
require "./abstract_command"
module GX::Commands
class ConfigInit < AbstractCommand
def initialize(config : GX::Config) # FIXME
end
def execute
end
def self.handles_mode
GX::Types::Mode::ConfigInit
end
end
end

View file

@ -1,15 +0,0 @@
require "./abstract_command"
module GX::Commands
class GlobalCompletion < AbstractCommand
def initialize(@config : GX::Config)
end
def execute
end
def self.handles_mode
GX::Types::Mode::GlobalConfig
end
end
end

View file

@ -1,15 +0,0 @@
require "./abstract_command"
module GX::Commands
class GlobalConfig < AbstractCommand
def initialize(config : GX::Config) # FIXME
end
def execute
end
def self.handles_mode
GX::Types::Mode::GlobalConfig
end
end
end

View file

@ -1,18 +0,0 @@
require "./abstract_command"
module GX::Commands
class GlobalHelp < AbstractCommand
def initialize(@config : GX::Config) # FIXME
end
def execute
STDOUT.puts ""
@config.help_options.try { |opts| puts opts.parser_snapshot }
exit(0)
end
def self.handles_mode
GX::Types::Mode::GlobalHelp
end
end
end

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,25 +0,0 @@
require "./abstract_command"
require "../file_system_manager"
module GX::Commands
class GlobalTui < AbstractCommand
@file_system_manager : FileSystemManager
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)
@file_system_manager.auto_open(filesystem) if filesystem.mounted? && @config.auto_open
end
def self.handles_mode
GX::Types::Mode::GlobalTui
end
end
end

View file

@ -1,17 +0,0 @@
require "./abstract_command"
require "../config"
module GX::Commands
class GlobalVersion < AbstractCommand
def initialize(config : GX::Config) # FIXME
end
def execute
STDOUT.puts "#{File.basename PROGRAM_NAME} #{VERSION}"
end
def self.handles_mode
GX::Types::Mode::GlobalVersion
end
end
end

View file

@ -1,16 +0,0 @@
require "./abstract_command"
module GX::Commands
class MappingCreate < AbstractCommand
def initialize(config : GX::Config) # FIXME
end
def execute
# FIXME: implement
end
def self.handles_mode
GX::Types::Mode::MappingCreate
end
end
end

View file

@ -1,16 +0,0 @@
require "./abstract_command"
module GX::Commands
class MappingDelete < AbstractCommand
def initialize(config : GX::Config) # FIXME
end
def execute
# FIXME: implement
end
def self.handles_mode
GX::Types::Mode::MappingDelete
end
end
end

View file

@ -1,16 +0,0 @@
require "./abstract_command"
module GX::Commands
class MappingEdit < AbstractCommand
def initialize(config : GX::Config) # FIXME
end
def execute
# FIXME: implement
end
def self.handles_mode
GX::Types::Mode::MappingEdit
end
end
end

View file

@ -1,45 +0,0 @@
require "./abstract_command"
require "../file_system_manager"
require "tablo"
module GX::Commands
class MappingList < AbstractCommand
def initialize(@config : GX::Config)
@config.load_from_env
@config.load_from_file
@file_system_manager = FileSystemManager.new(@config)
end
def execute
filesystems = @config.root.try &.filesystems
return if filesystems.nil?
# pp filesystems
fsdata = [] of Array(String)
filesystems.each do |item|
fsdata << [
item.type,
item.name,
item.mounted?.to_s,
]
end
# pp fsdata
report = Tablo::Table.new(
fsdata,
# connectors: Tablo::CONNECTORS_SINGLE_ROUNDED
column_padding: 0,
style: "" # Tablo::STYLE_NO_MID_COL
) do |table|
table.add_column("TYPE") { |row| row[0] }
table.add_column("NAME", width: 40) { |row| row[1] }
table.add_column("MOUNTED") { |row| row[2] }
end
puts report
end
def self.handles_mode
GX::Types::Mode::MappingList
end
end
end

View file

@ -1,26 +0,0 @@
require "./abstract_command"
require "../file_system_manager"
module GX::Commands
class MappingMount < AbstractCommand
@file_system_manager : FileSystemManager
def initialize(@config : GX::Config) # FIXME
@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
end
def self.handles_mode
GX::Types::Mode::MappingMount
end
end
end

View file

@ -1,24 +0,0 @@
require "./abstract_command"
require "../file_system_manager"
module GX::Commands
class MappingUmount < AbstractCommand
@file_system_manager : FileSystemManager
def initialize(@config : GX::Config) # FIXME
@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
end
def self.handles_mode
GX::Types::Mode::MappingUmount
end
end
end

View file

@ -6,11 +6,6 @@
require "crinja" require "crinja"
require "./models" require "./models"
require "./types/modes"
require "./parsers/options/help_options"
require "./parsers/options/config_options"
require "./parsers/options/config_init_options"
require "./commands/abstract_command"
module GX module GX
class Config class Config
@ -19,6 +14,14 @@ module GX
class MissingFileError < Exception class MissingFileError < Exception
end end
enum Mode
ConfigAdd
ConfigDelete
ConfigEdit
ShowVersion
Mount
end
record NoArgs record NoArgs
record AddArgs, name : String, path : String record AddArgs, name : String, path : String
record DelArgs, name : String record DelArgs, name : String
@ -28,31 +31,26 @@ module GX
getter root : Models::RootConfig? getter root : Models::RootConfig?
property verbose : Bool property verbose : Bool
property mode : Types::Mode property mode : 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 def initialize()
property help_options : Parsers::Options::HelpOptions?
property config_init_options : Parsers::Options::ConfigInitOptions?
property config_options : Parsers::Options::ConfigOptions?
def initialize
raise Models::InvalidEnvironmentError.new("Home directory not found") if !ENV["HOME"]? raise Models::InvalidEnvironmentError.new("Home directory not found") if !ENV["HOME"]?
@home_dir = ENV["HOME"] @home_dir = ENV["HOME"]
@verbose = false @verbose = false
@auto_open = false @auto_open = false
@mode = Types::Mode::GlobalTui @mode = Mode::Mount
@filesystems = [] of Models::AbstractFilesystemConfig @filesystems = [] of Models::AbstractFilesystemConfig
@path = nil @path = nil
@args = NoArgs @args = NoArgs
end end
private def detect_config_file private def detect_config_file()
possible_files = [ possible_files = [
File.join(@home_dir, ".config", "mfm", "config.yaml"), File.join(@home_dir, ".config", "mfm", "config.yaml"),
File.join(@home_dir, ".config", "mfm", "config.yml"), File.join(@home_dir, ".config", "mfm", "config.yml"),
@ -75,7 +73,7 @@ module GX
raise MissingFileError.new("Configuration file not found") raise MissingFileError.new("Configuration file not found")
end end
def load_from_env def load_from_env()
if !ENV["FZF_DEFAULT_OPTS"]? if !ENV["FZF_DEFAULT_OPTS"]?
# force defaults settings if none defined # force defaults settings if none defined
ENV["FZF_DEFAULT_OPTS"] = "--height 40% --layout=reverse --border" ENV["FZF_DEFAULT_OPTS"] = "--height 40% --layout=reverse --border"

View file

@ -1,142 +0,0 @@
# require "./models/abstract_filesystem_config"
require "./utils/fzf"
module GX
class FileSystemManager
Log = ::Log.for("file_system_manager")
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?
selected_filesystem.mount
else
selected_filesystem.umount
end
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
mount_point_safe = filesystem.mount_point
raise Models::InvalidMountpointError.new("Invalid filesystem") if mount_point_safe.nil?
if graphical_environment?
process = Process.new(
"xdg-open", # # FIXME: make configurable
[mount_point_safe],
input: STDIN,
output: STDOUT,
error: STDERR
)
unless process.wait.success?
puts "Error opening filesystem".colorize(:red)
return
end
else
process = Process.new(
"vifm", # # FIXME: make configurable
[mount_point_safe],
input: STDIN,
output: STDOUT,
error: STDERR
)
unless process.wait.success?
puts "Error opening filesystem".colorize(:red)
return
end
end
end
def each(&)
config_root = @config.root
return if config_root.nil?
config_root.filesystems.each do |filesystem|
yield filesystem
end
end
def filesystems
config_root = @config.root
return if config_root.nil?
config_root.filesystems
end
def choose_filesystem
names_display = {} of String => NamedTuple(filesystem: Models::AbstractFilesystemConfig, ansi_name: String)
config_root = @config.root
return 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
names_display[result_name] = {
filesystem: filesystem,
ansi_name: ansi_name,
}
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
end
end
end

View file

@ -3,8 +3,9 @@
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net> # SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net> # Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
module GX::Utils module GX
class Fzf class Fzf
def self.run(list : Array(String)) : String def self.run(list : Array(String)) : String
input = IO::Memory.new input = IO::Memory.new
input.puts list.join("\n") input.puts list.join("\n")
@ -32,3 +33,4 @@ module GX::Utils
end end
end end
end end

View file

@ -34,3 +34,5 @@ end
app = GX::Cli.new app = GX::Cli.new
app.parse_command_line(ARGV) app.parse_command_line(ARGV)
app.run app.run

View file

@ -19,7 +19,7 @@ module GX::Models
use_yaml_discriminator "type", { use_yaml_discriminator "type", {
gocryptfs: GoCryptFSConfig, gocryptfs: GoCryptFSConfig,
sshfs: SshFSConfig, sshfs: SshFSConfig,
httpdirfs: HttpDirFSConfig, httpdirfs: HttpDirFSConfig
} }
getter type : String getter type : String
@ -27,12 +27,12 @@ module GX::Models
property mount_point : String? property mount_point : String?
abstract def _mount_wrapper(&block) abstract def _mount_wrapper(&block)
abstract def _mount_action abstract def _mount_action()
abstract def _mounted_prefix abstract def _mounted_prefix()
abstract def mounted_name abstract def mounted_name()
abstract def mounted? abstract def mounted?()
abstract def mount abstract def mount()
abstract def umount abstract def umount()
abstract def mount_point? abstract def mount_point?()
end end
end end

View file

@ -1,13 +1,14 @@
module GX::Models::Concerns module GX::Models::Concerns
module Base module Base
def mounted? : Bool def mounted?() : Bool
mount_point_safe = @mount_point mount_point_safe = @mount_point
raise InvalidMountpointError.new("Invalid mountpoint value") if mount_point_safe.nil? raise InvalidMountpointError.new("Invalid mountpoint value") if mount_point_safe.nil?
`mount`.includes?(" on #{mount_point_safe} type ") `mount`.includes?(" on #{mount_point_safe} type ")
end end
def umount : Nil def umount() : Nil
mount_point_safe = @mount_point mount_point_safe = @mount_point
raise InvalidMountpointError.new("Invalid mountpoint value") if mount_point_safe.nil? raise InvalidMountpointError.new("Invalid mountpoint value") if mount_point_safe.nil?
@ -21,11 +22,11 @@ module GX::Models::Concerns
end end
end end
def mount_point? def mount_point?()
!mount_point.nil? !mount_point.nil?
end end
def mount def mount()
_mount_wrapper() do _mount_wrapper() do
_mount_action _mount_action
end end
@ -51,4 +52,5 @@ module GX::Models::Concerns
end end
end end
end end
end end

View file

@ -17,7 +17,7 @@ module GX::Models
@[YAML::Field(key: "mount_point_base")] @[YAML::Field(key: "mount_point_base")]
getter mount_point_base : String? getter mount_point_base : String?
def after_initialize def after_initialize()
raise InvalidEnvironmentError.new("Home directory not found") if !ENV["HOME"]? raise InvalidEnvironmentError.new("Home directory not found") if !ENV["HOME"]?
home_dir = ENV["HOME"] home_dir = ENV["HOME"]
@ -28,3 +28,5 @@ module GX::Models
end end
end end
end end

View file

@ -13,15 +13,15 @@ module GX::Models
include Concerns::Base include Concerns::Base
def _mounted_prefix def _mounted_prefix()
"#{encrypted_path}" "#{encrypted_path}"
end end
def mounted_name def mounted_name()
"#{@name}.Open" "#{@name}.Open"
end end
def _mount_action def _mount_action()
mount_point_safe = @mount_point mount_point_safe = @mount_point
raise InvalidMountpointError.new("Invalid mount point") if mount_point_safe.nil? raise InvalidMountpointError.new("Invalid mount point") if mount_point_safe.nil?

View file

@ -13,15 +13,15 @@ module GX::Models
include Concerns::Base include Concerns::Base
def _mounted_prefix def _mounted_prefix()
"httpdirfs" "httpdirfs"
end end
def mounted_name def mounted_name()
@name @name
end end
def _mount_action def _mount_action()
mount_point_safe = @mount_point mount_point_safe = @mount_point
raise InvalidMountpointError.new("Invalid mount point") if mount_point_safe.nil? raise InvalidMountpointError.new("Invalid mount point") if mount_point_safe.nil?

View file

@ -27,7 +27,7 @@ module GX::Models
include YAML::Serializable include YAML::Serializable
include YAML::Serializable::Strict include YAML::Serializable::Strict
# @[YAML::Field(key: "version", converter: GX::Models::CrinjaConverter)] # @[YAML::Field(key: "version", converter: GX::Models::CrinjaConverter)]
@[YAML::Field(key: "version")] @[YAML::Field(key: "version")]
getter version : String getter version : String
@ -38,3 +38,4 @@ module GX::Models
getter filesystems : Array(AbstractFilesystemConfig) getter filesystems : Array(AbstractFilesystemConfig)
end end
end end

View file

@ -16,15 +16,15 @@ module GX::Models
include Concerns::Base include Concerns::Base
def _mounted_prefix def _mounted_prefix()
"#{@remote_user}@#{@remote_host}:#{@remote_path}" "#{@remote_user}@#{@remote_host}:#{@remote_path}"
end end
def mounted_name def mounted_name()
@name @name
end end
def _mount_action def _mount_action()
mount_point_safe = @mount_point mount_point_safe = @mount_point
raise InvalidMountpointError.new("Invalid mount point") if mount_point_safe.nil? raise InvalidMountpointError.new("Invalid mount point") if mount_point_safe.nil?
@ -33,7 +33,7 @@ module GX::Models
[ [
"-p", remote_port, "-p", remote_port,
"#{@remote_user}@#{@remote_host}:#{@remote_path}", "#{@remote_user}@#{@remote_host}:#{@remote_path}",
mount_point_safe, mount_point_safe
], ],
input: STDIN, input: STDIN,
output: STDOUT, output: STDOUT,

View file

@ -1,5 +0,0 @@
module GX::Parsers
abstract class AbstractParser
abstract def build(parser : OptionParser, ancestors : BreadCrumbs, config : Config)
end
end

View file

@ -1,26 +0,0 @@
require "./base.cr"
module GX::Parsers
class CompletionParser < AbstractParser
def build(parser, ancestors, config)
breadcrumbs = ancestors + "completion"
parser.banner = Utils.usage_line(
breadcrumbs,
"Manage #{PROGRAM_NAME} completion",
true
)
parser.separator("\nCompletion commands:")
parser.on("--bash", "Generate bash completion") do |flag|
Log.info { "Set bash completion" }
end
parser.on("--zsh", "Generate zsh completion") do |flag|
Log.info { "Set zsh completion" }
end
parser.separator Utils.help_line(breadcrumbs)
end
end
end

View file

@ -1,38 +0,0 @@
require "./options/config_options"
require "./options/config_init_options"
require "./base"
require "../types/modes"
require "../utils/parser_lines"
module GX::Parsers
class ConfigParser < AbstractParser
def build(parser, ancestors, config)
breadcrumbs = ancestors + "config"
config.config_options = Parsers::Options::ConfigOptions.new
parser.banner = Utils.usage_line(
breadcrumbs,
"Helpers for #{PROGRAM_NAME}'s configuration file",
true
)
parser.separator("\nConfig commands")
parser.on("init", "Create initial mfm configuration") do
config.mode = Types::Mode::ConfigInit
config.config_init_options = Parsers::Options::ConfigInitOptions.new
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|
config.config_init_options.try do |opts|
opts.path = path
end
end
parser.separator(Utils.help_line(breadcrumbs + "init"))
end
parser.separator(Utils.help_line(breadcrumbs))
end
end
end

View file

@ -1,73 +0,0 @@
require "./base.cr"
require "../utils/parser_lines"
module GX::Parsers
class MappingParser < AbstractParser
def build(parser, ancestors, config)
breadcrumbs = ancestors + "mapping"
add_args = {name: "", path: ""}
delete_args = {name: ""}
parser.banner = Utils.usage_line(
breadcrumbs,
"Manage FUSE filesystem mappings",
true
)
parser.separator("\nCommands")
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
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})
end
parser.on("-p", "--path", "Set vault encrypted path") do |path|
add_args = add_args.merge({path: path})
end
parser.separator(Utils.help_line(breadcrumbs + "create"))
end
parser.on("edit", "Edit configuration") do |flag|
config.mode = Types::Mode::MappingEdit
parser.separator(Utils.help_line(breadcrumbs + "edit"))
# abort("FIXME: Not implemented")
end
parser.on("mount", "Mount mapping") do |flag|
config.mode = Types::Mode::MappingMount
parser.separator(Utils.help_line(breadcrumbs + "mount"))
# abort("FIXME: Not implemented")
end
parser.on("umount", "Umount mapping") do |flag|
config.mode = Types::Mode::MappingUmount
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.on("-n", "--name", "Set vault name") do |name|
delete_args = delete_args.merge({name: name})
end
parser.separator(Utils.help_line(breadcrumbs + "delete"))
end
parser.separator Utils.help_line(breadcrumbs)
end
end
end

View file

@ -1,7 +0,0 @@
require "option_parser"
module GX::Parsers::Options
class ConfigInitOptions
property path : String?
end
end

View file

@ -1,6 +0,0 @@
require "option_parser"
module GX::Parsers::Options
class ConfigOptions
end
end

View file

@ -1,7 +0,0 @@
require "option_parser"
module GX::Parsers::Options
class HelpOptions
property parser_snapshot : OptionParser? = nil
end
end

View file

@ -1,92 +0,0 @@
require "./base"
require "./config_parser"
require "./mapping_parser"
require "./completion_parser"
require "../utils/parser_lines"
require "../commands"
module GX::Parsers
class RootParser < AbstractParser
def build(parser, ancestors, config)
breadcrumbs = ancestors + (File.basename PROGRAM_NAME)
parser.banner = Utils.usage_line(
breadcrumbs,
"A management tool for your various FUSE filesystems",
true
)
parser.on("-c", "--config FILE", "Set configuration file") do |path|
Log.info { "Configuration set to #{path}" }
config.path = path
end
parser.on("-v", "--verbose", "Set more verbosity") do |flag|
Log.info { "Verbosity enabled" }
config.verbose = true
end
parser.on("-o", "--open", "Automatically open directory after mount") do |flag|
Log.info { "Auto-open enabled" }
config.auto_open = true
end
parser.on("--version", "Show version") do |flag|
config.mode = Types::Mode::GlobalVersion
end
parser.on("-h", "--help", "Show this help") do |flag|
config.mode = Types::Mode::GlobalHelp
config.help_options = Parsers::Options::HelpOptions.new
config.help_options.try { |opts| opts.parser_snapshot = parser.dup }
end
parser.separator("\nGlobal commands:")
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.command = Commands::Config.new(config)
Parsers::ConfigParser.new.build(parser, breadcrumbs, config)
end
parser.on("tui", "Interactive text user interface (default)") do
config.mode = Types::Mode::GlobalTui
end
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 }
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)
end
parser.separator(Utils.help_line(breadcrumbs))
# Manage errors
parser.unknown_args do |remaining_args, _|
next if remaining_args.size == 0
puts parser
abort("ERROR: Invalid arguments: #{remaining_args.join(" ")}")
end
parser.invalid_option do |ex|
puts parser
abort("ERROR: Invalid option: '#{ex}'!")
end
end
end
end

View file

@ -1,21 +0,0 @@
module GX::Types
enum Mode
None
GlobalVersion
GlobalHelp
GlobalCompletion
GlobalTui
GlobalConfig
GlobalMapping
ConfigInit
MappingCreate
MappingDelete
MappingEdit
MappingList
MappingMount
MappingUmount
end
end

View file

@ -1,19 +0,0 @@
module GX::Utils
class BreadCrumbs
def initialize(base : Array(String))
@ancestors = base
end
def +(elem : String)
b = BreadCrumbs.new(@ancestors + [elem])
end
def to_s
@ancestors.join(" ")
end
def to_a
@ancestors.clone
end
end
end

View file

@ -1,17 +0,0 @@
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]",
"",
description,
"",
"Global options:",
].join("\n")
end
def self.help_line(breadcrumbs : BreadCrumbs)
"\nRun '#{breadcrumbs.to_s} COMMAND --help' for more information on a command."
end
end

View file

@ -1,5 +1,8 @@
require "version_from_shard" require "version_from_shard"
module GX module GX
VersionFromShard.declare VersionFromShard.declare
end end