diff options
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | poezio/args.py | 86 | ||||
-rw-r--r-- | poezio/libpoezio.pyi | 2 | ||||
-rw-r--r-- | poezio/poezio.py | 4 | ||||
-rw-r--r-- | src/args.rs | 97 | ||||
-rw-r--r-- | src/error.rs | 41 | ||||
-rw-r--r-- | src/lib.rs | 13 |
7 files changed, 158 insertions, 88 deletions
@@ -5,7 +5,9 @@ edition = "2021" authors = [ "Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>", "Maxime “pep” Buquet <pep@bouah.net>", + "Mathieu Pasquet <mathieui@mathieui.net>", ] +description = "A console XMPP client" [dependencies] pyo3 = { version = "0.16", features = ["extension-module"] } @@ -14,6 +16,7 @@ chrono = "0.4" ncurses = "5" lazy_static = "1" enum-set = "0.0" +clap = { version = "3.2.17", features = ["derive"] } [lib] crate-type = ["cdylib"] diff --git a/poezio/args.py b/poezio/args.py deleted file mode 100644 index 3907fc88..00000000 --- a/poezio/args.py +++ /dev/null @@ -1,86 +0,0 @@ -""" -Module related to the argument parsing -""" -import pkg_resources -import stat -import sys -from argparse import ArgumentParser, SUPPRESS, Namespace -from pathlib import Path -from shutil import copy2 -from typing import Tuple - -from poezio.version import __version__ -from poezio import xdg - - -def parse_args(CONFIG_PATH: Path): - """ - Parse the arguments from the command line - """ - parser = ArgumentParser('poezio') - parser.add_argument( - "-c", - "--check-config", - dest="check_config", - action='store_true', - help='Check the config file') - parser.add_argument( - "-d", - "--debug", - dest="debug", - help="The file where debug will be written", - metavar="DEBUG_FILE") - parser.add_argument( - "-f", - "--file", - dest="filename", - default=CONFIG_PATH / 'poezio.cfg', - type=Path, - help="The config file you want to use", - metavar="CONFIG_FILE") - parser.add_argument( - '-v', - '--version', - action='version', - version='Poezio v%s' % __version__, - ) - parser.add_argument( - "--custom-version", - dest="custom_version", - help=SUPPRESS, - metavar="VERSION", - default=__version__ - ) - return parser.parse_args() - - -def run_cmdline_args() -> Tuple[Namespace, bool]: - "Parse the command line arguments" - options = parse_args(xdg.CONFIG_HOME) - firstrun = False - - # Copy a default file if none exists - if not options.filename.is_file(): - try: - options.filename.parent.mkdir(parents=True, exist_ok=True) - except OSError as e: - sys.stderr.write( - 'Poezio was unable to create the config directory: %s\n' % e) - sys.exit(1) - default = Path(__file__).parent / '..' / 'data' / 'default_config.cfg' - other = Path( - pkg_resources.resource_filename('poezio', 'default_config.cfg')) - if default.is_file(): - copy2(str(default), str(options.filename)) - elif other.is_file(): - copy2(str(other), str(options.filename)) - - # Inside the nixstore and possibly other distributions, the reference - # file is readonly, so is the copy. - # Make it writable by the user who just created it. - if options.filename.exists(): - options.filename.chmod(options.filename.stat().st_mode - | stat.S_IWUSR) - firstrun = True - - return (options, firstrun) diff --git a/poezio/libpoezio.pyi b/poezio/libpoezio.pyi index e02e0a0f..1212d2c2 100644 --- a/poezio/libpoezio.pyi +++ b/poezio/libpoezio.pyi @@ -1,2 +1,4 @@ +from typing import Any, Dict, List, Tuple def to_curses_attr(fg: int, bg: int, attrs: str) -> int: ... +def run_cmdline_args(argv: List[str]) -> Tuple[Dict[Any, Any], bool]: ... diff --git a/poezio/poezio.py b/poezio/poezio.py index b149abd4..3b83f7c7 100644 --- a/poezio/poezio.py +++ b/poezio/poezio.py @@ -79,8 +79,8 @@ def main(): sys.stdout.write("\x1b]0;poezio\x07") sys.stdout.flush() - from poezio.args import run_cmdline_args - options, firstrun = run_cmdline_args() + from poezio.libpoezio import run_cmdline_args + options, firstrun = run_cmdline_args(sys.argv) from poezio import config config.create_global_config(options.filename) config.setup_logging(options.debug) diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 00000000..cb25c876 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,97 @@ +// Copyright (C) 2022 Maxime “pep” Buquet <pep@bouah.net> +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::error::Error; +use crate::xdg::PROJECT; + +use std::cell::LazyCell; +use std::fs; +use std::io::Write; +use std::path::PathBuf; + +use clap::Parser; +use pyo3::{ + marker::Python, + prelude::{pyclass, pymethods, PyObject, PyResult}, +}; + +const VERSION: &'static str = "v0.14.0"; +const CONFIG_FILE: LazyCell<PathBuf> = + LazyCell::new(|| PROJECT.config_dir().to_path_buf().join("poezio.cfg")); + +#[pyclass] +#[derive(Parser, Debug)] +#[clap(author, version = VERSION, about, long_about = None)] +pub(crate) struct Args { + /// Check the config file + #[pyo3(get)] + #[clap(short, long, action)] + pub(crate) check_config: bool, + + /// The file where debug will be written + #[clap(short, long, value_name = "DEBUG_FILE")] + pub(crate) debug: Option<PathBuf>, + + /// The config file to use + #[clap(short, long = "file", value_name = "CONFIG_FILE", default_value_os_t = CONFIG_FILE.to_path_buf())] + pub(crate) filename: PathBuf, + + /// Custom version passed to Poezio + #[pyo3(get)] + #[clap(long, help = None, default_value_t = VERSION.to_string())] + pub(crate) custom_version: String, +} + +#[pymethods] +impl Args { + #[getter] + fn debug(&self, py: Python<'_>) -> PyResult<PyObject> { + // TODO: Stop importing pathlib all the time + let pathlib = py.import("pathlib")?; + let path: PyObject = pathlib.getattr("Path")?.extract()?; + if let Some(ref debug) = self.debug { + Ok(path.call1(py, (debug.clone(),))?) + } else { + Ok(py.None()) + } + } + + #[getter] + fn filename(&self, py: Python<'_>) -> PyResult<PyObject> { + // TODO: Stop importing pathlib all the time + let pathlib = py.import("pathlib")?; + let path: PyObject = pathlib.getattr("Path")?.extract()?; + Ok(path.call1(py, (self.filename.clone(),))?) + } +} + +/// Parse command line arguments and return whether it's our firstrun alongside Args +pub(crate) fn parse_args(argv: Vec<String>) -> Result<(Args, bool), Error> { + let args = Args::parse_from(argv); + + if args.filename.exists() { + return Ok((args, false)); + }; + + let parent = args + .filename + .parent() + .ok_or(Error::UnableToCreateConfigDir)?; + fs::create_dir_all(parent).map_err(|_| Error::UnableToCreateConfigDir)?; + let default = include_bytes!("../data/default_config.cfg"); + let mut file = fs::File::create(args.filename.clone())?; + file.write_all(default)?; + Ok((args, true)) +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..c1a0cb88 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,41 @@ +// Copyright (C) 2022 Maxime “pep” Buquet <pep@bouah.net> +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Affero General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License +// for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use std::error::Error as StdError; +use std::fmt; +use std::io; + +#[derive(Debug)] +pub(crate) enum Error { + IOError(io::Error), + UnableToCreateConfigDir, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::IOError(e) => write!(f, "io error: {}", e), + Error::UnableToCreateConfigDir => write!(f, "Unable to create config dir"), + } + } +} + +impl StdError for Error {} + +impl From<io::Error> for Error { + fn from(err: io::Error) -> Error { + Error::IOError(err) + } +} @@ -1,6 +1,11 @@ +#![feature(once_cell)] + +mod args; +mod error; mod logger; mod theming; +use crate::args::parse_args; use crate::logger::LogItem; use crate::theming::{curses_attr, parse_attrs}; @@ -8,6 +13,7 @@ use chrono::{Datelike, Timelike}; use pyo3::{ conversion::{IntoPy, ToPyObject}, create_exception, + exceptions::PyIOError, marker::Python, prelude::{pyfunction, pymodule, wrap_pyfunction, PyErr, PyModule, PyObject, PyResult}, types::{PyDateTime, PyDict}, @@ -20,6 +26,7 @@ fn libpoezio(py: Python, m: &PyModule) -> PyResult<()> { m.add("LogParseError", py.get_type::<LogParseError>())?; m.add_function(wrap_pyfunction!(to_curses_attr, m)?)?; m.add_function(wrap_pyfunction!(parse_logs, m)?)?; + m.add_function(wrap_pyfunction!(run_cmdline_args, m)?)?; Ok(()) } @@ -86,3 +93,9 @@ fn parse_logs(py: Python, input: &str) -> PyResult<PyObject> { } Ok(items.into_py(py).to_object(py)) } + +#[pyfunction] +fn run_cmdline_args(py: Python, argv: Vec<String>) -> PyResult<(PyObject, bool)> { + let (args, firstrun) = parse_args(argv).map_err(|err| PyIOError::new_err(err.to_string()))?; + Ok((args.into_py(py), firstrun)) +} |