Compare commits
7 commits
cbf39027c5
...
36fd938325
Author | SHA1 | Date | |
---|---|---|---|
36fd938325 | |||
5b0655780e | |||
f6f320e389 | |||
702f731d14 | |||
32f0b6832b | |||
6feedc2c70 | |||
9ef261779c |
48 changed files with 931 additions and 259 deletions
|
@ -8,8 +8,15 @@
|
||||||
|
|
||||||
# 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
1
.gitignore
vendored
|
@ -3,6 +3,7 @@
|
||||||
# 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
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -11,7 +11,7 @@ prepare:
|
||||||
shards install
|
shards install
|
||||||
|
|
||||||
build:
|
build:
|
||||||
shards build --error-trace
|
shards build --error-trace -Dpreview_mt
|
||||||
@echo SUCCESS
|
@echo SUCCESS
|
||||||
|
|
||||||
watch:
|
watch:
|
||||||
|
|
42
README.md
42
README.md
|
@ -66,6 +66,8 @@ version](https://code.apps.glenux.net/glenux/mfm/releases) of MFM.
|
||||||
|
|
||||||
### Command Line Options
|
### Command Line Options
|
||||||
|
|
||||||
|
Global
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage: mfm [options]
|
Usage: mfm [options]
|
||||||
|
|
||||||
|
@ -77,9 +79,43 @@ Global options
|
||||||
-h, --help Show this help
|
-h, --help Show this help
|
||||||
|
|
||||||
Commands (not implemented yet):
|
Commands (not implemented yet):
|
||||||
create Add a new filesystem
|
config Manage configuration file
|
||||||
delete Remove an existing filesystem
|
mapping Manage filesystems
|
||||||
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
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
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
|
||||||
|
@ -8,6 +12,10 @@ 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
|
||||||
|
|
23
shard.yml
23
shard.yml
|
@ -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.1.11
|
version: 0.2.0
|
||||||
|
|
||||||
targets:
|
targets:
|
||||||
mfm:
|
mfm:
|
||||||
main: src/main.cr
|
main: src/main.cr
|
||||||
|
|
||||||
# authors:
|
authors:
|
||||||
# - name <email@example.com>
|
- Glenn Y. Rolland <glenux@glenux.net>
|
||||||
|
|
||||||
# description: |
|
description: |
|
||||||
# Short description of gx-vault
|
FIXME. write description
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
crinja:
|
crinja:
|
||||||
|
@ -24,14 +24,11 @@ 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
|
||||||
|
|
||||||
# dependencies:
|
development_dependencies:
|
||||||
# pg:
|
ameba:
|
||||||
# github: will/crystal-pg
|
github: crystal-ameba/ameba
|
||||||
# version: "~> 0.5"
|
|
||||||
|
|
||||||
# development_dependencies:
|
|
||||||
# webmock:
|
|
||||||
# github: manastech/webmock.cr
|
|
||||||
|
|
||||||
license: GPL-3
|
license: GPL-3
|
||||||
|
|
1
spec/spec_helper.cr
Normal file
1
spec/spec_helper.cr
Normal file
|
@ -0,0 +1 @@
|
||||||
|
require "spec"
|
46
spec/utils/breadcrumbs_spec.cr
Normal file
46
spec/utils/breadcrumbs_spec.cr
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
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
|
184
src/cli.cr
184
src/cli.cr
|
@ -5,194 +5,38 @@
|
||||||
|
|
||||||
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 : Config
|
@config : GX::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|
|
||||||
parser.banner = "Usage: #{PROGRAM_NAME} [options]\n\nGlobal options"
|
breadcrumbs = Utils::BreadCrumbs.new([] of String)
|
||||||
|
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
|
||||||
case @config.mode
|
command = CommandFactory.create_command(@config, @config.mode)
|
||||||
when Config::Mode::ShowVersion
|
abort("ERROR: unknown command for mode #{@config.mode}") if command.nil?
|
||||||
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?
|
|
||||||
|
|
||||||
mount_or_umount(filesystem)
|
command.try &.execute
|
||||||
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
|
||||||
|
|
11
src/command_factory.cr
Normal file
11
src/command_factory.cr
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
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
|
1
src/commands.cr
Normal file
1
src/commands.cr
Normal file
|
@ -0,0 +1 @@
|
||||||
|
require "./commands/*"
|
13
src/commands/abstract_command.cr
Normal file
13
src/commands/abstract_command.cr
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
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
|
15
src/commands/config_init.cr
Normal file
15
src/commands/config_init.cr
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
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
|
15
src/commands/global_completion.cr
Normal file
15
src/commands/global_completion.cr
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
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
|
15
src/commands/global_config.cr
Normal file
15
src/commands/global_config.cr
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
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
|
18
src/commands/global_help.cr
Normal file
18
src/commands/global_help.cr
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
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
|
16
src/commands/global_mapping.cr
Normal file
16
src/commands/global_mapping.cr
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
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
|
25
src/commands/global_tui.cr
Normal file
25
src/commands/global_tui.cr
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
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
|
17
src/commands/global_version.cr
Normal file
17
src/commands/global_version.cr
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
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
|
16
src/commands/mapping_create.cr
Normal file
16
src/commands/mapping_create.cr
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
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
|
16
src/commands/mapping_delete.cr
Normal file
16
src/commands/mapping_delete.cr
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
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
|
16
src/commands/mapping_edit.cr
Normal file
16
src/commands/mapping_edit.cr
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
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
|
45
src/commands/mapping_list.cr
Normal file
45
src/commands/mapping_list.cr
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
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
|
26
src/commands/mapping_mount.cr
Normal file
26
src/commands/mapping_mount.cr
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
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
|
24
src/commands/mapping_umount.cr
Normal file
24
src/commands/mapping_umount.cr
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
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
|
|
@ -6,6 +6,11 @@
|
||||||
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
|
||||||
|
@ -14,14 +19,6 @@ 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
|
||||||
|
@ -31,26 +28,31 @@ module GX
|
||||||
getter root : Models::RootConfig?
|
getter root : Models::RootConfig?
|
||||||
|
|
||||||
property verbose : Bool
|
property verbose : Bool
|
||||||
property mode : 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
|
||||||
|
|
||||||
def initialize()
|
# FIXME: refactor and remove these parts from here
|
||||||
|
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 = Mode::Mount
|
@mode = Types::Mode::GlobalTui
|
||||||
@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"),
|
||||||
|
@ -73,7 +75,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"
|
||||||
|
|
142
src/file_system_manager.cr
Normal file
142
src/file_system_manager.cr
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
# 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
|
|
@ -34,5 +34,3 @@ end
|
||||||
app = GX::Cli.new
|
app = GX::Cli.new
|
||||||
app.parse_command_line(ARGV)
|
app.parse_command_line(ARGV)
|
||||||
app.run
|
app.run
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
|
|
||||||
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?
|
||||||
|
|
||||||
|
@ -22,11 +21,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
|
||||||
|
@ -52,5 +51,4 @@ module GX::Models::Concerns
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -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,5 +28,3 @@ module GX::Models
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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?
|
||||||
|
|
||||||
|
|
|
@ -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?
|
||||||
|
|
||||||
|
|
|
@ -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,4 +38,3 @@ module GX::Models
|
||||||
getter filesystems : Array(AbstractFilesystemConfig)
|
getter filesystems : Array(AbstractFilesystemConfig)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
5
src/parsers/base.cr
Normal file
5
src/parsers/base.cr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module GX::Parsers
|
||||||
|
abstract class AbstractParser
|
||||||
|
abstract def build(parser : OptionParser, ancestors : BreadCrumbs, config : Config)
|
||||||
|
end
|
||||||
|
end
|
26
src/parsers/completion_parser.cr
Normal file
26
src/parsers/completion_parser.cr
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
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
|
38
src/parsers/config_parser.cr
Normal file
38
src/parsers/config_parser.cr
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
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
|
73
src/parsers/mapping_parser.cr
Normal file
73
src/parsers/mapping_parser.cr
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
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
|
7
src/parsers/options/config_init_options.cr
Normal file
7
src/parsers/options/config_init_options.cr
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
require "option_parser"
|
||||||
|
|
||||||
|
module GX::Parsers::Options
|
||||||
|
class ConfigInitOptions
|
||||||
|
property path : String?
|
||||||
|
end
|
||||||
|
end
|
6
src/parsers/options/config_options.cr
Normal file
6
src/parsers/options/config_options.cr
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
require "option_parser"
|
||||||
|
|
||||||
|
module GX::Parsers::Options
|
||||||
|
class ConfigOptions
|
||||||
|
end
|
||||||
|
end
|
7
src/parsers/options/help_options.cr
Normal file
7
src/parsers/options/help_options.cr
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
require "option_parser"
|
||||||
|
|
||||||
|
module GX::Parsers::Options
|
||||||
|
class HelpOptions
|
||||||
|
property parser_snapshot : OptionParser? = nil
|
||||||
|
end
|
||||||
|
end
|
92
src/parsers/root_parser.cr
Normal file
92
src/parsers/root_parser.cr
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
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
|
21
src/types/modes.cr
Normal file
21
src/types/modes.cr
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
module GX::Types
|
||||||
|
enum Mode
|
||||||
|
None
|
||||||
|
|
||||||
|
GlobalVersion
|
||||||
|
GlobalHelp
|
||||||
|
GlobalCompletion
|
||||||
|
GlobalTui
|
||||||
|
GlobalConfig
|
||||||
|
GlobalMapping
|
||||||
|
|
||||||
|
ConfigInit
|
||||||
|
|
||||||
|
MappingCreate
|
||||||
|
MappingDelete
|
||||||
|
MappingEdit
|
||||||
|
MappingList
|
||||||
|
MappingMount
|
||||||
|
MappingUmount
|
||||||
|
end
|
||||||
|
end
|
19
src/utils/breadcrumbs.cr
Normal file
19
src/utils/breadcrumbs.cr
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
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
|
|
@ -3,9 +3,8 @@
|
||||||
# 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
|
module GX::Utils
|
||||||
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")
|
||||||
|
@ -29,8 +28,7 @@ module GX
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
result = output.to_s.strip #.split.first?
|
result = output.to_s.strip # .split.first?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
17
src/utils/parser_lines.cr
Normal file
17
src/utils/parser_lines.cr
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
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
|
|
@ -1,8 +1,5 @@
|
||||||
|
|
||||||
require "version_from_shard"
|
require "version_from_shard"
|
||||||
|
|
||||||
module GX
|
module GX
|
||||||
VersionFromShard.declare
|
VersionFromShard.declare
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue