From b3365afe5b816dcb10d4a83585afb767ce159f07 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Thu, 1 Jan 2026 18:37:40 +0100 Subject: [PATCH] docs: added documentation, enforced documentation --- src/build.rs | 1 + src/changelog.rs | 18 +++++------------- src/context/api.rs | 36 +++++++++++++++++++++++++++++++++-- src/context/manager.rs | 12 ++++++++++++ src/context/mod.rs | 2 ++ src/deb/mod.rs | 4 ++++ src/lib.rs | 19 ++++++++++++++++++- src/main.rs | 2 +- src/package_info.rs | 43 ++++++++++++++++++++++++++++++------------ src/pull.rs | 5 +++++ 10 files changed, 113 insertions(+), 29 deletions(-) diff --git a/src/build.rs b/src/build.rs index d5889a7..c0733d9 100644 --- a/src/build.rs +++ b/src/build.rs @@ -2,6 +2,7 @@ use std::error::Error; use std::path::Path; use std::process::Command; +/// Build a Debian source package (to a .dsc) pub fn build_source_package(cwd: Option<&Path>) -> Result<(), Box> { let cwd = cwd.unwrap_or_else(|| Path::new(".")); diff --git a/src/changelog.rs b/src/changelog.rs index 5ee419a..ab7a990 100644 --- a/src/changelog.rs +++ b/src/changelog.rs @@ -5,9 +5,7 @@ use std::fs::File; use std::io::{self, BufRead, Read, Write}; use std::path::Path; -/* -* Automatically generate a changelog entry from a commit history and previous changelog -*/ +/// Automatically generate a changelog entry from a commit history and previous changelog pub fn generate_entry( changelog_file: &str, cwd: Option<&Path>, @@ -61,10 +59,8 @@ pub fn generate_entry( Ok(()) } -/* -* Compute the next (most probable) version number of a package, from old version and -* conditions on changes (is ubuntu upload, is a no change rebuild, is a non-maintainer upload) -*/ +/// Compute the next (most probable) version number of a package, from old version and +/// conditions on changes (is ubuntu upload, is a no change rebuild, is a non-maintainer upload) fn compute_new_version( old_version: &str, is_ubuntu: bool, @@ -87,9 +83,7 @@ fn compute_new_version( increment_suffix(old_version, "") } -/* -* Increment a version number by 1, for a given suffix -*/ +/// Increment a version number by 1, for a given suffix fn increment_suffix(version: &str, suffix: &str) -> String { // If suffix is empty, we just look for trailing digits // If suffix is not empty, we look for suffix followed by digits @@ -120,9 +114,7 @@ fn increment_suffix(version: &str, suffix: &str) -> String { } } -/* -* Parse a changelog file first entry header, to obtain (package, version, series) -*/ +/// Parse a changelog file first entry header, to obtain (package, version, series) pub fn parse_changelog_header( path: &Path, ) -> Result<(String, String, String), Box> { diff --git a/src/context/api.rs b/src/context/api.rs index 25d6e22..0403886 100644 --- a/src/context/api.rs +++ b/src/context/api.rs @@ -10,6 +10,7 @@ use super::schroot::SchrootDriver; use super::ssh::SshDriver; use super::unshare::UnshareDriver; +/// A ContextDriver is the interface for the logic happening inside a context pub trait ContextDriver { fn ensure_available(&self, src: &Path, dest_root: &str) -> io::Result; fn retrieve_path(&self, src: &Path, dest: &Path) -> io::Result<()>; @@ -41,34 +42,52 @@ pub trait ContextDriver { #[serde(tag = "type")] #[derive(Default)] pub enum ContextConfig { + /// Local context: actions executed locally #[serde(rename = "local")] #[default] Local, + /// SSH context: actions over an SSH connection #[serde(rename = "ssh")] Ssh { + /// Host for the SSH connection host: String, + /// User for the SSH connection user: Option, + /// TCP port for the SSH connection port: Option, }, + /// Schroot context: using `schroot` #[serde(rename = "schroot")] Schroot { + /// Name of the schroot name: String, + /// Optional parent context for the Schroot context parent: Option, }, + /// Unshare context: chroot with dropped permissions (using `unshare`) #[serde(rename = "unshare")] Unshare { + /// Path to use for chrooting path: String, + /// Optional parent context for the Unshare context parent: Option, }, } +/// A context, allowing to run commands, read and write files, etc pub struct Context { + /// Configuration for the context pub config: ContextConfig, + /// Parent context for the context + /// + /// For example, you could have a chroot context over an ssh connection pub parent: Option>, + /// ContextDriver for the context, implementing the logic for actions driver: Mutex>>, } impl Context { + /// Create a context from configuration pub fn new(config: ContextConfig) -> Self { let parent = match &config { ContextConfig::Schroot { @@ -97,6 +116,7 @@ impl Context { } } + /// Create a context with an explicit parent context pub fn with_parent(config: ContextConfig, parent: Arc) -> Self { Self { config, @@ -105,6 +125,7 @@ impl Context { } } + /// Make a command inside context pub fn command>(&self, program: S) -> ContextCommand<'_> { ContextCommand { context: self, @@ -126,6 +147,7 @@ impl Context { .ensure_available(src, dest_root) } + /// Create a temp directory inside context pub fn create_temp_dir(&self) -> io::Result { self.driver().as_ref().unwrap().create_temp_dir() } @@ -143,18 +165,22 @@ impl Context { self.driver().as_ref().unwrap().list_files(path) } + /// Copy a path inside context pub fn copy_path(&self, src: &Path, dest: &Path) -> io::Result<()> { self.driver().as_ref().unwrap().copy_path(src, dest) } + /// Read a file inside context pub fn read_file(&self, path: &Path) -> io::Result { self.driver().as_ref().unwrap().read_file(path) } + /// Write a file inside context pub fn write_file(&self, path: &Path, content: &str) -> io::Result<()> { self.driver().as_ref().unwrap().write_file(path, content) } + /// Create and obtain a specific driver for the context pub fn driver( &self, ) -> std::sync::MutexGuard<'_, Option>> { @@ -182,6 +208,7 @@ impl Context { driver_lock } + /// Clone a context pub fn clone_raw(&self) -> Self { Self { config: self.config.clone(), @@ -207,12 +234,13 @@ pub struct ContextCommand<'a> { } impl<'a> ContextCommand<'a> { + /// Add an argument to current command pub fn arg>(&mut self, arg: S) -> &mut Self { self.args.push(arg.as_ref().to_string_lossy().to_string()); self } - // Support chaining args + /// Add multiple command arguments pub fn args(&mut self, args: I) -> &mut Self where I: IntoIterator, @@ -224,6 +252,7 @@ impl<'a> ContextCommand<'a> { self } + /// Set environment variable for command pub fn env(&mut self, key: K, val: V) -> &mut Self where K: AsRef, @@ -236,6 +265,7 @@ impl<'a> ContextCommand<'a> { self } + /// Set multiple environment variables for command pub fn envs(&mut self, vars: I) -> &mut Self where I: IntoIterator, @@ -248,11 +278,13 @@ impl<'a> ContextCommand<'a> { self } + /// Set current working directory for command pub fn current_dir>(&mut self, dir: P) -> &mut Self { self.cwd = Some(dir.as_ref().to_string_lossy().to_string()); self } + /// Run command and obtain exit status pub fn status(&mut self) -> io::Result { self.context.driver().as_ref().unwrap().run( &self.program, @@ -262,7 +294,7 @@ impl<'a> ContextCommand<'a> { ) } - // Capture output + /// Run command, capturing output pub fn output(&mut self) -> io::Result { self.context.driver().as_ref().unwrap().run_output( &self.program, diff --git a/src/context/manager.rs b/src/context/manager.rs index 1401bf0..485fb44 100644 --- a/src/context/manager.rs +++ b/src/context/manager.rs @@ -26,6 +26,7 @@ impl Default for Config { } } +/// Helper managing contexts pub struct ContextManager { context: RwLock>, config_path: PathBuf, @@ -67,10 +68,12 @@ impl ContextManager { }) } + /// Obtain current ContextManager configuration pub fn get_config(&self) -> std::sync::RwLockReadGuard<'_, Config> { self.config.read().unwrap() } + /// Make a ContextManager using a specific configuration path pub fn with_path(path: PathBuf) -> Self { let config = Config::default(); Self { @@ -80,6 +83,7 @@ impl ContextManager { } } + /// Save current context configuration to disk pub fn save(&self) -> io::Result<()> { let config = self.config.read().unwrap(); let content = serde_json::to_string_pretty(&*config) @@ -97,6 +101,7 @@ impl ContextManager { Context::new(context_config) } + /// List contexts from configuration pub fn list_contexts(&self) -> Vec { self.config .read() @@ -107,6 +112,7 @@ impl ContextManager { .collect() } + /// Add a context to configuration pub fn add_context(&self, name: &str, config: ContextConfig) -> io::Result<()> { self.config .write() @@ -116,6 +122,7 @@ impl ContextManager { self.save() } + /// Remove context from configuration pub fn remove_context(&self, name: &str) -> io::Result<()> { let mut config = self.config.write().unwrap(); if name == "local" { @@ -137,6 +144,7 @@ impl ContextManager { Ok(()) } + /// Set current context from name (modifying configuration) pub fn set_current(&self, name: &str) -> io::Result<()> { let mut config = self.config.write().unwrap(); if config.contexts.contains_key(name) { @@ -153,14 +161,18 @@ impl ContextManager { } } + /// Set current context, without modifying configuration pub fn set_current_ephemeral(&self, context: Context) { *self.context.write().unwrap() = context.into(); } + /// Obtain current context handle pub fn current(&self) -> Arc { self.context.read().unwrap().clone() } + /// Obtain current context name + /// Will not work for ephemeral context (obtained from config) pub fn current_name(&self) -> String { self.config.read().unwrap().context.clone() } diff --git a/src/context/mod.rs b/src/context/mod.rs index fc9cbea..5800197 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -9,10 +9,12 @@ pub use api::{Context, ContextCommand, ContextConfig}; pub use manager::ContextManager; use std::sync::Arc; +/// Obtain global context manager pub fn manager() -> &'static ContextManager { &manager::MANAGER } +/// Obtain current context pub fn current() -> Arc { manager::MANAGER.current() } diff --git a/src/deb/mod.rs b/src/deb/mod.rs index 6ed442e..45a8c42 100644 --- a/src/deb/mod.rs +++ b/src/deb/mod.rs @@ -6,12 +6,16 @@ use crate::context; use std::error::Error; use std::path::{Path, PathBuf}; +/// Build mode for the binary build #[derive(PartialEq)] pub enum BuildMode { + /// Use `sbuild` for the build, configured in unshare mode Sbuild, + /// Local build, directly on the context Local, } +/// Build package in 'cwd' to a .deb pub fn build_binary_package( arch: Option<&str>, series: Option<&str>, diff --git a/src/lib.rs b/src/lib.rs index 871d56f..e3fbba1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,27 @@ +//! pkh: Debian packaging helper +//! +//! pkh allows working with Debian packages, with multiple actions/submodules +#![deny(missing_docs)] + +/// Build a Debian source package (into a .dsc) pub mod build; +/// Parse or edit a Debian changelog of a source package pub mod changelog; -pub mod context; +/// Build a Debian package into a binary (.deb) pub mod deb; +/// Obtain information about one or multiple packages pub mod package_info; +/// Download a source package locally pub mod pull; +/// Handle context for .deb building: locally, over ssh, in a chroot... +pub mod context; + +/// Optional callback function (taking 4 arguments) +/// - Name of the current main operation (e.g. pulling package) +/// - Name of the current nested operation (e.g. cloning git repo) +/// - Progress, position, index of current operation (e.g. amount of data downloaded) +/// - Total amount for current operation (e.g. size of the file to download) pub type ProgressCallback<'a> = Option<&'a dyn Fn(&str, &str, usize, usize)>; /// Returns the architecture of current CPU, debian-compatible diff --git a/src/main.rs b/src/main.rs index cbd1f41..e7a16ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -103,9 +103,9 @@ fn main() { .map(|s| s.as_str()) .unwrap_or(""); - // Since pull is async, we need to block on it let (pb, progress_callback) = ui::create_progress_bar(&multi); + // Since pull is async, we need to block on it if let Err(e) = rt.block_on(pull( package, version, diff --git a/src/package_info.rs b/src/package_info.rs index d1b2b3c..12c6880 100644 --- a/src/package_info.rs +++ b/src/package_info.rs @@ -93,6 +93,7 @@ fn get_series_from_file(path: &str) -> Result, Box> { parse_series_csv(&content) } +/// Obtain a list of series from a distribution pub async fn get_dist_series(dist: &str) -> Result, Box> { if Path::new(format!("/usr/share/distro-info/{dist}.csv").as_str()).exists() { get_series_from_file(format!("/usr/share/distro-info/{dist}.csv").as_str()) @@ -105,6 +106,7 @@ pub async fn get_dist_series(dist: &str) -> Result, Box> } } +/// Obtain the distribution (eg. debian, ubuntu) from a distribution series (eg. noble, bookworm) pub async fn get_dist_from_series(series: &str) -> Result> { let debian_series = get_dist_series("debian").await?; if debian_series.contains(&series.to_string()) { @@ -117,34 +119,55 @@ pub async fn get_dist_from_series(series: &str) -> Result Err(format!("Unknown series: {}", series).into()) } +/// A File used in a source package #[derive(Debug, Clone)] pub struct FileEntry { + /// Name of the file pub name: String, + /// Size of the file pub size: u64, + /// SHA256 hash for the file pub sha256: String, } +/// A package 'stanza' as found is 'Sources.gz' files, containing basic information about a source package #[derive(Debug)] pub struct PackageStanza { + /// Name of the package pub package: String, + /// Version number for the package pub version: String, + /// Directory field in the stanza pub directory: String, + /// Source package format (e.g. '3.0 (quilt)') pub format: String, + /// Vcs-Git field in the stanza pub vcs_git: Option, + /// Vcs-Browser field in the stanza pub vcs_browser: Option, + /// Files present in the source package pub files: Vec, } +/// Source package information #[derive(Debug)] pub struct PackageInfo { - pub dist: String, - pub series: String, + /// Source 'stanza' for the package, containing basic information pub stanza: PackageStanza, + /// Distribution for the package + pub dist: String, + /// Distribution series for the package + pub series: String, + /// Preferred VCS for the source package + /// + /// Should be Launchpad on Ubuntu, and Salsa on Debian pub preferred_vcs: Option, + /// URL for the files of the source package pub archive_url: String, } impl PackageInfo { + /// Returns true if the package is a Debian native package (no orig) pub fn is_native(&self) -> bool { self.stanza.format.contains("(native)") } @@ -175,9 +198,7 @@ fn get_base_url(dist: &str) -> &str { } } -/* -* Obtain the URL for the 'Release' file of a distribution series -*/ +/// Obtain the URL for the 'Release' file of a distribution series fn get_release_url(base_url: &str, series: &str, pocket: &str) -> String { let pocket_full = if pocket.is_empty() { String::new() @@ -187,9 +208,7 @@ fn get_release_url(base_url: &str, series: &str, pocket: &str) -> String { format!("{base_url}/dists/{series}{pocket_full}/Release") } -/* -* Obtain the components of a distribution series by parsing the 'Release' file -*/ +/// Obtain the components of a distribution series by parsing the 'Release' file async fn get_components( base_url: &str, series: &str, @@ -296,10 +315,8 @@ impl Iterator for DebianSources { } } -/* -* Parse a 'Sources.gz' debian package file data, to look for a target package and -* return the data for that package stanza -*/ +/// Parse a 'Sources.gz' debian package file data, to look for a target package and +/// return the data for that package stanza fn parse_sources( data: &[u8], target_package: &str, @@ -314,6 +331,7 @@ fn parse_sources( })) } +/// Get package information from a package, distribution series, and pocket pub async fn get( package_name: &str, series: &str, @@ -387,6 +405,7 @@ pub async fn get( .into()) } +/// Try to find package information in a distribution, trying all series and pockets pub async fn find_package( package_name: &str, dist: &str, diff --git a/src/pull.rs b/src/pull.rs index 233ed7f..9816b7e 100644 --- a/src/pull.rs +++ b/src/pull.rs @@ -333,6 +333,11 @@ async fn fetch_archive_sources( Ok(()) } +/// Pull a source package locally +/// +/// Will try to find the package information, and use it to download it over prefered way +/// (either git or direct archive download), as well as orig tarball, inside 'package' directory +/// The source will be extracted under 'package/package' pub async fn pull( package: &str, _version: &str,