From bc1a1c00a479b623cee12cd121d187d141ee07a9 Mon Sep 17 00:00:00 2001 From: djairoh Date: Thu, 11 May 2023 14:05:59 +0200 Subject: [PATCH] started documentation effort --- src/main.rs | 27 ++++++++++++--- src/structs/cli.rs | 3 +- src/structs/config.rs | 76 +++++++++++++++++++++++++++++++++++++++++-- src/structs/data.rs | 9 +++++ 4 files changed, 106 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index a98a1a4..86d66d2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ +//! This file contains all driver code for the program. use core::time; -use std::process::exit; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use std::thread; use clap::Parser; -use log::{error, info}; +use log::{error, info, warn}; use mpris::PlayerFinder; use structs::cli::Cli; use structs::{config::Config, data::Data}; @@ -19,13 +19,18 @@ mod print_text; mod structs; mod print_players; +/// This function deals with an incoming (USR1) signal. +/// It is hard-coded to play/payse the active player. +/// +/// input: +/// data: Data struct for active configuration. fn handle_signal(data: &Data) { if let Some(p) = &data.current_player { match p.checked_play_pause() { Ok(b) => { match b { true => info!("Player play/paused succesfully!"), - false => info!("Failed to send play/pause signal!"), + false => warn!("Failed to send play/pause signal!"), } }, Err(e) => error!("{e}"), @@ -33,26 +38,35 @@ fn handle_signal(data: &Data) { } } +/// This function contains the default maim loop body of the program. +/// It updates the active player, updates the output strings based on this, and finally formats and outputs these strings to stdout. +/// +/// input: +/// pf: PlayerFinder instance for the program +/// cfg: Configuration of the program +/// data: mutable Data struct, active state of the program +/// r: pre-computed rating strings fn default_loop(pf: &PlayerFinder, cfg: &Config, data: &mut Data, r: &Vec) { update_players(pf, cfg, data); update_message(cfg, data, r); print_text(cfg, data); } +/// Main function. Mostly concerned with initialisation. fn main() { + // logging initialisation std::env::set_var("RUST_LOG", "error"); if let Err(e) = env_logger::init() { error!("{e}"); return } + // Cli flags, Config, Data, and PlayerFinder initialisation let cli = Cli::parse(); match confy::load::("polybar-now-playing", cli.config_file.as_str()) { Ok(cfg) => { let mut data: Data = Data::default(); let rating_strings = cfg.build_rating_strings(); - - let term = Arc::new(AtomicBool::new(false)); let pf: PlayerFinder; match PlayerFinder::new() { @@ -63,11 +77,14 @@ fn main() { }, } + // signal interception initialisation + let term = Arc::new(AtomicBool::new(false)); if let Err(e) = signal_hook::flag::register(signal_hook::consts::SIGUSR1, Arc::clone(&term)) { error!("{e}"); return } + // main body loop loop { thread::sleep(time::Duration::from_millis(cfg.update_delay)); match cli.debug { diff --git a/src/structs/cli.rs b/src/structs/cli.rs index 4e5d44e..4482a00 100644 --- a/src/structs/cli.rs +++ b/src/structs/cli.rs @@ -1,9 +1,10 @@ +//! This file contains structs and functionality that are relevant to the Command Line Interface part of the program. use clap::Parser; /// Program which finds the active mpris player and displays metadata about the playing piece of media. /// /// This program is intended to be used with polybar. -/// as such, most configuration is done through config files. +/// As such, most configuration is done through config files. #[derive(Parser)] pub struct Cli { /// The name of the config file to use. diff --git a/src/structs/config.rs b/src/structs/config.rs index 0b724f6..01f3692 100644 --- a/src/structs/config.rs +++ b/src/structs/config.rs @@ -1,39 +1,76 @@ +//! This file contains structs and functions concerning themselves with the configuration of the program. use std::{collections::HashMap}; use serde::{Serialize, Deserialize}; - +/// This struct represents one metadata field to be rendered, as well as the maximum length of its' output. #[derive(Serialize, Deserialize)] pub struct Field { + /// The name of the metadata field. pub field: String, + /// The maximum length of the metadata field's output. pub num_chars: u8 } impl Field { + /// Create a new field from given values. + /// input: + /// field: name of the field + /// num_chars: maximum length of the field + /// + /// returns: + /// a new Field with the given parameters. fn new(field: String, num_chars: u8) -> Self { Field { field, num_chars } } - pub fn constructor(metadata_field: &str, num_chars: u8) -> Self { - Self::new(metadata_field.to_owned(), num_chars) + + /// Create a new field from given values. + /// input: + /// field: name of the field + /// num_chars: maximum length of the field + /// + /// returns: + /// a new Field with the given parameters. + pub fn constructor(field: &str, num_chars: u8) -> Self { + Self::new(field.to_owned(), num_chars) } } + +/// This struct contains the 3 symbols used to represent a given userRating in a media field. #[derive(Serialize, Deserialize)] pub struct Rating { + /// character for an empty token pub nil: char, + /// character for a half token pub half: char, + /// character for a full token pub full: char } impl Rating { + /// This function repeats a given character n times, interspersing each occurence with a space. + /// It's kinda unwieldy here, but this is the least odd place to put it. + /// + /// input: + /// c: character to repeat + /// n: number of times to repeat the character + /// + /// output: + /// string of the form ' '{n} fn repeat(c: char, n: usize) -> String { let mut s = c.to_string(); s.push(' '); s.repeat(n) } + /// As there are only a small, run-time defined variances on possible ratings (from 5 empty tokens to 5 full ones), + /// this function computes all these strings during initialization. This saves a near negligble amount of operations during run-time. + /// + /// output: + /// Vec of Strings representing all possible rating configurations fn build_rating_strings(&self) -> Vec { let mut out = Vec::new(); out.push(Self::repeat(self.nil, 5)); @@ -51,6 +88,8 @@ impl Rating { } } +/// Defaults for Rating struct. +/// uses UTF-8, ASCII compatible tokens. impl Default for Rating { fn default() -> Self { Self { @@ -61,21 +100,41 @@ impl Default for Rating { } } + +/// This struct contains all possible configuration fields. +/// It should not be used as mutable; all data in this struct should effectively be treated as read-only. #[derive(Serialize, Deserialize)] pub struct Config { + /// Whether to hide the last output if there are currently no accepted players. pub hide_output: bool, + /// Whether to apply 'fuzzy' cutoff to strings exceeding their maximum lenght. pub fuzzy: bool, + /// Whether to render the prefix at all. pub render_prefix: bool, + /// Time in milliseconds to wait between loops of the program. pub update_delay: u64, + /// String to insert between different metadata fields. pub metadata_separator: String, + /// Character to insert between Array values (used when a MetadataVaue is of type Vec (ie multiple artists on one track)). pub array_separator: char, + /// Character to insert when a string is truncated. None implies no cut off character is inserted and the strings are truncated as is. pub break_character: Option, + /// Vec of mpris identities, describing what players are considered acceptable. + /// Prioritised based on vec index (closer to 0 -> higher priority). pub player_priorities: Vec, + /// Characters to use for the xesam:userRating field. + /// If None, default values are used ('-', '/', '+'). pub rating_icons: Option, + /// Vec of Fields. Each field represents one metadata_string to be shown in output, as well as the maximum number of characters for this field. + /// Output is shown based on Vec index (vec[0] first, vec[1] second, etc). pub metadata_fields: Vec, + /// Hashmap which maps Player Identities (strings; key) to prefixes (char; value). + /// If left blank all players will use the default prefix character ('>'). pub player_prefixes: HashMap, } +/// Defaults for the Config struct. +/// This is generated when a non-existant config file is specified in the command line. impl Default for Config { fn default() -> Self { Config { @@ -95,6 +154,10 @@ impl Default for Config { } impl Config { + /// This function returns the index of a given player identity in the player_priorities hashmap. + /// If the given identity is not in the map, the value of i32::MAX is returned instead. + /// + /// TODO: using a HashMap would be more efficient i think. pub fn find_player_priorities_idx(&self, name: &str) -> i32 { match self.player_priorities.iter() .position(|x| x.eq(&name)) { @@ -103,6 +166,7 @@ impl Config { } } + /// This function builds the pre-computed rating strings for a given Rating_icons field. pub fn build_rating_strings(&self) -> Vec { match self.rating_icons.as_ref() { Some(r) => r.build_rating_strings(), @@ -110,6 +174,8 @@ impl Config { } } + /// This function returns the default player_priorities, used when a non-existent config file is requested. + /// The values of these are based on nothing but my own experience; in fact I'm not even sure if the Spotify app's identity is correct. fn default_player_priorities() -> Vec { vec![ "Clementine".to_owned(), @@ -121,6 +187,8 @@ impl Config { ] } + /// This function returns the default metadata fields, used when a non-existent config file is requested. + /// It contains the "title" and "artist" fields, with 40 and 20 maximum characters respectively. fn default_metadata_fields() -> Vec { vec![ Field::constructor("xesam:title", 40), @@ -128,6 +196,8 @@ impl Config { ] } + /// This function returns the default prefixes, used when a non-existent config file is requested. + /// Like the player priorities function, this is mostly just based on my own experience. fn default_player_prefixes() -> HashMap { let mut out: HashMap = HashMap::new(); diff --git a/src/structs/data.rs b/src/structs/data.rs index 1583fdd..d208661 100644 --- a/src/structs/data.rs +++ b/src/structs/data.rs @@ -1,13 +1,22 @@ +//! This file contains structs and functions related to data management within the program. +//! It effectively contains the state of the program. use std::collections::HashMap; use mpris::Player; +/// This struct concerns itself with the current state of the program. pub struct Data { + /// Represents the media player marked as active. + /// Should be None when no (accepted) players are active. pub current_player: Option, + /// HashMap representing the current output strings for each configured field. pub field_text: HashMap, + /// What character to render as prefix. pub prefix: char, } +/// Defaults for Data struct. +/// Generates an empty hashmap, and prefix, as well as None for current_player. impl Default for Data { fn default() -> Self { Self {