From 6aa3df301efecca92f2253c423f7f34fd2818fb2 Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Fri, 9 Apr 2021 23:02:37 +0530 Subject: [PATCH] Separate config.yml file from rust files Also be less aggressive for version compatibility. Use the following logic: Knowing that we use `{major}.{minor}.{patch}` versioning, - Major version mismatch are incompatible. Fail with error, suggesting to visit the Upgrade Guide. - Minor version updates and patch fixes are compatible. Suggest user to update the config file version manually. Or visit the Upgrade Guide. - However, if the config file has greater value for minor version than the app, also fail with error. Suggesting the user to visit Upgrade Guide. Though in this case, the user will be downgrading. Ref: https://github.com/sayanarijit/xplr/issues/45 --- Cargo.lock | 1 + Cargo.toml | 3 +- src/app.rs | 59 ++-- src/config.rs | 762 +----------------------------------------- src/config.yml | 723 +++++++++++++++++++++++++++++++++++++++ src/default_config.rs | 8 + src/lib.rs | 1 + src/main.rs | 3 +- tests/test_version.rs | 44 ++- 9 files changed, 816 insertions(+), 788 deletions(-) create mode 100644 src/config.yml create mode 100644 src/default_config.rs diff --git a/Cargo.lock b/Cargo.lock index 674007e..b300fc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1370,6 +1370,7 @@ dependencies = [ "crossterm", "dirs", "handlebars", + "lazy_static", "mime_guess", "notify", "serde", diff --git a/Cargo.toml b/Cargo.toml index 757d935..534c1de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "xplr" -version = "0.3.13" # Update app.rs and default.nix +version = "0.3.13" # Update default_config.rs and default.nix authors = ["Arijit Basu "] edition = "2018" description = "A hackable, minimal, fast TUI file explorer, stealing ideas from nnn and fzf" @@ -23,6 +23,7 @@ mime_guess = "2.0.3" anyhow = "1.0" chrono = { version = "0.4", features = ["serde"] } notify = "4.0.12" +lazy_static = "1.4.0" [dev-dependencies] criterion = "0.3" diff --git a/src/app.rs b/src/app.rs index b9ac1fa..5f766c5 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,5 +1,6 @@ use crate::config::Config; use crate::config::Mode; +use crate::default_config::DEFAULT_CONFIG; use crate::input::Key; use anyhow::{bail, Result}; use chrono::{DateTime, Utc}; @@ -12,7 +13,6 @@ use std::fs; use std::io; use std::path::PathBuf; -pub const VERSION: &str = "v0.3.13"; // Update Cargo.toml and default.nix pub const TEMPLATE_TABLE_ROW: &str = "TEMPLATE_TABLE_ROW"; pub const UNSUPPORTED_STR: &str = "???"; pub const UPGRADE_GUIDE_LINK: &str = "https://github.com/sayanarijit/xplr/wiki/Upgrade-Guide"; @@ -695,19 +695,35 @@ pub enum HelpMenuLine { Paragraph(String), } -pub fn is_compatible(existing: &str, required: &str) -> bool { - let mut existing = existing.split('.'); - let mut required = required.split('.'); - - let mut major_existing = existing.next().unwrap_or_default(); - let mut major_required = required.next().unwrap_or_default(); - - if major_existing == "v0" && major_required == "v0" { - major_existing = existing.next().unwrap_or_default(); - major_required = required.next().unwrap_or_default(); +/// Major version should be the same. +/// Config minor version should be lower. +/// Patch/fix version can be anything. +pub fn is_compatible(configv: &str, appv: &str) -> bool { + let mut configv = configv + .strip_prefix('v') + .unwrap_or_default() + .split('.') + .map(|c| c.parse::().unwrap()); + + let mut appv = appv + .strip_prefix('v') + .unwrap_or_default() + .split('.') + .map(|c| c.parse::().unwrap()); + + let mut major_configv = configv.next().unwrap_or_default(); + let mut minor_configv = configv.next().unwrap_or_default(); + let mut major_appv = appv.next().unwrap_or_default(); + let mut minor_appv = appv.next().unwrap_or_default(); + + if major_configv == 0 && major_appv == 0 { + major_configv = minor_configv; + minor_configv = configv.next().unwrap_or_default(); + major_appv = minor_appv; + minor_appv = appv.next().unwrap_or_default(); }; - major_existing == major_required + major_configv == major_appv && minor_configv <= minor_appv } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -741,7 +757,9 @@ impl App { Config::default() }; - if config.version != VERSION && !is_compatible(&config.version, VERSION) { + if config.version != DEFAULT_CONFIG.version + && !is_compatible(&config.version, &DEFAULT_CONFIG.version) + { bail!( "incompatible configuration version in {} You config version is : {} @@ -749,16 +767,17 @@ impl App { Visit {}", config_file.to_string_lossy().to_string(), config.version, - VERSION, + DEFAULT_CONFIG.version, UPGRADE_GUIDE_LINK, ) }; - let mode = config - .modes - .get(&"default".to_string()) - .map(|k| k.to_owned()) - .unwrap_or_default(); + let mode = match config.modes.get(&"default".to_string()) { + Some(m) => m.clone(), + None => { + bail!("'default' mode is missing") + } + }; let pid = std::process::id(); let session_path = dirs::runtime_dir() @@ -779,7 +798,7 @@ impl App { } Ok(Self { - version: VERSION.to_string(), + version: DEFAULT_CONFIG.version.clone(), config, pwd: pwd.to_string_lossy().to_string(), directory_buffers: Default::default(), diff --git a/src/config.rs b/src/config.rs index c534b07..7f3a9b6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,12 +1,10 @@ use crate::app::ExternalMsg; use crate::app::HelpMenuLine; -use crate::app::VERSION; +use crate::default_config::DEFAULT_CONFIG; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::collections::HashMap; use tui::layout::Constraint as TuiConstraint; -use tui::style::Color; -use tui::style::Modifier; use tui::style::Style; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -46,33 +44,7 @@ pub struct FileTypesConfig { impl Default for FileTypesConfig { fn default() -> Self { - FileTypesConfig { - directory: FileTypeConfig { - icon: "ð".into(), - style: Style::default() - .add_modifier(Modifier::BOLD) - .fg(Color::Cyan), - custom: Default::default(), - }, - - file: FileTypeConfig { - icon: "ƒ".into(), - style: Default::default(), - custom: Default::default(), - }, - - symlink: FileTypeConfig { - icon: "§".into(), - style: Style::default() - .add_modifier(Modifier::ITALIC) - .fg(Color::Cyan), - custom: Default::default(), - }, - - mime_essence: Default::default(), - extension: Default::default(), - special: Default::default(), - } + DEFAULT_CONFIG.filetypes.clone() } } @@ -168,62 +140,7 @@ pub struct GeneralConfig { impl Default for GeneralConfig { fn default() -> Self { - let yaml = r###" - show_hidden: false - table: - header: - cols: - - format: "│ path" - - format: "type" - - format: " index" - height: 1 - style: - add_modifier: - bits: 1 - sub_modifier: - bits: 0 - row: - cols: - - format: "{{{tree}}}{{{prefix}}}{{{icon}}} {{{relativePath}}}{{#if isDir}}/{{/if}}{{{suffix}}}" - - format: "{{{mimeEssence}}}" - - format: "{{#if isBeforeFocus}}-{{else}} {{/if}}{{{relativeIndex}}}/{{{index}}}/{{{total}}}" - - col_spacing: 3 - col_widths: - - percentage: 60 - - percentage: 20 - - percentage: 20 - - tree: - - format: "├─" - - format: "├─" - - format: "╰─" - - normal_ui: - prefix: " " - suffix: "" - - focused_ui: - prefix: "▸[" - suffix: "]" - style: - fg: Blue - add_modifier: - bits: 1 - sub_modifier: - bits: 0 - - selection_ui: - prefix: " {" - suffix: "}" - style: - fg: LightGreen - add_modifier: - bits: 1 - sub_modifier: - bits: 0 - "###; - serde_yaml::from_str(yaml).unwrap() + DEFAULT_CONFIG.general.clone() } } @@ -245,156 +162,6 @@ pub struct KeyBindings { pub default: Option, } -impl Default for KeyBindings { - fn default() -> Self { - let on_key: BTreeMap = serde_yaml::from_str( - r###" - up: - help: up [k] - messages: - - FocusPrevious - - k: - messages: - - FocusPrevious - - down: - help: down [j] - messages: - - FocusNext - - j: - messages: - - FocusNext - - right: - help: enter [l] - messages: - - Enter - - l: - messages: - - Enter - - left: - help: back [h] - messages: - - Back - - h: - messages: - - Back - - g: - help: go to - messages: - - SwitchMode: go to - - G: - help: go to bottom - messages: - - FocusLast - - ctrl-f: - help: search [/] - messages: - - SwitchMode: search - - SetInputBuffer: "" - - Explore - - /: - messages: - - SwitchMode: search - - SetInputBuffer: "" - - Explore - - d: - help: delete - messages: - - SwitchMode: delete - - ":": - help: action - messages: - - SwitchMode: action - - space: - help: toggle selection [v] - messages: - - ToggleSelection - - FocusNext - - v: - messages: - - ToggleSelection - - FocusNext - - r: - help: rename - messages: - - SwitchMode: rename - - BashExecSilently: | - echo "SetInputBuffer: $(basename ${XPLR_FOCUS_PATH})" >> "${XPLR_PIPE_MSG_IN:?}" - - ".": - help: show hidden - messages: - - ToggleNodeFilter: - filter: RelativePathDoesNotStartWith - input: . - - Explore - - enter: - help: quit with result - messages: - - PrintResultAndQuit - - "#": - messages: - - PrintAppStateAndQuit - - "?": - help: global help menu - messages: - - BashExec: | - ${PAGER:-less} "${XPLR_PIPE_GLOBAL_HELP_MENU_OUT}" - - ctrl-c: - help: cancel & quit [q] - messages: - - Terminate - - q: - messages: - - Terminate - "###, - ) - .unwrap(); - - let default = Some(Action { - help: None, - messages: vec![ExternalMsg::SwitchMode("default".into())], - }); - - let on_number = Some(Action { - help: Some("input".to_string()), - messages: vec![ - ExternalMsg::ResetInputBuffer, - ExternalMsg::SwitchMode("number".into()), - ExternalMsg::BufferInputFromKey, - ], - }); - - Self { - on_key, - on_alphabet: Default::default(), - on_number, - on_special_character: Default::default(), - default, - } - } -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Mode { pub name: String, @@ -405,7 +172,6 @@ pub struct Mode { #[serde(default)] pub extra_help: Option, - #[serde(default)] pub key_bindings: KeyBindings, } @@ -464,17 +230,6 @@ impl Mode { } } -impl Default for Mode { - fn default() -> Self { - Self { - name: "default".into(), - help: Default::default(), - extra_help: Default::default(), - key_bindings: Default::default(), - } - } -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { pub version: String, @@ -485,523 +240,16 @@ pub struct Config { #[serde(default)] pub filetypes: FileTypesConfig, - #[serde(default)] pub modes: HashMap, } impl Default for Config { fn default() -> Self { - let search_mode: Mode = serde_yaml::from_str( - r###" - name: search - key_bindings: - on_key: - enter: - help: focus - messages: - - RemoveNodeFilterFromInput: - filter: RelativePathDoesContain - case_sensitive: false - - SwitchMode: default - - Explore - - up: - help: up - messages: - - FocusPrevious - - down: - help: down - messages: - - FocusNext - - right: - help: enter - messages: - - RemoveNodeFilterFromInput: - filter: RelativePathDoesContain - case_sensitive: false - - Enter - - SetInputBuffer: "" - - Explore - - left: - help: back - messages: - - RemoveNodeFilterFromInput: - filter: RelativePathDoesContain - case_sensitive: false - - Back - - SetInputBuffer: "" - - Explore - - esc: - help: cancel - messages: - - RemoveNodeFilterFromInput: - filter: RelativePathDoesContain - case_sensitive: false - - SwitchMode: default - - Explore - - backspace: - help: clear - messages: - - RemoveNodeFilterFromInput: - filter: RelativePathDoesContain - case_sensitive: false - - SetInputBuffer: "" - - Explore - - ctrl-c: - help: cancel & quit - messages: - - Terminate - - default: - messages: - - RemoveNodeFilterFromInput: - filter: RelativePathDoesContain - case_sensitive: false - - BufferInputFromKey - - AddNodeFilterFromInput: - filter: RelativePathDoesContain - case_sensitive: false - - Explore - "###, - ) - .unwrap(); - - let goto_mode: Mode = serde_yaml::from_str( - r###" - name: go to - key_bindings: - on_key: - g: - help: top - messages: - - FocusFirst - - SwitchMode: default - - x: - help: open in gui - messages: - - BashExecSilently: | - OPENER="$(which xdg-open)" - ${OPENER:-open} "${XPLR_FOCUS_PATH:?}" &> /dev/null - - SwitchMode: default - - ctrl-c: - help: cancel & quit - messages: - - Terminate - - default: - messages: - - SwitchMode: default - "###, - ) - .unwrap(); - - let action_mode: Mode = serde_yaml::from_str( - r###" - name: action to - key_bindings: - on_number: - help: go to index - messages: - - ResetInputBuffer - - SwitchMode: number - - BufferInputFromKey - - on_key: - "!": - help: shell - messages: - - Call: - command: bash - - Explore - - SwitchMode: default - - c: - help: create - messages: - - SwitchMode: create - - e: - help: open in editor - messages: - - BashExec: | - ${EDITOR:-vi} "${XPLR_FOCUS_PATH:?}" - - SwitchMode: default - - s: - help: selection operations - messages: - - SwitchMode: selection ops - - l: - help: logs - messages: - - BashExec: | - cat "${XPLR_PIPE_LOGS_OUT}" - read -p "[enter to continue]" - - SwitchMode: default - - ctrl-c: - help: cancel & quit [q] - messages: - - Terminate - - q: - messages: - - Terminate - - default: - messages: - - SwitchMode: default - "###, - ) - .unwrap(); - - let selection_ops_mode: Mode = serde_yaml::from_str( - r###" - name: selection ops - key_bindings: - on_key: - c: - help: copy here - messages: - - BashExec: | - (while IFS= read -r line; do - if cp -vr "${line:?}" ./; then - echo "LogSuccess: $line copied to $PWD" >> "${XPLR_PIPE_MSG_IN:?}" - else - echo "LogError: failed to copy $line to $PWD" >> "${XPLR_PIPE_MSG_IN:?}" - fi - done < "${XPLR_PIPE_SELECTION_OUT:?}") - echo Explore >> "${XPLR_PIPE_MSG_IN:?}" - echo ClearSelection >> "${XPLR_PIPE_MSG_IN:?}" - read -p "[enter to continue]" - - SwitchMode: default - - m: - help: move here - messages: - - BashExec: | - (while IFS= read -r line; do - if mv -v "${line:?}" ./; then - echo "LogSuccess: $line moved to $PWD" >> "${XPLR_PIPE_MSG_IN:?}" - else - echo "LogError: failed to move $line to $PWD" >> "${XPLR_PIPE_MSG_IN:?}" - fi - done < "${XPLR_PIPE_SELECTION_OUT:?}") - echo Explore >> "${XPLR_PIPE_MSG_IN:?}" - read -p "[enter to continue]" - - SwitchMode: default - - ctrl-c: - help: cancel & quit - messages: - - Terminate - - default: - messages: - - SwitchMode: default - "###, - ) - .unwrap(); - - let number_mode: Mode = serde_yaml::from_str( - r###" - name: number - key_bindings: - on_key: - up: - help: to up [k] - messages: - - FocusPreviousByRelativeIndexFromInput - - SwitchMode: default - - k: - messages: - - FocusPreviousByRelativeIndexFromInput - - SwitchMode: default - - down: - help: to down [j] - messages: - - FocusNextByRelativeIndexFromInput - - SwitchMode: default - - j: - messages: - - FocusNextByRelativeIndexFromInput - - SwitchMode: default - - enter: - help: to index - messages: - - FocusByIndexFromInput - - SwitchMode: default - - backspace: - help: clear - messages: - - ResetInputBuffer - - ctrl-c: - help: cancel & quit - messages: - - Terminate - - on_number: - help: input - messages: - - BufferInputFromKey - - default: - messages: - - SwitchMode: default - "###, - ) - .unwrap(); - - let create_mode: Mode = serde_yaml::from_str( - r###" - name: create - key_bindings: - on_key: - f: - help: create file - messages: - - SwitchMode: create file - - SetInputBuffer: "" - - d: - help: create directory - messages: - - SwitchMode: create directory - - SetInputBuffer: "" - - esc: - help: cancel - messages: - - SwitchMode: default - - ctrl-c: - help: cancel & quit - messages: - - Terminate - - default: - messages: - - SwitchMode: default - "###, - ) - .unwrap(); - - let create_file_mode: Mode = serde_yaml::from_str( - r###" - name: create file - key_bindings: - on_key: - enter: - help: create file - messages: - - BashExecSilently: | - PTH="${XPLR_INPUT_BUFFER:?}" - if touch "${PTH:?}"; then - echo Explore >> "${XPLR_PIPE_MSG_IN:?}" - echo "LogSuccess: $PTH created" >> "${XPLR_PIPE_MSG_IN:?}" - echo "FocusPath: $PTH" >> "${XPLR_PIPE_MSG_IN:?}" - else - echo "LogError: failed to create $PTH" >> "${XPLR_PIPE_MSG_IN:?}" - echo Refresh >> "${XPLR_PIPE_MSG_IN:?}" - fi - - SwitchMode: default - - backspace: - help: clear - messages: - - SetInputBuffer: "" - - esc: - help: cancel - messages: - - SwitchMode: default - - ctrl-c: - help: cancel & quit - messages: - - Terminate - - default: - messages: - - BufferInputFromKey - "###, - ) - .unwrap(); - - let create_dir_mode: Mode = serde_yaml::from_str( - r###" - name: create directory - key_bindings: - on_key: - enter: - help: create directory - messages: - - BashExecSilently: | - PTH="${XPLR_INPUT_BUFFER:?}" - if mkdir -p "$PTH"; then - echo Explore >> "${XPLR_PIPE_MSG_IN:?}" - echo "LogSuccess: $PTH created" >> "${XPLR_PIPE_MSG_IN:?}" - echo "FocusPath: $PTH" >> "${XPLR_PIPE_MSG_IN:?}" - else - echo "LogError: failed to create $PTH" >> "${XPLR_PIPE_MSG_IN:?}" - fi - - SwitchMode: default - - backspace: - help: clear - messages: - - SetInputBuffer: "" - - esc: - help: cancel - messages: - - SwitchMode: default - - ctrl-c: - help: cancel & quit - messages: - - Terminate - - default: - messages: - - BufferInputFromKey - "###, - ) - .unwrap(); - - let rename_mode: Mode = serde_yaml::from_str( - r###" - name: rename - key_bindings: - on_key: - enter: - help: rename - messages: - - BashExecSilently: | - SRC="${XPLR_FOCUS_PATH:?}" - TARGET="${XPLR_INPUT_BUFFER:?}" - if mv -v "${SRC:?}" "${TARGET:?}"; then - echo Explore >> "${XPLR_PIPE_MSG_IN:?}" - echo "LogSuccess: $SRC renamed to $TARGET" >> "${XPLR_PIPE_MSG_IN:?}" - echo "FocusPath: $TARGET" >> "${XPLR_PIPE_MSG_IN:?}" - else - echo "LogError: failed to rename $SRC to $TARGET" >> "${XPLR_PIPE_MSG_IN:?}" - fi - - SwitchMode: default - - backspace: - help: clear - messages: - - SetInputBuffer: "" - - esc: - help: cancel - messages: - - SwitchMode: default - - ctrl-c: - help: cancel & quit - messages: - - Terminate - - default: - messages: - - BufferInputFromKey - "###, - ) - .unwrap(); - - let delete_mode: Mode = serde_yaml::from_str( - r###" - name: delete - key_bindings: - on_key: - d: - help: delete - messages: - - BashExec: | - (while IFS= read -r line; do - if [ -d "$line" ]; then - if rmdir -v "${line:?}"; then - echo "LogSuccess: $line deleted" >> "${XPLR_PIPE_MSG_IN:?}" - else - echo "LogError: failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}" - fi - else - if rm -v "${line:?}"; then - echo "FocusNext" >> "${XPLR_PIPE_MSG_IN:?}" - echo "LogSuccess: $line deleted" >> "${XPLR_PIPE_MSG_IN:?}" - else - echo "LogError: failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}" - fi - fi - done < "${XPLR_PIPE_RESULT_OUT:?}") - echo Explore >> "${XPLR_PIPE_MSG_IN:?}" - read -p "[enter to continue]" - - SwitchMode: default - - D: - help: force delete - messages: - - BashExec: | - (while IFS= read -r line; do - if rm -rfv "${line:?}"; then - echo "FocusNext" >> "${XPLR_PIPE_MSG_IN:?}" - echo "LogSuccess: $line deleted" >> "${XPLR_PIPE_MSG_IN:?}" - else - echo "LogError: failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}" - fi - done < "${XPLR_PIPE_RESULT_OUT:?}") - echo Explore >> "${XPLR_PIPE_MSG_IN:?}" - read -p "[enter to continue]" - - SwitchMode: default - - Explore - - ctrl-c: - help: cancel & quit - messages: - - Terminate - - default: - messages: - - SwitchMode: default - "###, - ) - .unwrap(); - - let mut modes: HashMap = Default::default(); - modes.insert("default".into(), Mode::default()); - modes.insert("go to".into(), goto_mode); - modes.insert("number".into(), number_mode); - modes.insert("create".into(), create_mode); - modes.insert("rename".into(), rename_mode); - modes.insert("create file".into(), create_file_mode); - modes.insert("create directory".into(), create_dir_mode); - modes.insert("delete".into(), delete_mode); - modes.insert("action".into(), action_mode); - modes.insert("search".into(), search_mode); - modes.insert("selection ops".into(), selection_ops_mode); - Self { - version: VERSION.into(), + version: DEFAULT_CONFIG.version.clone(), general: Default::default(), filetypes: Default::default(), - modes, + modes: DEFAULT_CONFIG.modes.clone(), } } } diff --git a/src/config.yml b/src/config.yml new file mode 100644 index 0000000..443a3fe --- /dev/null +++ b/src/config.yml @@ -0,0 +1,723 @@ +version: v0.3.13 +general: + show_hidden: false + table: + header: + cols: + - format: │ path + style: + fg: null + bg: null + add_modifier: + bits: 0 + sub_modifier: + bits: 0 + - format: type + style: + fg: null + bg: null + add_modifier: + bits: 0 + sub_modifier: + bits: 0 + - format: ' index' + style: + fg: null + bg: null + add_modifier: + bits: 0 + sub_modifier: + bits: 0 + style: + fg: null + bg: null + add_modifier: + bits: 1 + sub_modifier: + bits: 0 + height: 1 + row: + cols: + - format: '{{{tree}}}{{{prefix}}}{{{icon}}} {{{relativePath}}}{{#if isDir}}/{{/if}}{{{suffix}}}' + style: + fg: null + bg: null + add_modifier: + bits: 0 + sub_modifier: + bits: 0 + - format: '{{{mimeEssence}}}' + style: + fg: null + bg: null + add_modifier: + bits: 0 + sub_modifier: + bits: 0 + - format: '{{#if isBeforeFocus}}-{{else}} {{/if}}{{{relativeIndex}}}/{{{index}}}/{{{total}}}' + style: + fg: null + bg: null + add_modifier: + bits: 0 + sub_modifier: + bits: 0 + style: + fg: null + bg: null + add_modifier: + bits: 0 + sub_modifier: + bits: 0 + height: 0 + style: + fg: null + bg: null + add_modifier: + bits: 0 + sub_modifier: + bits: 0 + tree: + - format: ├─ + style: + fg: null + bg: null + add_modifier: + bits: 0 + sub_modifier: + bits: 0 + - format: ├─ + style: + fg: null + bg: null + add_modifier: + bits: 0 + sub_modifier: + bits: 0 + - format: ╰─ + style: + fg: null + bg: null + add_modifier: + bits: 0 + sub_modifier: + bits: 0 + col_spacing: 3 + col_widths: + - percentage: 60 + - percentage: 20 + - percentage: 20 + normal_ui: + prefix: ' ' + suffix: '' + style: + fg: null + bg: null + add_modifier: + bits: 0 + sub_modifier: + bits: 0 + focused_ui: + prefix: ▸[ + suffix: ']' + style: + fg: Blue + bg: null + add_modifier: + bits: 1 + sub_modifier: + bits: 0 + selection_ui: + prefix: ' {' + suffix: '}' + style: + fg: LightGreen + bg: null + add_modifier: + bits: 1 + sub_modifier: + bits: 0 +filetypes: + directory: + icon: ð + style: + fg: Cyan + bg: null + add_modifier: + bits: 1 + sub_modifier: + bits: 0 + custom: {} + file: + icon: ƒ + style: + fg: null + bg: null + add_modifier: + bits: 0 + sub_modifier: + bits: 0 + custom: {} + symlink: + icon: § + style: + fg: Cyan + bg: null + add_modifier: + bits: 4 + sub_modifier: + bits: 0 + custom: {} + mime_essence: {} + extension: {} + special: {} +modes: + selection ops: + name: selection ops + help: null + extra_help: null + key_bindings: + on_key: + c: + help: copy here + messages: + - BashExec: | + (while IFS= read -r line; do + if cp -vr "${line:?}" ./; then + echo "LogSuccess: $line copied to $PWD" >> "${XPLR_PIPE_MSG_IN:?}" + else + echo "LogError: failed to copy $line to $PWD" >> "${XPLR_PIPE_MSG_IN:?}" + fi + done < "${XPLR_PIPE_SELECTION_OUT:?}") + echo Explore >> "${XPLR_PIPE_MSG_IN:?}" + echo ClearSelection >> "${XPLR_PIPE_MSG_IN:?}" + read -p "[enter to continue]" + - SwitchMode: default + + m: + help: move here + messages: + - BashExec: | + (while IFS= read -r line; do + if mv -v "${line:?}" ./; then + echo "LogSuccess: $line moved to $PWD" >> "${XPLR_PIPE_MSG_IN:?}" + else + echo "LogError: failed to move $line to $PWD" >> "${XPLR_PIPE_MSG_IN:?}" + fi + done < "${XPLR_PIPE_SELECTION_OUT:?}") + echo Explore >> "${XPLR_PIPE_MSG_IN:?}" + read -p "[enter to continue]" + - SwitchMode: default + + ctrl-c: + help: cancel & quit + messages: + - Terminate + + default: + messages: + - SwitchMode: default + + create file: + name: create file + help: null + extra_help: null + key_bindings: + on_key: + enter: + help: create file + messages: + - BashExecSilently: | + PTH="${XPLR_INPUT_BUFFER:?}" + if touch "${PTH:?}"; then + echo Explore >> "${XPLR_PIPE_MSG_IN:?}" + echo "LogSuccess: $PTH created" >> "${XPLR_PIPE_MSG_IN:?}" + echo "FocusPath: $PTH" >> "${XPLR_PIPE_MSG_IN:?}" + else + echo "LogError: failed to create $PTH" >> "${XPLR_PIPE_MSG_IN:?}" + echo Refresh >> "${XPLR_PIPE_MSG_IN:?}" + fi + - SwitchMode: default + + backspace: + help: clear + messages: + - SetInputBuffer: "" + + esc: + help: cancel + messages: + - SwitchMode: default + + ctrl-c: + help: cancel & quit + messages: + - Terminate + + default: + messages: + - BufferInputFromKey + + create: + name: create + help: null + extra_help: null + key_bindings: + on_key: + ctrl-c: + help: cancel & quit + messages: + - Terminate + d: + help: create directory + messages: + - SwitchMode: create directory + - SetInputBuffer: '' + esc: + help: cancel + messages: + - SwitchMode: default + f: + help: create file + messages: + - SwitchMode: create file + - SetInputBuffer: '' + on_alphabet: null + on_number: null + on_special_character: null + default: + help: null + messages: + - SwitchMode: default + rename: + name: rename + help: null + extra_help: null + key_bindings: + on_key: + enter: + help: rename + messages: + - BashExecSilently: | + SRC="${XPLR_FOCUS_PATH:?}" + TARGET="${XPLR_INPUT_BUFFER:?}" + if mv -v "${SRC:?}" "${TARGET:?}"; then + echo Explore >> "${XPLR_PIPE_MSG_IN:?}" + echo "LogSuccess: $SRC renamed to $TARGET" >> "${XPLR_PIPE_MSG_IN:?}" + echo "FocusPath: $TARGET" >> "${XPLR_PIPE_MSG_IN:?}" + else + echo "LogError: failed to rename $SRC to $TARGET" >> "${XPLR_PIPE_MSG_IN:?}" + fi + - SwitchMode: default + + backspace: + help: clear + messages: + - SetInputBuffer: "" + + esc: + help: cancel + messages: + - SwitchMode: default + + ctrl-c: + help: cancel & quit + messages: + - Terminate + + default: + messages: + - BufferInputFromKey + + default: + name: default + help: null + extra_help: null + key_bindings: + on_key: + '#': + help: null + messages: + - PrintAppStateAndQuit + .: + help: show hidden + messages: + - ToggleNodeFilter: + filter: RelativePathDoesNotStartWith + input: . + case_sensitive: false + - Explore + /: + help: null + messages: + - SwitchMode: search + - SetInputBuffer: '' + - Explore + ':': + help: action + messages: + - SwitchMode: action + '?': + help: global help menu + messages: + - BashExec: | + ${PAGER:-less} "${XPLR_PIPE_GLOBAL_HELP_MENU_OUT}" + G: + help: go to bottom + messages: + - FocusLast + ctrl-c: + help: cancel & quit [q] + messages: + - Terminate + ctrl-f: + help: search [/] + messages: + - SwitchMode: search + - SetInputBuffer: '' + - Explore + d: + help: delete + messages: + - SwitchMode: delete + down: + help: down [j] + messages: + - FocusNext + enter: + help: quit with result + messages: + - PrintResultAndQuit + g: + help: go to + messages: + - SwitchMode: go to + h: + help: null + messages: + - Back + j: + help: null + messages: + - FocusNext + k: + help: null + messages: + - FocusPrevious + l: + help: null + messages: + - Enter + left: + help: back [h] + messages: + - Back + q: + help: null + messages: + - Terminate + r: + help: rename + messages: + - SwitchMode: rename + - BashExecSilently: | + echo "SetInputBuffer: $(basename ${XPLR_FOCUS_PATH})" >> "${XPLR_PIPE_MSG_IN:?}" + + right: + help: enter [l] + messages: + - Enter + space: + help: toggle selection [v] + messages: + - ToggleSelection + - FocusNext + up: + help: up [k] + messages: + - FocusPrevious + v: + help: null + messages: + - ToggleSelection + - FocusNext + on_alphabet: null + on_number: + help: input + messages: + - ResetInputBuffer + - SwitchMode: number + - BufferInputFromKey + on_special_character: null + default: + help: null + messages: + - SwitchMode: default + go to: + name: go to + help: null + extra_help: null + key_bindings: + on_key: + ctrl-c: + help: cancel & quit + messages: + - Terminate + g: + help: top + messages: + - FocusFirst + - SwitchMode: default + x: + help: open in gui + messages: + - BashExecSilently: | + OPENER="$(which xdg-open)" + ${OPENER:-open} "${XPLR_FOCUS_PATH:?}" &> /dev/null + - SwitchMode: default + + on_alphabet: null + on_number: null + on_special_character: null + default: + help: null + messages: + - SwitchMode: default + + number: + name: number + help: null + extra_help: null + key_bindings: + on_key: + backspace: + help: clear + messages: + - ResetInputBuffer + ctrl-c: + help: cancel & quit + messages: + - Terminate + down: + help: to down [j] + messages: + - FocusNextByRelativeIndexFromInput + - SwitchMode: default + enter: + help: to index + messages: + - FocusByIndexFromInput + - SwitchMode: default + j: + help: null + messages: + - FocusNextByRelativeIndexFromInput + - SwitchMode: default + k: + help: null + messages: + - FocusPreviousByRelativeIndexFromInput + - SwitchMode: default + up: + help: to up [k] + messages: + - FocusPreviousByRelativeIndexFromInput + - SwitchMode: default + on_alphabet: null + on_number: + help: input + messages: + - BufferInputFromKey + on_special_character: null + default: + help: null + messages: + - SwitchMode: default + delete: + name: delete + help: null + extra_help: null + key_bindings: + on_key: + d: + help: delete + messages: + - BashExec: | + (while IFS= read -r line; do + if [ -d "$line" ]; then + if rmdir -v "${line:?}"; then + echo "LogSuccess: $line deleted" >> "${XPLR_PIPE_MSG_IN:?}" + else + echo "LogError: failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}" + fi + else + if rm -v "${line:?}"; then + echo "FocusNext" >> "${XPLR_PIPE_MSG_IN:?}" + echo "LogSuccess: $line deleted" >> "${XPLR_PIPE_MSG_IN:?}" + else + echo "LogError: failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}" + fi + fi + done < "${XPLR_PIPE_RESULT_OUT:?}") + echo Explore >> "${XPLR_PIPE_MSG_IN:?}" + read -p "[enter to continue]" + - SwitchMode: default + + D: + help: force delete + messages: + - BashExec: | + (while IFS= read -r line; do + if rm -rfv "${line:?}"; then + echo "FocusNext" >> "${XPLR_PIPE_MSG_IN:?}" + echo "LogSuccess: $line deleted" >> "${XPLR_PIPE_MSG_IN:?}" + else + echo "LogError: failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}" + fi + done < "${XPLR_PIPE_RESULT_OUT:?}") + echo Explore >> "${XPLR_PIPE_MSG_IN:?}" + read -p "[enter to continue]" + - SwitchMode: default + - Explore + + ctrl-c: + help: cancel & quit + messages: + - Terminate + + default: + messages: + - SwitchMode: default + + action: + name: action to + help: null + extra_help: null + key_bindings: + on_number: + help: go to index + messages: + - ResetInputBuffer + - SwitchMode: number + - BufferInputFromKey + + on_key: + "!": + help: shell + messages: + - Call: + command: bash + - Explore + - SwitchMode: default + + c: + help: create + messages: + - SwitchMode: create + + e: + help: open in editor + messages: + - BashExec: | + ${EDITOR:-vi} "${XPLR_FOCUS_PATH:?}" + - SwitchMode: default + + s: + help: selection operations + messages: + - SwitchMode: selection ops + + l: + help: logs + messages: + - BashExec: | + cat "${XPLR_PIPE_LOGS_OUT}" + read -p "[enter to continue]" + - SwitchMode: default + + ctrl-c: + help: cancel & quit [q] + messages: + - Terminate + + q: + messages: + - Terminate + + default: + messages: + - SwitchMode: default + search: + name: search + help: null + extra_help: null + key_bindings: + on_key: + backspace: + help: clear + messages: + - RemoveNodeFilterFromInput: + filter: RelativePathDoesContain + case_sensitive: false + - SetInputBuffer: '' + - Explore + ctrl-c: + help: cancel & quit + messages: + - Terminate + down: + help: down + messages: + - FocusNext + enter: + help: focus + messages: + - RemoveNodeFilterFromInput: + filter: RelativePathDoesContain + case_sensitive: false + - SwitchMode: default + - Explore + esc: + help: cancel + messages: + - RemoveNodeFilterFromInput: + filter: RelativePathDoesContain + case_sensitive: false + - SwitchMode: default + - Explore + left: + help: back + messages: + - RemoveNodeFilterFromInput: + filter: RelativePathDoesContain + case_sensitive: false + - Back + - SetInputBuffer: '' + - Explore + right: + help: enter + messages: + - RemoveNodeFilterFromInput: + filter: RelativePathDoesContain + case_sensitive: false + - Enter + - SetInputBuffer: '' + - Explore + up: + help: up + messages: + - FocusPrevious + on_alphabet: null + on_number: null + on_special_character: null + default: + help: null + messages: + - RemoveNodeFilterFromInput: + filter: RelativePathDoesContain + case_sensitive: false + - BufferInputFromKey + - AddNodeFilterFromInput: + filter: RelativePathDoesContain + case_sensitive: false + - Explore diff --git a/src/default_config.rs b/src/default_config.rs new file mode 100644 index 0000000..2883f20 --- /dev/null +++ b/src/default_config.rs @@ -0,0 +1,8 @@ +pub const DEFAULT_CONFIG_YAML: &str = include_str!("config.yml"); + +use crate::config::Config; +use lazy_static::lazy_static; + +lazy_static! { + pub static ref DEFAULT_CONFIG: Config = serde_yaml::from_str(DEFAULT_CONFIG_YAML).unwrap(); +} diff --git a/src/lib.rs b/src/lib.rs index bd6a0d8..169f511 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ pub mod app; pub mod auto_refresher; pub mod config; +pub mod default_config; pub mod event_reader; pub mod explorer; pub mod input; diff --git a/src/main.rs b/src/main.rs index 2f91b89..fed88c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -95,7 +95,8 @@ fn main() -> Result<()> { if app.version() != &app.config().version { let msg = format!( - "version mismatch, to update your config file to {}, visit {}", + "you can update your config file version from {} to {}, visit {} for more info.", + app.config().version, app.version(), app::UPGRADE_GUIDE_LINK, ); diff --git a/tests/test_version.rs b/tests/test_version.rs index be3a5c4..f5b8955 100644 --- a/tests/test_version.rs +++ b/tests/test_version.rs @@ -1,14 +1,40 @@ use xplr::*; #[test] -fn test_version_incompatibility() { - assert!(app::is_compatible("v0.1.0", "v0.1.2")); - assert!(app::is_compatible("v0.2.0", "v0.2.2")); - assert!(!app::is_compatible("v0.1.0", "v0.2.0")); - assert!(!app::is_compatible("v0.1.0", "v1.1.0")); +fn test_version_compatibility() { + // Config version == app version + assert!(app::is_compatible("v0.1.0", "v0.1.0")); assert!(app::is_compatible("v1.1.0", "v1.1.0")); - assert!(app::is_compatible("v1.1.0", "v1.1.1")); - assert!(app::is_compatible("v1.1.0", "v1.2.1")); - assert!(app::is_compatible("v1.1.0", "v1.2.1")); - assert!(!app::is_compatible("v1.1.0", "v2.0.0")); + + // Config major version < app major version + assert!(!app::is_compatible("v0.1.0", "v0.2.0")); + assert!(!app::is_compatible("v0.2.0", "v0.12.0")); + assert!(!app::is_compatible("v1.0.0", "v2.0.0")); + assert!(!app::is_compatible("v2.0.0", "v12.0.0")); + + // Config minor version < app minor version + assert!(app::is_compatible("v0.0.1", "v0.0.2")); + assert!(app::is_compatible("v0.0.2", "v0.0.12")); + assert!(app::is_compatible("v1.1.0", "v1.2.0")); + assert!(app::is_compatible("v1.2.0", "v1.12.0")); + + // Config patch version < app patch version + assert!(app::is_compatible("v1.1.1", "v1.1.2")); + assert!(app::is_compatible("v1.1.2", "v1.1.12")); + + // Config major version > app major version + assert!(!app::is_compatible("v0.2.0", "v0.1.0")); + assert!(!app::is_compatible("v0.12.0", "v0.2.0")); + assert!(!app::is_compatible("v2.0.0", "v1.0.0")); + assert!(!app::is_compatible("v12.0.0", "v2.0.0")); + + // Config minor version > app minor version + assert!(!app::is_compatible("v0.0.2", "v0.0.1")); + assert!(!app::is_compatible("v0.0.12", "v0.0.2")); + assert!(!app::is_compatible("v1.2.0", "v1.1.0")); + assert!(!app::is_compatible("v1.12.0", "v1.2.0")); + + // Config patch version > app patch version + assert!(app::is_compatible("v1.1.2", "v1.1.1")); + assert!(app::is_compatible("v1.1.12", "v1.1.2")); }