1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
use clap::Clap;
use derive_more::{Display, Error};
use directories::ProjectDirs;
use lazy_static::lazy_static;
use log::LevelFilter;
use std::path::PathBuf;

lazy_static! {
    static ref DEFAULT_CACHE_DIR: String =
        ProjectDirs::from("com", "chipsenkbeil", "vimwiki_server")
            .map(|dir| dir.cache_dir().to_string_lossy().to_string())
            .unwrap_or_default();
}

#[derive(Clap, Debug)]
#[clap(author, about, version)]
pub struct Config {
    /// Verbose mode (-v, -vv, -vvv, etc.)
    #[clap(short, long, parse(from_occurrences))]
    pub verbose: u8,

    /// Wiki paths to load, monitor, and manipulate
    /// Format is index[:name]:path
    #[clap(long = "wiki", number_of_values = 1)]
    pub wikis: Vec<WikiConfig>,

    /// Mode to run server (http = web; stdin = read input from stdin and reply on stdout)
    #[clap(long, arg_enum, default_value = "http")]
    pub mode: Mode,

    /// Host/IP address of server in http mode
    #[clap(long, default_value = "localhost")]
    pub host: String,

    /// Port of the server in http mode
    #[clap(long, default_value = "8000")]
    pub port: u16,

    /// Extensions for wiki files to parse
    #[clap(long = "ext", number_of_values = 1, default_value = "wiki")]
    pub exts: Vec<String>,

    /// Directory where cache information for use with server will be stored
    #[clap(long, default_value = &DEFAULT_CACHE_DIR)]
    pub cache_dir: PathBuf,
}

impl Config {
    /// Loads the configuration for the server
    pub fn load() -> Self {
        Config::parse()
    }

    /// The level to use for logging throughout the server
    pub fn log_level(&self) -> LevelFilter {
        match self.verbose {
            0 => LevelFilter::Warn,
            1 => LevelFilter::Info,
            2 => LevelFilter::Debug,
            _ => LevelFilter::Trace,
        }
    }
}

/// Represents the mode to run the server (input from stdin or HTTP)
#[derive(Clap, Copy, Clone, Debug, PartialEq, Eq)]
pub enum Mode {
    Stdin,
    Http,
}

/// Represents input information about a wiki
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct WikiConfig {
    pub index: u32,
    pub name: Option<String>,
    pub path: PathBuf,
}

impl std::fmt::Display for WikiConfig {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.index)?;

        if let Some(name) = self.name.as_ref() {
            write!(f, "/{}", name)?;
        }

        write!(f, ":{}", self.path.to_string_lossy())?;

        Ok(())
    }
}

/// Represents parsing errors that can occur for a wiki opt
#[derive(Debug, Display, Error)]
pub enum ParseWikiConfigError {
    InvalidPath,
    InvalidIndex,
    InvalidName,
    InvalidInput,
}

impl std::str::FromStr for WikiConfig {
    type Err = ParseWikiConfigError;

    /// Parse input in form of <index>[:<name>]:path
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = s.split(':').collect();
        let parts_len: usize = parts.len();
        if !(2..=3).contains(&parts_len) {
            return Err(Self::Err::InvalidInput);
        }

        let index = parts[0]
            .parse::<u32>()
            .map_err(|_| Self::Err::InvalidIndex)?;

        let instance = if parts.len() == 2 {
            Self {
                index,
                name: None,
                path: PathBuf::from(parts[1]),
            }
        } else {
            Self {
                index,
                name: Some(parts[1].to_string()),
                path: PathBuf::from(parts[2]),
            }
        };

        // If name is not none, but is empty, return an error
        if instance
            .name
            .as_ref()
            .map(|x| x.is_empty())
            .unwrap_or_default()
        {
            return Err(Self::Err::InvalidName);
        }

        // If path does not exist, return an error
        if !instance.path.exists() {
            return Err(Self::Err::InvalidPath);
        }

        Ok(instance)
    }
}