Thomas 1 year ago
parent
commit
46f19b16f5
6 changed files with 590 additions and 147 deletions
  1. 207 0
      Cargo.lock
  2. 5 1
      Cargo.toml
  3. 230 132
      src/commands/dorado.rs
  4. 121 0
      src/commands/dorado_pty.rs
  5. 1 0
      src/commands/mod.rs
  6. 26 14
      src/lib.rs

+ 207 - 0
Cargo.lock

@@ -845,6 +845,15 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
 
+[[package]]
+name = "conpty"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b72b06487a0d4683349ad74d62e87ad639b09667082b3c495c5b6bab7d84b3da"
+dependencies = [
+ "windows",
+]
+
 [[package]]
 name = "console"
 version = "0.15.8"
@@ -997,6 +1006,18 @@ dependencies = [
  "syn 2.0.71",
 ]
 
+[[package]]
+name = "duct"
+version = "0.13.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4ab5718d1224b63252cd0c6f74f6480f9ffeb117438a2e0f5cf6d9a4798929c"
+dependencies = [
+ "libc",
+ "once_cell",
+ "os_pipe",
+ "shared_child",
+]
+
 [[package]]
 name = "easy-cast"
 version = "0.5.2"
@@ -1060,6 +1081,28 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 
+[[package]]
+name = "errno"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "expectrl"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ede784925953fcab9a3351d5009bcb8d2b0c13e940924c88087e8e2ce0c4717a"
+dependencies = [
+ "conpty",
+ "nix 0.26.4",
+ "ptyprocess",
+ "regex",
+]
+
 [[package]]
 name = "flatbuffers"
 version = "23.5.26"
@@ -1531,6 +1574,12 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee"
 
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+
 [[package]]
 name = "locale_config"
 version = "0.3.0"
@@ -1608,6 +1657,15 @@ version = "2.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
 
+[[package]]
+name = "memoffset"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "minimal-lexical"
 version = "0.2.1"
@@ -1643,6 +1701,31 @@ dependencies = [
  "rustc_version 0.1.7",
 ]
 
+[[package]]
+name = "nix"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if",
+ "libc",
+ "memoffset",
+ "pin-utils",
+]
+
+[[package]]
+name = "nix"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+dependencies = [
+ "bitflags 2.6.0",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
 [[package]]
 name = "nom"
 version = "7.1.3"
@@ -1863,6 +1946,16 @@ dependencies = [
  "vcpkg",
 ]
 
+[[package]]
+name = "os_pipe"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "overload"
 version = "0.1.1"
@@ -1915,16 +2008,21 @@ dependencies = [
  "byte-unit",
  "chrono",
  "csv",
+ "duct",
  "env_logger 0.11.3",
+ "expectrl",
  "glob",
  "locale_config",
  "log",
  "logtest",
+ "nix 0.29.0",
  "noodles-csi",
  "num-format",
  "pandora_lib_bindings",
  "pandora_lib_pileup",
  "pandora_lib_pod5",
+ "pty-process",
+ "ptyprocess",
  "regex",
  "serde",
  "test-log",
@@ -2064,6 +2162,25 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "pty-process"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8749b545e244c90bf74a5767764cc2194f1888bb42f84015486a64c82bea5cc0"
+dependencies = [
+ "libc",
+ "rustix",
+]
+
+[[package]]
+name = "ptyprocess"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e05aef7befb11a210468a2d77d978dde2c6381a0381e33beb575e91f57fe8cf"
+dependencies = [
+ "nix 0.26.4",
+]
+
 [[package]]
 name = "quick-error"
 version = "1.2.3"
@@ -2294,6 +2411,20 @@ dependencies = [
  "semver 1.0.23",
 ]
 
+[[package]]
+name = "rustix"
+version = "0.38.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
+dependencies = [
+ "bitflags 2.6.0",
+ "errno",
+ "itoa",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "rustversion"
 version = "1.0.17"
@@ -2370,6 +2501,16 @@ dependencies = [
  "lazy_static",
 ]
 
+[[package]]
+name = "shared_child"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
 [[package]]
 name = "shlex"
 version = "1.3.0"
@@ -2864,6 +3005,15 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
+[[package]]
+name = "windows"
+version = "0.44.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
 [[package]]
 name = "windows-core"
 version = "0.52.0"
@@ -2891,6 +3041,21 @@ dependencies = [
  "windows-targets 0.52.6",
 ]
 
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
 [[package]]
 name = "windows-targets"
 version = "0.48.5"
@@ -2922,6 +3087,12 @@ dependencies = [
  "windows_x86_64_msvc 0.52.6",
 ]
 
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
 [[package]]
 name = "windows_aarch64_gnullvm"
 version = "0.48.5"
@@ -2934,6 +3105,12 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.48.5"
@@ -2946,6 +3123,12 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.48.5"
@@ -2964,6 +3147,12 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.48.5"
@@ -2976,6 +3165,12 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.48.5"
@@ -2988,6 +3183,12 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
 
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.48.5"
@@ -3000,6 +3201,12 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
 
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.48.5"

+ 5 - 1
Cargo.toml

@@ -23,4 +23,8 @@ noodles-csi = "0.37.0"
 num-format = "0.4.4"
 locale_config = "0.3.0"
 byte-unit = "5.1.4"
-
+pty-process = "0.4.0"
+nix = { version = "0.29.0", features = ["term", "process" ] }
+expectrl = "0.7.1"
+ptyprocess = "0.4.1"
+duct = "0.13.7"

+ 230 - 132
src/commands/dorado.rs

@@ -1,17 +1,23 @@
 use std::{
     fs,
-    io::{BufRead, BufReader},
-    process::Command,
-    time::SystemTime,
+    io::{BufRead, BufReader, Read, Write},
+    process::{ChildStderr, Command, Stdio},
+    sync::{
+        mpsc::{self, Sender},
+        Arc, Mutex,
+    },
+    thread,
+    time::{Duration, SystemTime},
 };
 
 use log::info;
+use pty_process::blocking::Pty;
 
 pub trait Run {
-    fn run(self);
+    fn run(self) -> anyhow::Result<()>;
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct DoradoConfig {
     pub ref_fa: String,
     pub ref_mmi: String,
@@ -29,161 +35,251 @@ pub struct Dorado {
 }
 
 impl Dorado {
-    pub fn init(config: DoradoConfig) -> Self {
-        Self {
+    pub fn init(config: DoradoConfig) -> anyhow::Result<Self> {
+        Ok(Self {
             config,
             start_time: SystemTime::now(),
             end_time: SystemTime::now(),
             is_done: false,
             log: Vec::new(),
-        }
+        })
     }
-}
-
-impl Run for Dorado {
-    fn run(mut self) {
-        // Start timer
-        let start_time = std::time::SystemTime::now();
-        self.start_time = start_time;
-
-        info!("Running Dorado with params: {:#?}", self.config);
-
-        let name = &self.config.name;
-        let time = &self.config.time;
-        let pod_dir = &self.config.pod_dir;
-
-        let ref_fa = &self.config.ref_fa;
-        let ref_mmi = &self.config.ref_mmi;
-
-        let data_dir = "/data/longreads_basic_pipe";
-        let dorado_bin = "/data/tools/dorado-0.7.2-linux-x64/bin/dorado";
-        let case_dir = format!("{}/{}", data_dir, name);
-        let time_dir = format!("{}/{}", case_dir, time);
-        let bam = format!("{}/{}_{}_hs1.bam", time_dir, name, time);
-
+    fn create_reference_mmi(&self, ref_mmi: &str, ref_fa: &str) -> anyhow::Result<()> {
         if !std::path::Path::new(ref_mmi).exists() {
             Command::new("minimap2")
                 .args(["-x", "map-ont", "-d", ref_mmi, ref_fa])
-                .output()
-                .expect("Failed to execute minimap2");
+                .output()?;
         }
+        Ok(())
+    }
 
-        if !std::path::Path::new(&case_dir).exists() {
-            fs::create_dir(&case_dir).expect("Failed to create case directory");
+    fn create_directories(&self, case_dir: &str, time_dir: &str) -> anyhow::Result<()> {
+        if !std::path::Path::new(case_dir).exists() {
+            fs::create_dir(case_dir)?;
         }
-
-        if !std::path::Path::new(&time_dir).exists() {
-            fs::create_dir(&time_dir).expect("Failed to create time directory");
+        if !std::path::Path::new(time_dir).exists() {
+            fs::create_dir(time_dir)?;
         }
+        Ok(())
+    }
 
-        println!("Reading {} pod5 from: {}", time, pod_dir);
+    fn run_dorado_and_samtools(
+        &mut self,
+        dorado_bin: &str,
+        pod_dir: &str,
+        ref_mmi: &str,
+        bam: &str,
+    ) -> anyhow::Result<()> {
+        let (sender, receiver) = mpsc::channel();
 
-        if !std::path::Path::new(&bam).exists() {
-            let dorado_output = Command::new(dorado_bin)
-                .args([
-                    "basecaller",
-                    "sup,5mC_5hmC",
-                    pod_dir,
-                    "--trim",
-                    "all",
-                    "--reference",
-                    ref_mmi,
-                ])
-                .stdout(std::process::Stdio::piped())
-                .stderr(std::process::Stdio::piped())
-                .spawn()
-                .expect("Failed to execute Dorado");
-
-            if let Some(stderr) = dorado_output.stderr {
-                print_stderr(stderr, &mut self.log);
+        let mut dorado = Command::new(dorado_bin)
+            .args([
+                "basecaller",
+                "sup,5mC_5hmC",
+                pod_dir,
+                "--trim",
+                "all",
+                "--reference",
+                ref_mmi,
+            ])
+            .stdout(Stdio::piped())
+            .stderr(Stdio::piped())
+            .spawn()?;
+
+        // let stderr = dorado.stderr.take().expect("Failed to open stderr");
+
+        let dorado_stderr = Arc::new(Mutex::new(dorado.stderr.take().unwrap()));
+        let dorado_sender = sender.clone();
+        let dorado_stderr_clone = Arc::clone(&dorado_stderr);
+        thread::spawn(move || {
+            let _ = Self::print_stderr_live(dorado_stderr_clone, dorado_sender);
+        });
+
+        let mut view = Command::new("samtools")
+            .args(["view", "-h", "-@ 20", "-b", "/dev/stdin"])
+            .stdin(Stdio::from(dorado.stdout.take().unwrap()))
+            .stdout(Stdio::piped())
+            .stderr(Stdio::piped())
+            .spawn()?;
+
+        let view_stderr = Arc::new(Mutex::new(view.stderr.take().unwrap()));
+        let view_sender = sender.clone();
+        let view_stderr_clone = Arc::clone(&view_stderr);
+        thread::spawn(move || {
+            if let Err(e) = Self::print_stderr_live(view_stderr_clone, view_sender) {
+                eprintln!("Error in view stderr thread: {}", e);
             }
+        });
 
-            let samtools_view_output = Command::new("samtools")
-                .args(["view", "-h", "-@ 20", "-b", "/dev/stdin"])
-                .stdin(dorado_output.stdout.unwrap())
-                .stdout(std::process::Stdio::piped())
-                .stderr(std::process::Stdio::piped())
-                .spawn()
-                .expect("Failed to execute samtools view");
+        let mut sort = Command::new("samtools")
+            .args(["sort", "-@ 30", "/dev/stdin", "-o", bam])
+            .stdin(Stdio::from(view.stdout.take().unwrap()))
+            .stdout(Stdio::piped())
+            .stderr(Stdio::piped())
+            .spawn()?;
 
-            if let Some(stderr) = samtools_view_output.stderr {
-                print_stderr(stderr, &mut self.log);
+        let sort_stderr = Arc::new(Mutex::new(sort.stderr.take().unwrap()));
+        let sort_sender = sender.clone();
+        let sort_stderr_clone = Arc::clone(&sort_stderr);
+        thread::spawn(move || {
+            if let Err(e) = Self::print_stderr_live(sort_stderr_clone, sort_sender) {
+                eprintln!("Error in sort stderr thread: {}", e);
             }
+        });
+
+        // Wait for all commands to finish
+        dorado.wait()?;
+        view.wait()?;
+        sort.wait()?;
+
+        // Run samtools index
+        let index_output = Command::new("samtools")
+            .args(["index", "-@ 150", bam])
+            .output()?;
 
-            Command::new("samtools")
-                .args(["sort", "-@ 30", "/dev/stdin", "-o", &bam])
-                .stdin(samtools_view_output.stdout.unwrap())
-                .output()
-                .expect("Failed to execute samtools sort");
-
-            // Create a buffer to store the output
-            // let stderr = dorado_output.stderr.take().expect("Failed to open stderr");
-            // let mut reader = std::io::BufReader::new(stderr);
-            // let mut output = String::new();
-            //
-            // loop {
-            //     // Read the output of the command
-            //     match reader.read_line(&mut output) {
-            //         Ok(0) => break, // End of output
-            //         Ok(_) => {
-            //             // Print or process the output here
-            //             print!("{}", output);
-            //             io::stderr().flush().unwrap();
-            //             // print!("{}[2J", 27 as char);
-            //             print!("\x1B[2J\x1B[1;1H");
-            //
-            //             output.clear();
-            //         }
-            //         Err(err) => {
-            //             eprintln!("Error reading from stdout: {}", err);
-            //             break;
-            //         }
-            //     }
-            //
-            //     // Optionally, you can add a delay to control refresh rate
-            //     std::thread::sleep(Duration::from_millis(100));
-            // }
-
-            Command::new("samtools")
-                .args(["index", "-@ 150", &bam])
-                .output()
-                .expect("Failed to execute samtools index");
+        self.print_stderr(BufReader::new(index_output.stderr.as_slice()), sender)?;
+
+        // Collect all logs
+        for log in receiver {
+            self.log.push(log);
         }
 
+        Ok(())
+    }
+
+    fn run_cramino(&self, bam: &str, time_dir: &str, name: &str, time: &str) -> anyhow::Result<()> {
         let cramino_out = format!("{}/{}_{}_hs1_cramino.txt", time_dir, name, time);
         if !std::path::Path::new(&cramino_out).exists() {
             println!("[pipe] Quality control of BAM: {}", bam);
-
             Command::new("cramino")
-                .args(["-t", "150", "--hist", "--checksum", "--karyotype", &bam])
-                .output()
-                .expect("Failed to execute cramino");
+                .args(["-t", "150", "--hist", "--checksum", "--karyotype", bam])
+                .output()?;
         }
+        Ok(())
+    }
 
+    fn run_modkit(&self, bam: &str, time_dir: &str, name: &str, time: &str) -> anyhow::Result<()> {
         let mod_summary = format!("{}/{}_{}_5mC_5hmC_summary.txt", time_dir, name, time);
         if !std::path::Path::new(&mod_summary).exists() {
             Command::new("modkit")
-                .args(["summary", "-t", "50", &bam])
-                .output()
-                .expect("Failed to execute modkit summary");
+                .args(["summary", "-t", "50", bam])
+                .output()?;
         }
+        Ok(())
+    }
 
+    fn create_fastq(
+        &self,
+        bam: &str,
+        case_dir: &str,
+        name: &str,
+        time: &str,
+    ) -> anyhow::Result<()> {
         let fastq = format!("{}/{}/{}/{}_{}.fastq.gz", case_dir, name, time, name, time);
         if !std::path::Path::new(&fastq).exists() {
-            Command::new("samtools")
-                .args(["fastq", "-@ 150", &bam])
-                .stdout(std::process::Stdio::piped())
-                .spawn()
-                .expect("Failed to execute samtools fastq");
+            let samtools_fastq = Command::new("samtools")
+                .args(["fastq", "-@ 150", bam])
+                .stdout(Stdio::piped())
+                .spawn()?;
 
             Command::new("crabz")
                 .args(["-f", "bgzf", "-", "-o", &fastq])
-                .stdin(std::process::Stdio::piped())
-                .output()
-                .expect("Failed to execute crabz");
+                .stdin(samtools_fastq.stdout.unwrap())
+                .output()?;
         }
+        Ok(())
+    }
+
+    fn print_stderr<R: BufRead>(
+        &mut self,
+        reader: R,
+        sender: Sender<String>,
+    ) -> anyhow::Result<()> {
+        for line in reader.lines() {
+            let line = line?;
+            eprintln!("{}", line);
+            sender.send(line.clone()).unwrap();
+            self.log.push(line);
+        }
+        Ok(())
+    }
+
+    fn print_stderr_live(
+        stderr: Arc<Mutex<ChildStderr>>,
+        sender: Sender<String>,
+    ) -> anyhow::Result<()> {
+        let mut lock = stderr.lock().unwrap();
+        let mut reader = BufReader::new(&mut *lock);
+        // let mut reader = BufReader::new(stderr.lock().unwrap());
+        let mut buffer = [0; 1];
+        let mut line = String::new();
+
+        loop {
+            match reader.read(&mut buffer) {
+                Ok(0) => break, // End of output
+                Ok(_) => {
+                    let char = buffer[0] as char;
+                    eprint!("{}", char);
+                    std::io::stderr().flush()?;
+
+                    if char == '\n' {
+                        // Send the complete line
+                        sender.send(line.trim().to_string())?;
+                        line.clear();
+                    } else {
+                        line.push(char);
+                    }
+                }
+                Err(err) => {
+                    eprintln!("Error reading from stderr: {}", err);
+                    break;
+                }
+            }
+        }
+
+        // Send any remaining content in the line
+        if !line.is_empty() {
+            sender.send(line.trim().to_string())?;
+        }
+
+        Ok(())
+    }
+}
+
+impl Run for Dorado {
+    fn run(mut self) -> anyhow::Result<()> {
+        let start_time = std::time::SystemTime::now();
+        self.start_time = start_time;
+
+        info!("Running Dorado with params: {:#?}", self.config);
+
+        let DoradoConfig {
+            name,
+            time,
+            pod_dir,
+            ref_fa,
+            ref_mmi,
+        } = self.config.clone();
+
+        let data_dir = "/data/longreads_basic_pipe";
+        let dorado_bin = "/data/tools/dorado-0.7.2-linux-x64/bin/dorado";
+        let case_dir = format!("{}/{}", data_dir, name);
+        let time_dir = format!("{}/{}", case_dir, time);
+        let bam = format!("{}/{}_{}_hs1.bam", time_dir, name, time);
+
+        self.create_reference_mmi(&ref_mmi, &ref_fa)?;
+        self.create_directories(&case_dir, &time_dir)?;
+
+        println!("Reading {} pod5 from: {}", time, pod_dir);
+
+        if !std::path::Path::new(&bam).exists() {
+            self.run_dorado_and_samtools(dorado_bin, &pod_dir, &ref_mmi, &bam)?;
+        }
+
+        self.run_cramino(&bam, &time_dir, &name, &time)?;
+        self.run_modkit(&bam, &time_dir, &name, &time)?;
+        self.create_fastq(&bam, &case_dir, &name, &time)?;
 
-        // Stop timer and calculate execution time
         let end_time = std::time::SystemTime::now();
         self.end_time = end_time;
         let execution_time = end_time.duration_since(start_time).unwrap().as_secs_f64();
@@ -192,18 +288,20 @@ impl Run for Dorado {
             execution_time
         );
         self.is_done = true;
-    }
-}
 
-fn print_stderr(stderr: std::process::ChildStderr, save: &mut Vec<String>) {
-    let stderr_reader = BufReader::new(stderr);
-    for line in stderr_reader.lines() {
-        match line {
-            Ok(line) => {
-                eprintln!("{}", line);
-                save.push(line);
-            }
-            Err(err) => eprintln!("Error reading stderr: {}", err),
-        }
+        Ok(())
     }
 }
+
+// fn print_stderr(stderr: std::process::ChildStderr, save: &mut Vec<String>) {
+//     let stderr_reader = BufReader::new(stderr);
+//     for line in stderr_reader.lines() {
+//         match line {
+//             Ok(line) => {
+//                 eprintln!("{}", line);
+//                 save.push(line);
+//             }
+//             Err(err) => eprintln!("Error reading stderr: {}", err),
+//         }
+//     }
+// }

+ 121 - 0
src/commands/dorado_pty.rs

@@ -0,0 +1,121 @@
+use super::dorado::DoradoConfig;
+use anyhow::{Context, Result};
+use log::{info, warn};
+
+use std::{
+    fs,
+    io::{BufRead, BufReader, Read, Write},
+    process::{ChildStderr, Command, Stdio},
+    sync::{
+        mpsc::{self, Sender},
+        Arc, Mutex,
+    },
+    thread,
+    time::{Duration, SystemTime},
+};
+
+#[derive(Debug)]
+pub struct DoradoPty {
+    config: DoradoConfig,
+    start_time: SystemTime,
+    end_time: SystemTime,
+}
+impl DoradoPty {
+    pub fn new(config: DoradoConfig) -> Result<Self> {
+        // match unsafe { fork() }? {
+        //     ForkResult::Parent { child, .. } => {
+        //         // Wait for the child process to finish
+        //         println!("Parent process, spawned child with PID: {}", child);
+        //         let _ = nix::sys::wait::waitpid(child, None)?;
+        //     }
+        //     ForkResult::Child => {
+        //         // In the child process, execute a shell command
+        //         let shell = CString::new("/bin/sh").unwrap();
+        //         let arg1 = CString::new("-c").unwrap();
+        //         let command = CString::new("ls -a | wc -l")
+        //             .unwrap();
+        //
+        //         execvp(&shell, &[shell.clone(), arg1, command])?;
+        //     }
+        // }
+
+        let config = DoradoConfig {
+        ref_fa: "/data/ref/hs1/chm13v2.0.fa".to_string(),
+        ref_mmi: "/data/ref/chm13v2.0.mmi".to_string(),
+        name: "MICHELAS".to_string(),
+        time: "diag".to_string(),
+        pod_dir: "/data/run_data/20240403-CL/CHENU-MRD-NB22_MICHELAS-DIAG-NB23/20240403_1431_1E_PAU78358_7b839f76/pod5_pass/barcode23".to_string(),
+    };
+        let DoradoConfig {
+            name,
+            time,
+            pod_dir,
+            ref_fa,
+            ref_mmi,
+        } = config.clone();
+
+        let data_dir = "/data/longreads_basic_pipe";
+        let dorado_bin = "/data/tools/dorado-0.7.2-linux-x64/bin/dorado";
+        let case_dir = format!("{}/{}", data_dir, name);
+        let time_dir = format!("{}/{}", case_dir, time);
+        let bam = format!("{}/{}_{}_hs1.bam", time_dir, name, time);
+        let dorado = format!(
+            "{dorado_bin} basecaller sup,5mC_5hmC {pod_dir} --trim all --reference {ref_mmi}"
+        );
+        let samtools_view = format!("samtools view -h -@ 20 -b /dev/stdin");
+        let samtools_sort = format!("samtools sort -@ 30 /dev/stdin -o {bam}");
+        let pipe = format!("{dorado} | {samtools_view} | {samtools_sort}");
+        let pipe_cmd = duct::cmd!("bash", "-c", pipe);
+        let mut reader = pipe_cmd.stdout_capture().reader()?;
+        // let mut reader = big_cmd.stderr_to_stdout().reader()?;
+        // let mut lines = BufReader::new(reader).lines();
+
+        warn!("dfsdf");
+        //  while let Some(line) = lines.next() {
+        //      match line {
+        //          Ok(line) => info!("{line}"),
+        //          Err(e) => warn!("{e}"),
+        //      }
+        // }
+
+        // let stderr = command.stdout.take().expect("Failed to open stderr");
+        // let mut reader = BufReader::new(stderr);
+        let mut buffer = [0; 1];
+        let mut line = String::new();
+
+        loop {
+            match reader.read(&mut buffer) {
+                Ok(0) => break, // End of output
+                Ok(_) => {
+                    let char = buffer[0] as char;
+                    eprint!("{}", char);
+                    std::io::stderr().flush()?;
+
+                    if char == '\n' {
+                        // Send the complete line
+                        line.clear();
+                    } else {
+                        line.push(char);
+                    }
+                }
+                Err(err) => {
+                    eprintln!("Error reading from stderr: {}", err);
+                    break;
+                }
+            }
+        }
+
+        // if let Some(&mut stderr) = command.stdin.take() {
+        //     let mut stderr_output = String::new();
+        //     let r = BufReader::new(&mut stderr);
+        //     stderr.read_to_string(&mut stderr_output)?;
+        //     println!("Captured stderr: {}", stderr_output);
+        // }
+        //
+        Ok(Self {
+            config,
+            start_time: SystemTime::now(),
+            end_time: SystemTime::UNIX_EPOCH,
+        })
+    }
+}

+ 1 - 0
src/commands/mod.rs

@@ -1,2 +1,3 @@
 pub mod dorado;
+pub mod dorado_pty;
 

+ 26 - 14
src/lib.rs

@@ -115,8 +115,8 @@ impl CollectionsTasks {
                         name: id.to_string(),
                         time: time_point.to_string(),
                         pod_dir: pod5_dir.display().to_string(),
-                    });
-                    d.run();
+                    }).unwrap();
+                    d.run().unwrap();
                 }
             }
 
@@ -132,7 +132,7 @@ impl CollectionsTasks {
 
 #[cfg(test)]
 mod tests {
-    use crate::bam::BamType;
+    use crate::{bam::BamType, commands::dorado_pty::DoradoPty};
 
     use self::commands::dorado::{self, Run};
 
@@ -144,17 +144,17 @@ mod tests {
         modkit::modkit(bam_path);
     }
 
-    #[test]
-    fn run_dorado() {
-        let d = dorado::Dorado::init(dorado::DoradoConfig {
-            ref_fa: "/data/ref/hs1/chm13v2.0.fa".to_string(),
-            ref_mmi: "/data/ref/chm13v2.0.mmi".to_string(),
-            name: "CONSIGNY".to_string(),
-            time: "mrd".to_string(),
-            pod_dir: "/data/run_data/20240326-CL/CONSIGNY-MRD-NB07_RICCO-DIAG-NB08/20240326_1355_1E_PAU78333_bc25da25/pod5_pass/barcode07".to_string()
-        });
-        d.run();
-    }
+    // #[test]
+    // fn run_dorado() {
+    //     let d = dorado::Dorado::init(dorado::DoradoConfig {
+    //         ref_fa: "/data/ref/hs1/chm13v2.0.fa".to_string(),
+    //         ref_mmi: "/data/ref/chm13v2.0.mmi".to_string(),
+    //         name: "CONSIGNY".to_string(),
+    //         time: "mrd".to_string(),
+    //         pod_dir: "/data/run_data/20240326-CL/CONSIGNY-MRD-NB07_RICCO-DIAG-NB08/20240326_1355_1E_PAU78333_bc25da25/pod5_pass/barcode07".to_string()
+    //     });
+    //     d.run();
+    // }
 
     #[test]
     fn pod5() -> anyhow::Result<()> {
@@ -170,6 +170,18 @@ mod tests {
         Ok(())
     }
 
+    #[test_log::test]
+    fn pty() -> anyhow::Result<()> {
+        DoradoPty::new(dorado::DoradoConfig {
+            ref_fa: "/data/ref/hs1/chm13v2.0.fa".to_string(),
+            ref_mmi: "/data/ref/chm13v2.0.mmi".to_string(),
+            name: "CONSIGNY".to_string(),
+            time: "mrd".to_string(),
+            pod_dir: "/data/run_data/20240326-CL/CONSIGNY-MRD-NB07_RICCO-DIAG-NB08/20240326_1355_1E_PAU78333_bc25da25/pod5_pass/barcode07".to_string()
+        })?;
+        Ok(())
+    }
+
     #[test_log::test]
     fn bam() -> anyhow::Result<()> {
         let bam_collection = bam::load_bam_collection("/data/longreads_basic_pipe");