webview-bundle-cli 0.0.0-next.65

TBD
Documentation
use crate::logging::setup_logging;
use crate::options::CliOptions;
use crate::prelude::*;
use async_walkdir::WalkDir;
use biome_console::{markup, Console, ConsoleExt};
use futures::StreamExt;
use human_bytes::human_bytes;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use tokio::io::AsyncWriteExt;
use webview_bundle::{encode_bytes, Bundle};

#[derive(Debug)]
pub(crate) struct PackCommandData {
  pub(crate) cli_options: CliOptions,
  pub(crate) dir: OsString,
  pub(crate) outfile: Option<String>,
  pub(crate) truncate: bool,
}

pub(crate) async fn pack(console: &mut dyn Console, data: PackCommandData) -> Result<()> {
  tracing::debug!("data: {:?}", data);
  let PackCommandData {
    cli_options,
    dir,
    outfile,
    truncate,
  } = data;
  setup_logging(cli_options.log_level, cli_options.log_kind);

  let dir_path = Path::new(&dir);
  let walker = FileWalker::new(dir_path.to_path_buf(), console)
    .walk()
    .await?;
  let mut builder = Bundle::builder();
  for file in walker.files {
    console.log(markup! {
      "Target file: "{file.relative_path.to_string_lossy()}
    });
    builder = builder.add_file(file.relative_path.to_str().unwrap(), &file.data);
  }
  let bundle = builder.build();
  let mut outfile_path = outfile
    .or(
      dir_path
        .file_name()
        .map(|x| x.to_string_lossy().to_string()),
    )
    .map(|x| Path::new(&x).to_path_buf())
    .context("Invalid outfile")?;
  if outfile_path.extension().is_none() {
    outfile_path = outfile_path.with_extension("wvb");
  }
  tracing::debug!("Outfile path: {:?}", outfile_path);
  let data = encode_bytes(&bundle)?;
  tracing::debug!("Encode file size: {:?}", data.len());
  match truncate {
    true => {
      tokio::fs::File::create(&outfile_path)
        .await
        .context("Fail to create outfile")?
        .write_all(&data)
        .await
        .context("Fail to write outfile")?;
    }
    false => {
      tokio::fs::File::create_new(&outfile_path)
        .await
        .context("Fail to create outfile")?
        .write_all(&data)
        .await
        .context("Fail to write outfile")?;
    }
  };
  let data_size = human_bytes(data.len() as f64);
  console.log(markup! {
    <Emphasis><Success>"Pack completed"</Success></Emphasis>": "{outfile_path.to_string_lossy()}" "<Dim>"("{data_size}")"</Dim>
  });
  Ok(())
}

struct File {
  relative_path: PathBuf,
  data: Vec<u8>,
}

struct FileWalker<'a> {
  base_dir: PathBuf,
  files: Vec<File>,
  console: &'a mut dyn Console,
}

impl<'a> FileWalker<'a> {
  fn new(base_dir: PathBuf, console: &'a mut dyn Console) -> Self {
    Self {
      base_dir,
      files: vec![],
      console,
    }
  }

  async fn walk(mut self) -> Result<Self> {
    let mut entries = WalkDir::new(&self.base_dir);
    loop {
      match entries.next().await {
        Some(Ok(entry)) => {
          tracing::debug!("Walk entry: {:?}", entry);
          let meta = entry.metadata().await.context("Fail to read metadata")?;
          if meta.is_symlink() {
            tracing::warn!(
              "Path is symbolic link. Skipping: {}",
              entry.path().display()
            );
            continue;
          }
          if !meta.is_file() {
            continue;
          }
          let relative_path =
            pathdiff::diff_paths(entry.path(), &self.base_dir).context("Path is not relative")?;
          let data = tokio::fs::read(entry.path())
            .await
            .context("Error while reading file")?;
          tracing::debug!("Add path to file: {}", entry.path().display());
          self.files.push(File {
            relative_path,
            data,
          })
        }
        Some(Err(e)) => {
          tracing::warn!("Error while reading path: {e}");
          self.console.log(markup! {
            <Emphasis><Warn>"Warning"</Warn></Emphasis>": "{e.to_string()}
          })
        }
        None => break,
      }
    }
    Ok(self)
  }
}