Repository: orhun/systeroid (language: rust)

Issue:
Add a configuration file
**Is your feature request related to a problem? Please describe.**
Currently there is no way to configure the default settings of `systeroid` other than using the command-line arguments. These settings include kernel section colors, TUI foreground/background colors and default argument values.

**Describe the solution you'd like**
Support configuring the settings with a configuration file. [INI format](https://en.wikipedia.org/wiki/INI_file) could be used.

**Describe alternatives you've considered**
\-

**Additional context**
There is already a `config` module in the core library. This struct type could potentially be used while implementing.



Discussion / hints:

Fix patch:
diff --git a/.dockerignore b/.dockerignore
index a86661c..7b0da2b 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -4,6 +4,7 @@
 /target/
 /.cargo/
 /assets/
+/config/
 
 # Files
 .editorconfig
diff --git a/Cargo.lock b/Cargo.lock
index 9659f4f..b52c6a0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8,6 +8,17 @@ version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 
+[[package]]
+name = "ahash"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "version_check",
+]
+
 [[package]]
 name = "aho-corasick"
 version = "0.7.18"
@@ -186,6 +197,15 @@ dependencies = [
  "once_cell",
 ]
 
+[[package]]
+name = "dirs"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
+dependencies = [
+ "dirs-sys",
+]
+
 [[package]]
 name = "dirs-next"
 version = "2.0.0"
@@ -196,6 +216,17 @@ dependencies = [
  "dirs-sys-next",
 ]
 
+[[package]]
+name = "dirs-sys"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
 [[package]]
 name = "dirs-sys-next"
 version = "0.1.2"
@@ -216,6 +247,12 @@ dependencies = [
  "libloading",
 ]
 
+[[package]]
+name = "dlv-list"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
+
 [[package]]
 name = "downcast-rs"
 version = "1.2.0"
@@ -288,6 +325,15 @@ dependencies = [
  "walkdir",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash",
+]
+
 [[package]]
 name = "hermit-abi"
 version = "0.1.19"
@@ -491,6 +537,16 @@ version = "1.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
 
+[[package]]
+name = "ordered-multimap"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a"
+dependencies = [
+ "dlv-list",
+ "hashbrown",
+]
+
 [[package]]
 name = "parseit"
 version = "0.1.0"
@@ -606,6 +662,16 @@ version = "0.6.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
 
+[[package]]
+name = "rust-ini"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df"
+dependencies = [
+ "cfg-if",
+ "ordered-multimap",
+]
+
 [[package]]
 name = "ryu"
 version = "1.0.10"
@@ -735,10 +801,12 @@ name = "systeroid-core"
 version = "0.1.1"
 dependencies = [
  "colored",
+ "dirs",
  "dirs-next",
  "lazy_static",
  "parseit",
  "rayon",
+ "rust-ini",
  "serde",
  "serde_json",
  "sysctl",
@@ -831,6 +899,12 @@ version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
 
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
 [[package]]
 name = "walkdir"
 version = "2.3.2"
diff --git a/README.md b/README.md
index 4a4f54d..e73f145 100644
--- a/README.md
+++ b/README.md
@@ -93,6 +93,7 @@ Although **systeroid** does not need the parameter section to be specified expli
     - [Changing the colors](#changing-the-colors)
     - [Viewing the parameter documentation](#viewing-the-parameter-documentation)
     - [Setting the refresh rate](#setting-the-refresh-rate)
+- [Configuration](#configuration)
 - [Resources](#resources)
   - [References](#references)
   - [Logo](#logo)
@@ -215,6 +216,7 @@ systeroid [options] [variable[=value] ...] --load[=<file>]
 -P, --no-pager      do not pipe output into a pager
 -v, --verbose       enable verbose logging
     --tui           show terminal user interface
+-c, --config <path> set the path of the configuration file
 -h, --help          display this help and exit (-d)
 -V, --version       output version information and exit
 ```
@@ -416,6 +418,7 @@ systeroid-tui [options]
                     set the foreground color [default: white]
 -n, --no-docs       do not show the kernel documentation
     --deprecated    include deprecated variables while listing
+-c, --config <path> set the path of the configuration file
 -h, --help          display this help and exit
 -V, --version       output version information and exit
 ```
@@ -548,6 +551,28 @@ It is possible to specify a value in milliseconds via `--tick-rate` argument for
 systeroid-tui --tick-rate 500
 ```
 
+## Configuration
+
+**systeroid** can be configured with a configuration file that uses the [INI format](https://en.wikipedia.org/wiki/INI_file). It can be specified via `--config` or `SYSTEROID_CONFIG` environment variable. It can also be placed in one of the following global locations:
+
+- `$HOME/.config/systeroid/systeroid.conf`
+- `$HOME/.systeroid/systeroid.conf`
+
+```sh
+# set the config path via argument
+systeroid --config config/systeroid.conf
+
+# set the config path via env
+SYSTEROID_CONFIG=config/systeroid.conf systeroid
+
+# use a global path
+mkdir -p "$HOME/.config/systeroid"
+cp config/systeroid.conf "$HOME/.config/systeroid"
+systeroid
+```
+
+See the example [systeroid.conf](./config/systeroid.conf) for the configuration options.
+
 ## Resources
 
 ### References
diff --git a/bors.toml b/bors.toml
index d43dbe1..cd06ebd 100644
--- a/bors.toml
+++ b/bors.toml
@@ -18,7 +18,6 @@ up_to_date_approvals = true
 delete_merged_branches = true
 update_base_for_deletes = true
 use_codeowners = true
-use_squash_merge = true
 commit_title = "Merge ${PR_REFS}"
 timeout_sec = 36000
 cut_body_after = "<details>"
diff --git a/config/systeroid.conf b/config/systeroid.conf
new file mode 100644
index 0000000..ff21342
--- /dev/null
+++ b/config/systeroid.conf
@@ -0,0 +1,66 @@
+; systeroid ~ configuration file
+; https://github.com/orhun/systeroid
+;
+; Each line either contains a comment or a command line argument grouped under a section.
+; Run "systeroid --help" or "systeroid-tui --help" to get a list of all possible configuration options.
+
+[general]
+; display the deprecated parameters such as base_reachable_time and retrans_time while listing
+; See https://bugzilla.redhat.com/show_bug.cgi?id=152435
+display_deprecated = false
+; path of the Linux kernel documentation
+; this is distro dependent, systeroid checks the following locations as default:
+; - /usr/share/doc/linux/
+; - /usr/share/doc/linux-doc/
+; - /usr/share/doc/linux-docs/
+; - /usr/share/doc/kernel-doc-*/Documentation/
+kernel_docs = "/usr/share/doc/linux"
+
+[cli]
+; enable verbose logging
+verbose = false
+; ignore unknown variable errors
+ignore_errors = true
+; do not print variable after the value is set
+quiet = false
+; do not pipe output into a pager
+; note that the default pager is less(1) and you can change it by using `PAGER` environment variable
+no_pager = false
+; display type for the parameter, available options are:
+; - default: print the parameter name along with its value
+; - name: print only the name of the parameter
+; - value: print only the value of the parameter
+; - binary: print only the value of the parameter without new line
+display_type = "default"
+; output type for the list, available options are:
+; - default: print the output as is
+; - tree: print the output in a tree-like format
+; - json: print the output in JSON format
+output_type = "default"
+
+[cli.colors]
+; available colors are defined in https://docs.rs/colored/latest/colored/enum.Color.html
+; default color for the symbols
+default_color = "bright black"
+; section colors
+section_abi = "red"
+section_fs = "green"
+section_kernel = "magenta"
+section_net = "blue"
+section_sunrpc = "yellow"
+section_user = "cyan"
+section_vm = "bright red"
+section_unknown = "white"
+
+[tui]
+; tick rate of the terminal
+tick_rate = 250
+; disable showing the parameter documentation
+no_docs = true
+
+[tui.colors]
+; available colors are defined in https://docs.rs/tui/latest/tui/style/enum.Color.html
+; terminal foreground color
+fg_color = "white"
+; terminal background color
+bg_color = "black"
diff --git a/systeroid-core/Cargo.toml b/systeroid-core/Cargo.toml
index c695e55..fec6ee4 100644
--- a/systeroid-core/Cargo.toml
+++ b/systeroid-core/Cargo.toml
@@ -21,3 +21,5 @@ serde = { version = "1.0.140", features = ["derive"] }
 serde_json = "1.0.82"
 dirs-next = "2.0.0"
 parseit = { version = "0.1.0", features = ["gzip"] }
+rust-ini = "0.18.0"
+dirs = "4.0.0"
diff --git a/systeroid-core/src/config.rs b/systeroid-core/src/config.rs
index c74e447..e90cc9e 100644
--- a/systeroid-core/src/config.rs
+++ b/systeroid-core/src/config.rs
@@ -1,9 +1,26 @@
-use crate::sysctl::display::DisplayType;
+use crate::error::Result;
+use crate::sysctl::r#type::{DisplayType, OutputType};
 use crate::sysctl::section::Section;
 use colored::Color;
+use ini::Ini;
 use std::collections::HashMap;
+use std::path::PathBuf;
 
-/* Macro for the concise initialization of HashMap */
+/// Default configuration file.
+pub const DEFAULT_CONFIG: &str = "systeroid.conf";
+
+/// Environment variable for setting the path of the configuration file.
+pub const CONFIG_ENV: &str = "SYSTEROID_CONFIG";
+
+lazy_static! {
+    /// Default locations for the configuration file.
+    pub static ref DEFAULT_CONFIG_PATHS: Vec<Option<PathBuf>> = vec![
+        dirs::config_dir().map(|p| p.join("systeroid").join(DEFAULT_CONFIG)),
+        dirs_next::home_dir().map(|p| p.join(".systeroid").join(DEFAULT_CONFIG)),
+    ];
+}
+
+/// Macro for the concise initialization of HashMap
 macro_rules! map {
     ($( $key: expr => $val: expr ),*) => {{
          let mut map = ::std::collections::HashMap::new();
@@ -12,47 +29,229 @@ macro_rules! map {
     }}
 }
 
+/// Macro for parsing a boolean value from INI format
+macro_rules! parse_ini_flag {
+    ($self: ident, $config: ident, $section: ident, $name: ident) => {
+        if let Some($name) = $section.get(stringify!($name)) {
+            $self.$config.$name = $name == "true";
+        }
+    };
+}
+
 /// Configuration.
 #[derive(Clone, Debug)]
 pub struct Config {
+    /// Whether if the deprecated variables should be included while listing.
+    pub display_deprecated: bool,
+    /// Path of the Linux kernel documentation.
+    pub kernel_docs: Option<PathBuf>,
+    /// CLI configuration.
+    pub cli: CliConfig,
+    /// TUI configuration.
+    pub tui: TuiConfig,
+}
+
+/// CLI configuration.
+#[derive(Clone, Debug)]
+pub struct CliConfig {
     /// Whether if the verbose logging is enabled.
     pub verbose: bool,
     /// Whether if the errors should be ignored.
     pub ignore_errors: bool,
-    /// Whether if the deprecated variables should be included while listing.
-    pub display_deprecated: bool,
     /// Whether if the quiet mode is enabled.
     pub quiet: bool,
     /// Whether if the pager is disabled.
     pub no_pager: bool,
-    /// Sections and the corresponding colors.
-    pub section_colors: HashMap<Section, Color>,
-    /// Default color for the output
-    pub default_color: Color,
     /// Display type of the kernel parameters.
     pub display_type: DisplayType,
+    /// Output type of the application.
+    pub output_type: OutputType,
+    /// Color configuration.
+    pub color: CliColorConfig,
+}
+
+/// CLI color configuration.
+#[derive(Clone, Debug)]
+pub struct CliColorConfig {
+    /// Default color for the output
+    pub default_color: Color,
+    /// Sections and the corresponding colors.
+    pub section_colors: HashMap<Section, Color>,
+}
+
+/// TUI configuration.
+#[derive(Clone, Debug)]
+pub struct TuiConfig {
+    /// Refresh rate of the terminal.
+    pub tick_rate: u64,
+    /// Do not parse/show Linux kernel documentation.
+    pub no_docs: bool,
+    /// Color configuration.
+    pub color: TuiColorConfig,
+}
+
+/// TUI color configuration.
+#[derive(Clone, Debug)]
+pub struct TuiColorConfig {
+    /// Foreground color.
+    pub fg_color: String,
+    /// Background color.
+    pub bg_color: String,
+}
+
+impl Config {
+    /// Parses the configuration file and overrides values.
+    pub fn parse(&mut self, path: Option<PathBuf>) -> Result<()> {
+        let mut config_paths = DEFAULT_CONFIG_PATHS.clone();
+        if path.is_some() {
+            config_paths.insert(0, path);
+        }
+        let mut config_path = None;
+        for path in config_paths.into_iter().flatten() {
+            if path.exists() {
+                config_path = Some(path);
+                break;
+            }
+        }
+        if let Some(path) = config_path {
+            let ini = Ini::load_from_file(path)?;
+            if let Some(general_section) = ini.section(Some("general")) {
+                if let Some(display_deprecated) = general_section.get("display_deprecated") {
+                    self.display_deprecated = display_deprecated == "true";
+                }
+                if let Some(kernel_docs) = general_section.get("kernel_docs") {
+                    self.kernel_docs = Some(PathBuf::from(kernel_docs));
+                }
+            }
+            if let Some(section) = ini.section(Some("cli")) {
+                parse_ini_flag!(self, cli, section, verbose);
+                parse_ini_flag!(self, cli, section, ignore_errors);
+                parse_ini_flag!(self, cli, section, quiet);
+                parse_ini_flag!(self, cli, section, no_pager);
+                if let Some(display_type) = section.get("display_type").map(DisplayType::from) {
+                    self.cli.display_type = display_type;
+                }
+                if let Some(output_type) = section.get("output_type").map(OutputType::from) {
+                    self.cli.output_type = output_type;
+                }
+            }
+            if let Some(section) = ini.section(Some("cli.colors")) {
+                if let Some(default_color) = section
+                    .get("default_color")
+                    .and_then(|v| Color::try_from(v).ok())
+                {
+                    self.cli.color.default_color = default_color;
+                }
+                for (key, value) in section.iter() {
+                    if key.starts_with("section_") {
+                        if let (sysctl_section, Some(color)) = (
+                            Section::from(key.trim_start_matches("section_").to_string()),
+                            Color::try_from(value).ok(),
+                        ) {
+                            self.cli.color.section_colors.insert(sysctl_section, color);
+                        }
+                    }
+                }
+            }
+            if let Some(section) = ini.section(Some("tui")) {
+                if let Some(tick_rate) = section.get("tick_rate").and_then(|v| v.parse().ok()) {
+                    self.tui.tick_rate = tick_rate;
+                }
+                parse_ini_flag!(self, tui, section, no_docs);
+            }
+            if let Some(section) = ini.section(Some("tui.colors")) {
+                if let Some(fg_color) = section.get("fg_color") {
+                    self.tui.color.fg_color = fg_color.to_string();
+                }
+                if let Some(bg_color) = section.get("bg_color") {
+                    self.tui.color.bg_color = bg_color.to_string();
+                }
+            }
+        }
+        Ok(())
+    }
 }
 
 impl Default for Config {
     fn default() -> Self {
         Self {
-            verbose: false,
-            ignore_errors: false,
             display_deprecated: false,
-            quiet: false,
-            no_pager: false,
-            section_colors: map! {
-                Section::Abi => Color::Red,
-                Section::Fs => Color::Green,
-                Section::Kernel => Color::Magenta,
-                Section::Net => Color::Blue,
-                Section::Sunrpc => Color::Yellow,
-                Section::User => Color::Cyan,
-                Section::Vm => Color::BrightRed,
-                Section::Unknown => Color::White
+            kernel_docs: None,
+            cli: CliConfig {
+                verbose: false,
+                ignore_errors: false,
+                quiet: false,
+                no_pager: false,
+                display_type: DisplayType::Default,
+                output_type: OutputType::Default,
+                color: CliColorConfig {
+                    default_color: Color::BrightBlack,
+                    section_colors: map! {
+                        Section::Abi => Color::Red,
+                        Section::Fs => Color::Green,
+                        Section::Kernel => Color::Magenta,
+                        Section::Net => Color::Blue,
+                        Section::Sunrpc => Color::Yellow,
+                        Section::User => Color::Cyan,
+                        Section::Vm => Color::BrightRed,
+                        Section::Unknown => Color::White
+                    },
+                },
+            },
+            tui: TuiConfig {
+                tick_rate: 250,
+                no_docs: false,
+                color: TuiColorConfig {
+                    fg_color: String::from("white"),
+                    bg_color: String::from("black"),
+                },
             },
-            default_color: Color::BrightBlack,
-            display_type: DisplayType::default(),
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn test_config() -> Result<()> {
+        let mut config = Config {
+            display_deprecated: true,
+            ..Default::default()
+        };
+        config.cli.display_type = DisplayType::Value;
+        config.cli.color.default_color = Color::Blue;
+        config.cli.color.section_colors = HashMap::new();
+        config.tui.tick_rate = 3000;
+        config.tui.color.fg_color = String::new();
+        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+            .parent()
+            .expect("parent directory not found")
+            .join("config")
+            .join(DEFAULT_CONFIG);
+        config.parse(Some(path))?;
+        assert_eq!(
+            Config::default().display_deprecated,
+            config.display_deprecated
+        );
+        assert_eq!(
+            Some(PathBuf::from("/usr/share/doc/linux")),
+            config.kernel_docs
+        );
+        assert_eq!(Config::default().cli.display_type, config.cli.display_type);
+        assert_eq!(
+            Config::default().cli.color.default_color,
+            config.cli.color.default_color
+        );
+        assert_eq!(
+            Config::default().cli.color.section_colors,
+            config.cli.color.section_colors
+        );
+        assert_eq!(Config::default().tui.tick_rate, config.tui.tick_rate);
+        assert_eq!(
+            Config::default().tui.color.fg_color,
+            config.tui.color.fg_color
+        );
+        Ok(())
+    }
+}
diff --git a/systeroid-core/src/error.rs b/systeroid-core/src/error.rs
index 9d3f5ed..524865d 100644
--- a/systeroid-core/src/error.rs
+++ b/systeroid-core/src/error.rs
@@ -24,6 +24,9 @@ pub enum Error {
     /// Error that may occur while handling sysctl operations.
     #[error("sysctl error: `{0}`")]
     SysctlError(#[from] sysctl::SysctlError),
+    /// Error that may occur while parsing an INI document.
+    #[error("INI parsing error: `{0}`")]
+    IniError(#[from] ini::Error),
 }
 
 /// Type alias for the standard [`Result`] type.
diff --git a/systeroid-core/src/sysctl/controller.rs b/systeroid-core/src/sysctl/controller.rs
index 35f31a0..0840756 100644
--- a/systeroid-core/src/sysctl/controller.rs
+++ b/systeroid-core/src/sysctl/controller.rs
@@ -10,7 +10,7 @@ use parseit::globwalk;
 use rayon::prelude::*;
 use std::convert::TryFrom;
 use std::env;
-use std::path::{Path, PathBuf};
+use std::path::Path;
 use std::result::Result as StdResult;
 use sysctl::{CtlFlags, CtlIter, Sysctl as SysctlImpl};
 
@@ -47,7 +47,7 @@ impl Sysctl {
                     }
                 }
                 Err(e) => {
-                    if config.verbose {
+                    if config.cli.verbose {
                         eprintln!("{} ({})", e, ctl.name()?);
                     }
                 }
@@ -73,7 +73,7 @@ impl Sysctl {
                     || param.get_absolute_name() == Some(&query.replace('/', "."))
             })
             .collect::<Vec<&Parameter>>();
-        if parameters.is_empty() && !self.config.ignore_errors {
+        if parameters.is_empty() && !self.config.cli.ignore_errors {
             eprintln!(
                 "{}: cannot stat {}{}: No such file or directory",
                 env!("CARGO_PKG_NAME").split('-').collect::<Vec<_>>()[0],
@@ -85,12 +85,8 @@ impl Sysctl {
     }
 
     /// Updates the descriptions of the kernel parameters using the given cached data.
-    pub fn update_docs_from_cache(
-        &mut self,
-        kernel_docs: Option<&PathBuf>,
-        cache: &Cache,
-    ) -> Result<()> {
-        let mut kernel_docs_path = if let Some(path) = kernel_docs {
+    pub fn update_docs_from_cache(&mut self, cache: &Cache) -> Result<()> {
+        let mut kernel_docs_path = if let Some(path) = &self.config.kernel_docs {
             vec![path.to_path_buf()]
         } else {
             Vec::new()
@@ -197,7 +193,7 @@ mod tests {
         );
         assert!(sysctl.get_parameters("---").is_empty());
 
-        sysctl.update_docs_from_cache(None, &Cache::init()?)?;
+        sysctl.update_docs_from_cache(&Cache::init()?)?;
 
         let parameter = sysctl
             .get_parameter("kernel.hostname")
diff --git a/systeroid-core/src/sysctl/display.rs b/systeroid-core/src/sysctl/display.rs
deleted file mode 100644
index a408550..0000000
--- a/systeroid-core/src/sysctl/display.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-/// Possible ways of displaying the kernel parameters.
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub enum DisplayType {
-    /// Print the kernel parameter name along with its value.
-    Default,
-    /// Print only the name of the parameter.
-    Name,
-    /// Print only the value of the parameter.
-    Value,
-    /// Print only the value of the parameter without new line.
-    Binary,
-}
-
-impl Default for DisplayType {
-    fn default() -> Self {
-        Self::Default
-    }
-}
diff --git a/systeroid-core/src/sysctl/mod.rs b/systeroid-core/src/sysctl/mod.rs
index 394e756..04a3339 100644
--- a/systeroid-core/src/sysctl/mod.rs
+++ b/systeroid-core/src/sysctl/mod.rs
@@ -4,8 +4,8 @@ pub mod controller;
 /// Sysctl section.
 pub mod section;
 
-/// Sysctl display options.
-pub mod display;
+/// Sysctl display/output options.
+pub mod r#type;
 
 /// Kernel parameter.
 pub mod parameter;
diff --git a/systeroid-core/src/sysctl/parameter.rs b/systeroid-core/src/sysctl/parameter.rs
index fd6f906..2ddfb2c 100644
--- a/systeroid-core/src/sysctl/parameter.rs
+++ b/systeroid-core/src/sysctl/parameter.rs
@@ -1,6 +1,6 @@
 use crate::config::Config;
 use crate::error::Result;
-use crate::sysctl::display::DisplayType;
+use crate::sysctl::r#type::DisplayType;
 use crate::sysctl::section::Section;
 use colored::*;
 use serde::{Deserialize, Serialize};
@@ -53,9 +53,11 @@ impl Parameter {
     /// Returns the parameter name with corresponding section colors.
     pub fn get_colored_name(&self, config: &Config) -> String {
         let section_color = *(config
+            .cli
+            .color
             .section_colors
             .get(&self.section)
-            .unwrap_or(&config.default_color));
+            .unwrap_or(&config.cli.color.default_color));
         let fields = self.name.split('.').collect::<Vec<&str>>();
         fields
             .iter()
@@ -66,7 +68,7 @@ impl Parameter {
                         result,
                         "{}{}",
                         v.color(section_color),
-                        ".".color(config.default_color)
+                        ".".color(config.cli.color.default_color)
                     );
                 } else {
                     result += v;
@@ -75,14 +77,16 @@ impl Parameter {
             })
     }
 
-    /// Returns the components of the parameter to contruct a [`Tree`].
+    /// Returns the components of the parameter to construct a [`Tree`].
     ///
     /// [`Tree`]: crate::tree::Tree
     pub fn get_tree_components(&self, config: &Config) -> Vec<String> {
         let section_color = *(config
+            .cli
+            .color
             .section_colors
             .get(&self.section)
-            .unwrap_or(&config.default_color));
+            .unwrap_or(&config.cli.color.default_color));
         let mut components = self
             .name
             .split('.')
@@ -95,11 +99,11 @@ impl Parameter {
             .for_each(|(i, component)| {
                 if i != total_components - 1 {
                     *component = component.color(section_color).to_string();
-                } else if config.display_type != DisplayType::Name {
+                } else if config.cli.display_type != DisplayType::Name {
                     *component = format!(
                         "{} {} {}",
                         component,
-                        "=".color(config.default_color),
+                        "=".color(config.cli.color.default_color),
                         self.value.replace('\n', " ").bold()
                     );
                 }
@@ -109,7 +113,7 @@ impl Parameter {
 
     /// Prints the kernel parameter to given output.
     pub fn display_value<Output: Write>(&self, config: &Config, output: &mut Output) -> Result<()> {
-        match config.display_type {
+        match config.cli.display_type {
             DisplayType::Name => {
                 writeln!(output, "{}", self.get_colored_name(config))?;
             }
@@ -125,7 +129,7 @@ impl Parameter {
                         output,
                         "{} {} {}",
                         self.get_colored_name(config),
-                        "=".color(config.default_color),
+                        "=".color(config.cli.color.default_color),
                         value.bold(),
                     )?;
                 }
@@ -187,7 +191,7 @@ impl Parameter {
         let ctl = Ctl::new(&self.name)?;
         let new_value = ctl.set_value_string(new_value)?;
         self.value = new_value;
-        if !config.quiet {
+        if !config.cli.quiet {
             self.display_value(config, output)?;
         }
         Ok(())
@@ -211,10 +215,12 @@ mod tests {
         assert_eq!(Some("test_param"), parameter.get_absolute_name());
 
         let mut config = Config {
-            default_color: Color::White,
             ..Default::default()
         };
+        config.cli.color.default_color = Color::White;
         *(config
+            .cli
+            .color
             .section_colors
             .get_mut(&Section::Kernel)
             .expect("failed to get color")) = Color::Yellow;
@@ -237,7 +243,7 @@ mod tests {
         );
 
         output.clear();
-        config.display_type = DisplayType::Name;
+        config.cli.display_type = DisplayType::Name;
         parameter.display_value(&config, &mut output)?;
         assert_eq!(
             "kernel.fictional.test_param\n",
@@ -245,12 +251,12 @@ mod tests {
         );
 
         output.clear();
-        config.display_type = DisplayType::Value;
+        config.cli.display_type = DisplayType::Value;
         parameter.display_value(&config, &mut output)?;
         assert_eq!("1\n", String::from_utf8_lossy(&output));
 
         output.clear();
-        config.display_type = DisplayType::Binary;
+        config.cli.display_type = DisplayType::Binary;
         parameter.display_value(&config, &mut output)?;
         assert_eq!("1", String::from_utf8_lossy(&output));
 
diff --git a/systeroid-core/src/sysctl/type.rs b/systeroid-core/src/sysctl/type.rs
new file mode 100644
index 0000000..ee70c44
--- /dev/null
+++ b/systeroid-core/src/sysctl/type.rs
@@ -0,0 +1,73 @@
+/// Macro for generating enum type with a Default variant and common implementations.
+macro_rules! gen_type_property {
+    ($name: ident,
+     $($variant: ident,)+
+    ) => {
+        /// Enum containing variants.
+        #[derive(Clone, Copy, Debug, Eq, PartialEq)]
+        pub enum $name {
+            /// Default variant.
+            Default,
+            $(
+                /// Variant.
+                $variant
+            ),+
+        }
+
+        impl<'a> From<&'a str> for $name {
+            fn from(value: &'a str) -> Self {
+                for section in Self::variants() {
+                    if value.to_lowercase() == section.to_string() {
+                        return *section;
+                    }
+                }
+                Self::Default
+            }
+        }
+
+        impl Default for $name {
+            fn default() -> Self {
+                Self::Default
+            }
+        }
+
+        impl ::std::fmt::Display for $name {
+            fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+                write!(f, "{}", format!("{:?}", self).to_lowercase())
+            }
+        }
+
+        impl $name {
+            /// Returns the variants.
+            pub fn variants() -> &'static [Self] {
+                &[Self::Default, $(Self::$variant),+]
+            }
+        }
+    };
+}
+
+gen_type_property!(DisplayType, Name, Value, Binary,);
+gen_type_property!(OutputType, Tree, Json,);
+
+#[cfg(test)]
+mod tests {
+    #[test]
+    fn test_gen_type() {
+        gen_type_property!(TestType, One, Two, Three,);
+        assert_eq!(TestType::Two, TestType::from("two"));
+        assert_eq!(TestType::Two, TestType::from("TwO"));
+        assert_eq!(TestType::Default, TestType::from("tw0"));
+        assert_eq!(TestType::Default, TestType::default());
+        assert_eq!("three", &TestType::Three.to_string());
+        assert_eq!("one", &TestType::One.to_string());
+        assert_eq!(
+            &[
+                TestType::Default,
+                TestType::One,
+                TestType::Two,
+                TestType::Three
+            ],
+            TestType::variants()
+        );
+    }
+}
diff --git a/systeroid-tui/src/args.rs b/systeroid-tui/src/args.rs
index 8b05edc..43341ed 100644
--- a/systeroid-tui/src/args.rs
+++ b/systeroid-tui/src/args.rs
@@ -1,7 +1,7 @@
-use crate::style::Colors;
 use getopts::Options;
 use std::env;
 use std::path::PathBuf;
+use systeroid_core::config::CONFIG_ENV;
 use systeroid_core::sysctl::section::Section;
 use systeroid_core::sysctl::KERNEL_DOCS_ENV;
 
@@ -18,6 +18,8 @@ For more details see {bin}(8)."#;
 /// Command-line arguments.
 #[derive(Debug, Default)]
 pub struct Args {
+    /// Location of the configuration file.
+    pub config: Option<PathBuf>,
     /// Refresh rate of the terminal.
     pub tick_rate: u64,
     /// Path of the Linux kernel documentation.
@@ -26,8 +28,10 @@ pub struct Args {
     pub section: Option<Section>,
     /// Query to search on startup.
     pub search_query: Option<String>,
-    /// Background/foreground colors.
-    pub colors: Colors,
+    /// Foreground color.
+    pub fg_color: String,
+    /// Background color.
+    pub bg_color: String,
     /// Do not parse/show Linux kernel documentation.
     pub no_docs: bool,
     /// Whether if the deprecated variables should be included while listing.
@@ -70,6 +74,12 @@ impl Args {
             "deprecated",
             "include deprecated variables while listing",
         );
+        opts.optopt(
+            "c",
+            "config",
+            "set the path of the configuration file",
+            "<path>",
+        );
         opts.optflag("h", "help", "display this help and exit");
         opts.optflag("V", "version", "output version information and exit");
         opts
@@ -106,14 +116,18 @@ impl Args {
                     .map(PathBuf::from),
                 section: matches.opt_str("s").map(Section::from),
                 search_query: matches.opt_str("q"),
-                colors: Colors::new(
-                    matches.opt_str("bg-color").as_deref().unwrap_or("black"),
-                    matches.opt_str("fg-color").as_deref().unwrap_or("white"),
-                )
-                .map_err(|e| eprintln!("error: `{}`", e))
-                .ok()?,
+                fg_color: matches
+                    .opt_str("fg-color")
+                    .unwrap_or_else(|| String::from("white")),
+                bg_color: matches
+                    .opt_str("bg-color")
+                    .unwrap_or_else(|| String::from("black")),
                 no_docs: matches.opt_present("n"),
                 display_deprecated: matches.opt_present("deprecated"),
+                config: matches
+                    .opt_str("c")
+                    .or_else(|| env::var(CONFIG_ENV).ok())
+                    .map(PathBuf::from),
             })
         }
     }
diff --git a/systeroid-tui/src/lib.rs b/systeroid-tui/src/lib.rs
index a1fa994..3ea6aa4 100644
--- a/systeroid-tui/src/lib.rs
+++ b/systeroid-tui/src/lib.rs
@@ -26,6 +26,7 @@ use crate::args::Args;
 use crate::command::Command;
 use crate::error::Result;
 use crate::event::{Event, EventHandler};
+use crate::style::Colors;
 use systeroid_core::cache::Cache;
 use systeroid_core::config::Config;
 use systeroid_core::sysctl::controller::Sysctl;
@@ -34,18 +35,25 @@ use tui::terminal::Terminal;
 
 /// Runs `systeroid-tui`.
 pub fn run<B: Backend>(args: Args, backend: B) -> Result<()> {
-    let config = Config {
+    let mut config = Config {
         display_deprecated: args.display_deprecated,
+        kernel_docs: args.kernel_docs,
         ..Default::default()
     };
+    config.tui.tick_rate = args.tick_rate;
+    config.tui.no_docs = args.no_docs;
+    config.tui.color.fg_color = args.fg_color;
+    config.tui.color.bg_color = args.bg_color;
+    config.parse(args.config)?;
+    let colors = Colors::new(&config.tui.color.bg_color, &config.tui.color.fg_color)?;
     let mut sysctl = Sysctl::init(config)?;
-    if !args.no_docs {
-        sysctl.update_docs_from_cache(args.kernel_docs.as_ref(), &Cache::init()?)?;
+    if !sysctl.config.tui.no_docs {
+        sysctl.update_docs_from_cache(&Cache::init()?)?;
     }
     let mut terminal = Terminal::new(backend)?;
     terminal.hide_cursor()?;
     terminal.clear()?;
-    let event_handler = EventHandler::new(args.tick_rate);
+    let event_handler = EventHandler::new(sysctl.config.tui.tick_rate);
     let mut app = App::new(&mut sysctl);
     if let Some(section) = args.section {
         app.section_list.state.select(Some(
@@ -63,7 +71,7 @@ pub fn run<B: Backend>(args: Args, backend: B) -> Result<()> {
         app.input = None;
     }
     while app.running {
-        terminal.draw(|frame| ui::render(frame, &mut app, &args.colors))?;
+        terminal.draw(|frame| ui::render(frame, &mut app, &colors))?;
         match event_handler.next()? {
             Event::KeyPress(key) => {
                 let command = Command::parse(key, app.is_input_mode());
@@ -91,6 +99,8 @@ mod tests {
     fn test_systeroid_tui() -> Result<()> {
         let args = Args {
             tick_rate: 1000,
+            fg_color: String::from("white"),
+            bg_color: String::from("black"),
             ..Args::default()
         };
         let backend = TestBackend::new(40, 10);
diff --git a/systeroid-tui/src/style.rs b/systeroid-tui/src/style.rs
index 99f7c14..2c3a9e3 100644
--- a/systeroid-tui/src/style.rs
+++ b/systeroid-tui/src/style.rs
@@ -76,13 +76,13 @@ impl FromStr for Color {
                 "magenta" => TuiColor::Magenta,
                 "cyan" => TuiColor::Cyan,
                 "gray" => TuiColor::Gray,
-                "darkgray" => TuiColor::DarkGray,
-                "lightred" => TuiColor::LightRed,
-                "lightgreen" => TuiColor::LightGreen,
-                "lightyellow" => TuiColor::LightYellow,
-                "lightblue" => TuiColor::LightBlue,
-                "lightmagenta" => TuiColor::LightMagenta,
-                "lightcyan" => TuiColor::LightCyan,
+                "darkgray" | "dark gray" => TuiColor::DarkGray,
+                "lightred" | "light red" => TuiColor::LightRed,
+                "lightgreen" | "light green" => TuiColor::LightGreen,
+                "lightyellow" | "light yellow" => TuiColor::LightYellow,
+                "lightblue" | "light blue" => TuiColor::LightBlue,
+                "lightmagenta" | "light magenta" => TuiColor::LightMagenta,
+                "lightcyan" | "light cyan" => TuiColor::LightCyan,
                 "white" => TuiColor::White,
                 _ => {
                     let rgb = Rgb::from_hex_str(&format!("#{}", s))?;
@@ -102,6 +102,8 @@ mod tests {
         assert_eq!(TuiColor::Gray, Color::from_str("gray")?.get());
         assert_eq!(TuiColor::Black, Color::from_str("black")?.get());
         assert_eq!(TuiColor::Green, Color::from_str("green")?.get());
+        assert_eq!(TuiColor::LightRed, Color::from_str("light red")?.get());
+        assert_eq!(TuiColor::LightBlue, Color::from_str("lightblue")?.get());
         assert_eq!(
             TuiColor::Rgb(152, 157, 69),
             Color::from_str("989D45")?.get()
diff --git a/systeroid/src/app.rs b/systeroid/src/app.rs
index 87dd1fa..7d6475a 100644
--- a/systeroid/src/app.rs
+++ b/systeroid/src/app.rs
@@ -1,4 +1,3 @@
-use crate::output::OutputType;
 use std::env;
 use std::io::{self, BufRead, Write};
 use std::path::PathBuf;
@@ -9,6 +8,7 @@ use systeroid_core::parseit::reader;
 use systeroid_core::parseit::regex::Regex;
 use systeroid_core::sysctl::controller::Sysctl;
 use systeroid_core::sysctl::parameter::Parameter;
+use systeroid_core::sysctl::r#type::OutputType;
 use systeroid_core::sysctl::{DEPRECATED_PARAMS, SYSTEM_PRELOAD};
 use systeroid_core::tree::{Tree, TreeNode};
 
@@ -19,18 +19,12 @@ pub struct App<'a, Output: Write> {
     sysctl: &'a mut Sysctl,
     /// Standard output.
     output: &'a mut Output,
-    /// Output type.
-    output_type: OutputType,
 }
 
 impl<'a, Output: Write> App<'a, Output> {
     /// Constructs a new instance.
-    pub fn new(sysctl: &'a mut Sysctl, output: &'a mut Output, output_type: OutputType) -> Self {
-        Self {
-            sysctl,
-            output,
-            output_type,
-        }
+    pub fn new(sysctl: &'a mut Sysctl, output: &'a mut Output) -> Self {
+        Self { sysctl, output }
     }
 
     /// Prints the given parameters to stdout.
@@ -38,7 +32,7 @@ impl<'a, Output: Write> App<'a, Output> {
     where
         I: Iterator<Item = &'b Parameter>,
     {
-        match self.output_type {
+        match self.sysctl.config.cli.output_type {
             OutputType::Default => {
                 parameters.try_for_each(|parameter| {
                     parameter.display_value(&self.sysctl.config, self.output)
@@ -54,7 +48,8 @@ impl<'a, Output: Write> App<'a, Output> {
                             .map(|v| v.as_ref()),
                     );
                 });
-                Tree::new(root_node.childs).print(self.output, self.sysctl.config.default_color)?;
+                Tree::new(root_node.childs)
+                    .print(self.output, self.sysctl.config.cli.color.default_color)?;
             }
             OutputType::Json => {
                 Parameter::display_bulk_json(parameters.collect(), self.output)?;
@@ -81,7 +76,7 @@ impl<'a, Output: Write> App<'a, Output> {
 
     /// Displays the documentation of a parameter.
     pub fn display_documentation(&mut self, param_name: &str) -> Result<()> {
-        let no_pager = self.sysctl.config.no_pager;
+        let no_pager = self.sysctl.config.cli.no_pager;
         for parameter in self.sysctl.get_parameters(param_name) {
             let mut fallback_to_default = false;
             if no_pager {
@@ -231,13 +226,12 @@ mod tests {
     #[test]
     fn test_app() -> Result<()> {
         let mut output = Vec::new();
-        let mut sysctl = Sysctl::init(Config {
-            no_pager: true,
-            ..Config::default()
-        })?;
-        sysctl.update_docs_from_cache(None, &Cache::init()?)?;
+        let mut config = Config::default();
+        config.cli.no_pager = true;
+        let mut sysctl = Sysctl::init(config)?;
+        sysctl.update_docs_from_cache(&Cache::init()?)?;
 
-        let mut app = App::new(&mut sysctl, &mut output, OutputType::Default);
+        let mut app = App::new(&mut sysctl, &mut output);
 
         app.display_parameters(Regex::new("kernel|vm").ok(), false)?;
         let result = String::from_utf8_lossy(app.output);
@@ -245,7 +239,7 @@ mod tests {
         assert!(result.contains("kernel.version ="));
         app.output.clear();
 
-        app.output_type = OutputType::Tree;
+        app.sysctl.config.cli.output_type = OutputType::Tree;
         app.display_parameters(None, false)?;
         assert!(String::from_utf8_lossy(app.output).contains("─ osrelease ="));
         app.output.clear();
@@ -255,7 +249,7 @@ mod tests {
         app.output.clear();
 
         let param_name = String::from("kernel.version");
-        app.output_type = OutputType::Default;
+        app.sysctl.config.cli.output_type = OutputType::Default;
         app.process_parameter(param_name.clone(), true, false)?;
         let result = String::from_utf8_lossy(app.output);
         assert_eq!(1, result.lines().count());
@@ -263,7 +257,7 @@ mod tests {
         app.output.clear();
 
         let param_name = String::from("kernel.version");
-        app.output_type = OutputType::Json;
+        app.sysctl.config.cli.output_type = OutputType::Json;
         app.process_parameter(param_name.clone(), true, false)?;
         let result = String::from_utf8_lossy(app.output);
         assert!(result.contains("\"section\":\"kernel\""));
diff --git a/systeroid/src/args.rs b/systeroid/src/args.rs
index 2e08c56..57ddccb 100644
--- a/systeroid/src/args.rs
+++ b/systeroid/src/args.rs
@@ -1,9 +1,10 @@
-use crate::output::OutputType;
 use getopts::Options;
 use std::env;
 use std::path::PathBuf;
+use systeroid_core::config::CONFIG_ENV;
 use systeroid_core::parseit::regex::Regex;
-use systeroid_core::sysctl::display::DisplayType;
+use systeroid_core::sysctl::r#type::DisplayType;
+use systeroid_core::sysctl::r#type::OutputType;
 use systeroid_core::sysctl::{DEFAULT_PRELOAD, KERNEL_DOCS_ENV};
 
 /// Help message for the arguments.
@@ -19,6 +20,8 @@ For more details see {bin}(8)."#;
 /// Command-line arguments.
 #[derive(Debug, Default)]
 pub struct Args {
+    /// Location of the configuration file.
+    pub config: Option<PathBuf>,
     /// Whether if the verbose logging is enabled.
     pub verbose: bool,
     /// Whether if the quiet mode is enabled.
@@ -97,6 +100,12 @@ impl Args {
         opts.optflag("P", "no-pager", "do not pipe output into a pager");
         opts.optflag("v", "verbose", "enable verbose logging");
         opts.optflag("", "tui", "show terminal user interface");
+        opts.optopt(
+            "c",
+            "config",
+            "set the path of the configuration file",
+            "<path>",
+        );
         opts.optflag("h", "help", "display this help and exit (-d)");
         opts.optflag("V", "version", "output version information and exit");
         opts
@@ -195,6 +204,10 @@ impl Args {
                 explain: matches.opt_present("E"),
                 output_type,
                 show_tui: matches.opt_present("tui"),
+                config: matches
+                    .opt_str("c")
+                    .or_else(|| env::var(CONFIG_ENV).ok())
+                    .map(PathBuf::from),
                 values: matches.free,
             })
         }
diff --git a/systeroid/src/lib.rs b/systeroid/src/lib.rs
index 674e912..fd6f220 100644
--- a/systeroid/src/lib.rs
+++ b/systeroid/src/lib.rs
@@ -6,8 +6,6 @@
 pub mod app;
 /// Command-line argument parser.
 pub mod args;
-/// Application output types.
-pub mod output;
 
 use crate::app::App;
 use crate::args::Args;
@@ -20,20 +18,23 @@ use systeroid_core::sysctl::controller::Sysctl;
 
 /// Runs `systeroid`.
 pub fn run<Output: Write>(args: Args, output: &mut Output) -> Result<()> {
-    let config = Config {
-        verbose: args.verbose,
-        ignore_errors: args.ignore_errors,
+    let mut config = Config {
         display_deprecated: args.display_deprecated,
-        quiet: args.quiet,
-        no_pager: args.no_pager,
-        display_type: args.display_type,
+        kernel_docs: args.kernel_docs,
         ..Default::default()
     };
+    config.cli.verbose = args.verbose;
+    config.cli.ignore_errors = args.ignore_errors;
+    config.cli.quiet = args.quiet;
+    config.cli.no_pager = args.no_pager;
+    config.cli.display_type = args.display_type;
+    config.cli.output_type = args.output_type;
+    config.parse(args.config)?;
     let mut sysctl = Sysctl::init(config)?;
     if args.explain {
-        sysctl.update_docs_from_cache(args.kernel_docs.as_ref(), &Cache::init()?)?;
+        sysctl.update_docs_from_cache(&Cache::init()?)?;
     }
-    let mut app = App::new(&mut sysctl, output, args.output_type);
+    let mut app = App::new(&mut sysctl, output);
 
     if args.preload_system_files {
         app.preload_from_system()?;
diff --git a/systeroid/src/main.rs b/systeroid/src/main.rs
index 49cd4c7..4623c52 100644
--- a/systeroid/src/main.rs
+++ b/systeroid/src/main.rs
@@ -8,6 +8,9 @@ fn main() {
         if args.show_tui {
             let bin = format!("{}-tui", env!("CARGO_PKG_NAME"));
             let mut command = Command::new(&bin);
+            if let Some(config) = args.config {
+                command.arg("--config").arg(config);
+            }
             if let Some(kernel_docs) = args.kernel_docs {
                 command.arg("--docs").arg(kernel_docs);
             }
diff --git a/systeroid/src/output.rs b/systeroid/src/output.rs
deleted file mode 100644
index 992b0e5..0000000
--- a/systeroid/src/output.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-/// Possible output types for the [`App`].
-///
-/// [`App`]: crate::app::App
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub enum OutputType {
-    /// Print the output as is.
-    Default,
-    /// Print the output in a tree-like format.
-    Tree,
-    /// Print the output in JSON format.
-    Json,
-}
-
-impl Default for OutputType {
-    fn default() -> Self {
-        Self::Default
-    }
-}


Test patch:
diff --git a/systeroid/tests/live_test.rs b/systeroid/tests/live_test.rs
index 301bcca..c84d9f7 100644
--- a/systeroid/tests/live_test.rs
+++ b/systeroid/tests/live_test.rs
@@ -1,7 +1,6 @@
-use {
-    systeroid::args::Args, systeroid_core::error::Result,
-    systeroid_core::sysctl::display::DisplayType,
-};
+use systeroid::args::Args;
+use systeroid_core::error::Result;
+use systeroid_core::sysctl::r#type::DisplayType;
 
 #[cfg_attr(not(feature = "live-tests"), ignore)]
 #[test]