]> git.nihav.org Git - nihav.git/commitdiff
nihav_hlblocks: split out template formatting into separate module
authorKostya Shishkov <kostya.shishkov@gmail.com>
Sat, 21 Feb 2026 03:57:39 +0000 (04:57 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Sat, 21 Feb 2026 03:57:39 +0000 (04:57 +0100)
nihav-hlblocks/Cargo.toml
nihav-hlblocks/src/imgseqdec.rs
nihav-hlblocks/src/lib.rs
nihav-hlblocks/src/template.rs [new file with mode: 0644]

index ee09e9abddb7ee6d3b083ded58ae4eea5f414cbc..b9317781c0d3b27dab98fbc811c89637a0b618d7 100644 (file)
@@ -14,4 +14,6 @@ path = "../nihav-registry"
 default = []
 
 demuxer = []
-imgseq_dec = []
+imgseq_dec = ["template"]
+
+template = []
index a580655c8fc97fa8527d9970007aaa58671e7189..abab728439f657a4b3f20113db8c779d9c560e73 100644 (file)
@@ -6,93 +6,7 @@ use nihav_core::demuxers::*;
 use std::fs::File;
 use std::io::BufReader;
 use std::io::Read;
-
-struct TemplateName {
-    prefix:         String,
-    pad_size:       usize,
-    suffix:         String,
-    single:         bool,
-}
-
-trait Deescape {
-    fn deescape(&mut self);
-}
-
-impl Deescape for String {
-    fn deescape(&mut self) {
-        while let Some(idx) = self.find("%%") {
-            self.remove(idx + 1);
-        }
-    }
-}
-
-impl TemplateName {
-    fn new(name: &str) -> Self {
-        let mut off = 0;
-        let mut tmpl_start = 0;
-        let mut tmpl_end = 0;
-        let mut pad_size = 0;
-        'parse_loop:
-        while let Some(idx) = name[off..].find('%') {
-            let idx = idx + off;
-            if idx + 1 == name.len() {
-                break;
-            }
-            if name[idx + 1..].starts_with('%') { // escape, skip it
-                off += 1;
-            }
-            if name[idx + 1..].starts_with('0') {
-                if let Some(end_idx) = name[idx + 2..].find('d') {
-                    if let Ok(val) = name[idx + 2..][..end_idx].parse::<usize>() {
-                        if val <= 32 {
-                            tmpl_start = idx;
-                            pad_size = val;
-                            tmpl_end = idx + 2 + end_idx + 1;
-                        }
-                    }
-                    break 'parse_loop;
-                }
-            }
-            if name[idx + 1..].starts_with('d') {
-                tmpl_start = idx;
-                tmpl_end = idx + 2;
-                break;
-            }
-            off += idx;
-        }
-
-        if tmpl_end == 0 {
-            let mut prefix = name.to_owned();
-            prefix.deescape();
-            Self {
-                prefix,
-                pad_size:   0,
-                suffix:     String::new(),
-                single:     true,
-            }
-        } else {
-            let mut prefix = name[..tmpl_start].to_string();
-            prefix.deescape();
-            let mut suffix = name[tmpl_end..].to_string();
-            suffix.deescape();
-            Self {
-                prefix, suffix, pad_size,
-                single: false,
-            }
-        }
-    }
-    fn format<T: Sized+ToString>(&self, id: T) -> String {
-        let mut number = id.to_string();
-        while number.len() < self.pad_size {
-            number.insert(0, '0');
-        }
-        let mut fname = String::with_capacity(self.prefix.len() + number.len() + self.suffix.len());
-        fname.push_str(&self.prefix);
-        fname.push_str(&number);
-        fname.push_str(&self.suffix);
-        fname
-    }
-}
+use crate::template::*;
 
 /// Image sequence demuxer
 ///
@@ -126,7 +40,7 @@ impl ImgSeqDemuxer {
     }
     /// Demuxes a packet.
     pub fn get_frame(&mut self) -> DemuxerResult<NAPacket> {
-        if self.cur_frame > 0 && self.template.single {
+        if self.cur_frame > 0 && self.template.is_single() {
             return Err(DemuxerError::EOF);
         }
         let fname = self.template.format(self.cur_frame);
index e440eb4a44b335333a6d6d20d05248ba68770d0c..1d232b055071f31ec03a355e4c7465058726ff25 100644 (file)
@@ -6,3 +6,6 @@ pub mod demux;
 #[cfg(feature="imgseq_dec")]
 pub mod imgseqdec;
 
+#[cfg(feature="template")]
+pub mod template;
+
diff --git a/nihav-hlblocks/src/template.rs b/nihav-hlblocks/src/template.rs
new file mode 100644 (file)
index 0000000..c9af951
--- /dev/null
@@ -0,0 +1,101 @@
+//! Simple `printf`-like template formatting.
+
+/// Template for formatting names using a provided pattern.
+///
+/// Supported template formats are:
+/// * `%d` for simple number substitution e.g. `out%d.ppm` -> `out9.ppm`, `out10.ppm`
+/// * `%Nd` for space-padded number substitution e.g. `out%4d.ppm` -> `out  99.ppm`
+/// * `%0Nd` for zero-padded number substitution e.g. `out%04d.ppm` -> `out0099.ppm`
+/// * and of course `%%` can be used to output single percent sign.
+pub struct TemplateName {
+    prefix:         String,
+    pad_size:       usize,
+    suffix:         String,
+    single:         bool,
+}
+
+trait Deescape {
+    fn deescape(&mut self);
+}
+
+impl Deescape for String {
+    fn deescape(&mut self) {
+        while let Some(idx) = self.find("%%") {
+            self.remove(idx + 1);
+        }
+    }
+}
+
+impl TemplateName {
+    /// Creates a new instance of `TemplateName` from the provided pattern.
+    pub fn new(name: &str) -> Self {
+        let mut off = 0;
+        let mut tmpl_start = 0;
+        let mut tmpl_end = 0;
+        let mut pad_size = 0;
+        'parse_loop:
+        while let Some(idx) = name[off..].find('%') {
+            let idx = idx + off;
+            if idx + 1 == name.len() {
+                break;
+            }
+            if name[idx + 1..].starts_with('%') { // escape, skip it
+                off += 1;
+            }
+            if name[idx + 1..].starts_with('0') {
+                if let Some(end_idx) = name[idx + 2..].find('d') {
+                    if let Ok(val) = name[idx + 2..][..end_idx].parse::<usize>() {
+                        if val <= 32 {
+                            tmpl_start = idx;
+                            pad_size = val;
+                            tmpl_end = idx + 2 + end_idx + 1;
+                        }
+                    }
+                    break 'parse_loop;
+                }
+            }
+            if name[idx + 1..].starts_with('d') {
+                tmpl_start = idx;
+                tmpl_end = idx + 2;
+                break;
+            }
+            off += idx;
+        }
+
+        if tmpl_end == 0 {
+            let mut prefix = name.to_owned();
+            prefix.deescape();
+            Self {
+                prefix,
+                pad_size:   0,
+                suffix:     String::new(),
+                single:     true,
+            }
+        } else {
+            let mut prefix = name[..tmpl_start].to_string();
+            prefix.deescape();
+            let mut suffix = name[tmpl_end..].to_string();
+            suffix.deescape();
+            Self {
+                prefix, suffix, pad_size,
+                single: false,
+            }
+        }
+    }
+    /// Reports whether template has no substitutions e.g. "output" vs. "output%d"
+    pub fn is_single(&self) -> bool { self.single }
+    /// Returns a string formatted using initial pattern and provided argument.
+    ///
+    /// If the initial pattern had no formatting specifier, this pattern will be returned instead.
+    pub fn format<T: Sized+ToString>(&self, id: T) -> String {
+        let mut number = id.to_string();
+        while number.len() < self.pad_size {
+            number.insert(0, '0');
+        }
+        let mut fname = String::with_capacity(self.prefix.len() + number.len() + self.suffix.len());
+        fname.push_str(&self.prefix);
+        fname.push_str(&number);
+        fname.push_str(&self.suffix);
+        fname
+    }
+}