rust-sailfish / sailfish

Simple, small, and extremely fast template engine for Rust
https://rust-sailfish.github.io/sailfish/
MIT License
757 stars 54 forks source link

Features request: Sailfish cli tool #147

Open 0xqd opened 3 months ago

0xqd commented 3 months ago

Features: I need a sailfish-cli tool to transform a nested folder a to folder b, will transform all files with ext.stpl to b same folder structure with files without .stpl. I have looked into sailfish and compiler, it mostly support macro for now which is a bit hard to navigate around. The dictionaries data to do the translation can be environment variable or config file through --config, -c If there is any hint to get it done? Thanks

vthg2themax commented 3 months ago

Check out rust build macros. That's what I use to copy things over for my builds, along with running the tailwindCSS executable to minify my CSS.

https://doc.rust-lang.org/cargo/reference/build-scripts.html

If you need a starter, I can give you some of my build script code.

vthg2themax commented 3 months ago

Here's my build.rs anyway: (Just ignore some of the bits about windows, I'm still trying to get it cross platform, but haven't gotten far enough yet.)

use std::env;
//use std::fs::create_dir;
use std::io::ErrorKind;
use std::path::PathBuf;
use std::process::Command;

//use std::thread::current;
//use regex::Regex;

fn main() {
    //let out_dir = env::var("OUT_DIR").unwrap();
    //Get the current directory
    let current_dir = String::from(format!(
        "{}",
        env::current_dir().expect("current_dir failed!").display()
    ));

    //use regex::Regex;
    //let re = Regex::new(r"[A-Za-z]").unwrap();
    //let result = re.replace_all("Hello World!", "x");
    //println!("{}", result); // => "xxxxx xxxxx!"

    // let icon_str = Regex::new(r##"fill=["'].*['"]"##).unwrap().replace_all(
    //     include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/img/heroicons/src/24/solid/chevron-right.svg")),
    //     "fill='white'"
    // ).into_owned();

    if cfg!(windows) {
        println!("cargo:warning=windows-specific-directory");
        //cd C:\
        //mkdir dev
        //cd dev
        //mkdir vcpkg
        //cd vcpkg
        //git clone https://github.com/microsoft/vcpkg
        //.\vcpkg\bootstrap-vcpkg.bat
        //cd C:\dev\vcpkg\vcpkg
        //.\vcpkg install openssl:x64-windows-static
        //mkdir "C:\Program Files\OpenSSL-Win64\certs"
        //winget install curl
        //C:\Program` Files\Git\mingw64\bin\curl --remote-name "C:\Program` Files\OpenSSL-Win64\certs\cacert.pem" -o "C:\Program` Files\OpenSSL-Win64\certs\cacert.pem" https://curl.se/ca/cacert.pem
        //set OPENSSL_NO_VENDOR=1
        //set RUSTFLAGS=-Ctarget-feature=+crt-static
        //set SSL_CERT_FILE=C:\OpenSSL-Win64\certs\cacert.pem
        /*

I just had this issue and none of them worked, but here is what worked for me on Windows 11.

    Install OpenSSL from http://slproweb.com/products/Win32OpenSSL.html into C:\Program Files\OpenSSL-Win64

    Set all your env variables, for me I set OPENSSL_CONF to C:\Program Files\OpenSSL-Win64\bin\openssl.cfg and added C:\Program Files\OpenSSL-Win64\bin to my path.

    Download the cert, assuming you have wget installed, wget https://curl.se/ca/cacert.pem -o cacert.pem into C:\Program Files\OpenSSL-Win64\certs

    Next step is to set up your environment for building openssl run in powershell:

 $env:OPENSSL_NO_VENDOR=1
 $env:RUSTFLAGS='-Ctarget-feature=+crt-static'
 $env:SSL_CERT = 'C:\OpenSSL-Win64\certs\cacert.pem'
 $env:OPENSSL_DIR = 'C:\Program Files\OpenSSL-Win64'

and run cargo build

         */
    } else {
        println!("cargo:warning=unix-directory");
    };

    if cfg!(target_os = "windows") {
        println!("Windows detected!");
        Command::new("cmd")
            .args(["/C", "echo hello"])
            .output()
            .expect("failed to execute process");
        return;
    } else {
        Command::new("sh")
            .arg("-c")
            .arg("echo hello")
            .output()
            .expect("failed to execute process");
    }
    //let src_dir : PathBuf = [ &current_dir, "src" ].iter().collect();
    //Setup the assets output directory
    let assets_dir: PathBuf = [&current_dir, "assets"].iter().collect();

    //copy the css files
    let css_dir: PathBuf = [&current_dir, "src", "css"].iter().collect();
    copy_dir(&css_dir, &assets_dir).expect("cargo:warning=Failed to copy_dir!");
    //Minify the CSS with the tailwindcss tool, thus overwriting the site.css file
    if cfg!(target_os = "linux") {
        let tailwindcss_status = Command::new("./tailwindcss-linux-x64_v3.2.4")
            .arg("-i")
            .arg("src/css/site.css")
            .arg("-o")
            .arg("assets/css/site.css")
            .arg("--minify")
            .spawn()
            .expect("Tailwind command failed to start!");

        println!("cargo:warning=tailwindcss-linux-x64 ran with status: '{tailwindcss_status:?}'");
    }

    //copy the favicon over
    let favicon_from_path : PathBuf = [&current_dir, "src", "favicon.ico"].iter().collect();
    let favicon_to_path : PathBuf = [&current_dir, "assets", "favicon.ico"].iter().collect();
    std::fs::copy(favicon_from_path, favicon_to_path).expect("cargo:warning=Failed to copy favicon.ico!");

    //copy the images over
    let img_dir: PathBuf = [&current_dir, "src", "img"].iter().collect();
    copy_dir(&img_dir, &assets_dir).expect("cargo:warning=Failed to copy_dir!");
    //copy the template pages over
    //let pages_dir: PathBuf = [&current_dir, "src", "pages"].iter().collect();
    //copy_dir(&pages_dir, &assets_dir).expect("cargo:warning=Failed to copy_dir!");

    //copy the js files over
    let js_dir: PathBuf = [&current_dir, "src", "js"].iter().collect();
    copy_dir(&js_dir, &assets_dir).expect("cargo:warning=Failed to copy_dir!");

    //copy the component js files over
    std::fs::create_dir_all(PathBuf::from_iter([&current_dir, "assets", "js", "components"].iter()))
                            .expect("failed to create director(y/ies)!");
    let components_dir: PathBuf = [&current_dir, "src", "pages", "components"].iter().collect();
    copy_files_with_extension(&components_dir, 
                              &PathBuf::from_iter([&current_dir, "assets", "js", "components"].iter()),
                              ".js")
                              .expect("cargo:warning=Failed to copy_dir!");

    //let dest_path = Path::new(&out_dir).join("hello.rs");
    //let mut f = File::create(&dest_path).unwrap();

    // f.write_all(b"
    //     pub fn message() -> &'static str {
    //         \"Hello, World!\"
    //     }
    // ").unwrap();
}

/// don't call this except when in copy_dir, or recursive_copy_dir.
fn recursive_copy_dir(from: &PathBuf, to: &PathBuf) -> Result<(), std::io::Error> {
    // println!("cargo:warning=recursive_copy_dir entered! from: {:?}",from.clone().display());
    // println!("cargo:warning=recursive_copy_dir entered! to  : {:?}",to.clone().display());

    for entry in std::fs::read_dir(from)? {
        let path = entry.expect("failed to read entry").path();

        // let metadata = std::fs::metadata(&path)?;
        // let last_modified = metadata
        //     .modified()?
        //     .elapsed()
        //     .expect("std::io::Error")
        //     .as_secs();

        // if last_modified < 24 * 3600 && metadata.is_file() {
        //     println!(
        //         "cargo:warning=Last modified: {:?} seconds, is read only: {:?}, size: {:?} bytes, filename: {:?}",
        //         last_modified,
        //         metadata.permissions().readonly(),
        //         metadata.len(),
        //         path.file_name().ok_or("No filename").expect("No filename!")
        //     );
        // }

        if path.is_file() {
            let from_filename = path.file_name().expect("filename not found!");
            let to_filepath = [to, &PathBuf::from(from_filename)].iter().collect::<PathBuf>();
            // println!("cargo:warning=path.is_file()");
            // println!("cargo:warning=from{:?} to filepath{:?}",&path,&filepath);
            let from_attr = std::fs::metadata(&path)?;
            //Check if the file already exists            
            match std::fs::metadata(&to_filepath) {
                Ok(to_attr) => {
                    //File exists in the target location
                    match from_attr.modified() {
                        Ok(from_modified) => {
                            match to_attr.modified() {
                                Ok(to_modified) => {
                                    //Only copy files over where the modification date is different
                                    if from_modified != to_modified {
                                        std::fs::copy(path, &to_filepath)?;
                                    }
                                },
                                Err(err) => {
                                    println!("cargo:warning=Unable to determine to's modified value: {:?}. Cannot continue. Error: {:?}",&to_attr,&err);        
                                }
                            }
                        },
                        Err(err) => {
                            println!("cargo:warning=Unable to determine from's modified value: {:?}. Cannot continue. Error: {:?}",&from_attr,&err);
                        },
                    }
                },
                Err(err)=> {
                    //File does not exist or there is an error reading the to location path                    
                    if err.kind() == ErrorKind::NotFound {
                        std::fs::copy(path, &to_filepath)?;
                    } else {
                        println!("cargo:warning=Unable to determine to_filepath's modified value: {:?}. Cannot continue. Error: {:?}",&from_attr,&err);
                    }
                }
            }

            //std::fs::copy(path, &filepath)?;
        } else if path.is_dir() {
            //If the current item we are copying is a directory, we need to create it
            //println!("cargo:warning=path.is_dir()");
            let dirname = path.file_name().expect("filename not found!");
            match  dirname.to_str() {
                Some(dirname_str) => {
                    //Don't copy over .hidden directories
                    if dirname_str.starts_with(".") == false {
                        let directory_path_to_create = [to, &PathBuf::from(dirname)].iter().collect();
                        //println!("cargo:warning=from {:?} to directory_path_to_create {:?}",&path,&directory_path_to_create);
                        std::fs::create_dir_all(&directory_path_to_create)
                            .expect("failed to create director(y/ies)!");
                        recursive_copy_dir(&path, &directory_path_to_create)?;
                    }
                },
                None => {

                },
            }

        } else {
            // Skip other content
        }
        //println!("cargo:warning=Counter is: {:?}", counter.clone());
    }
    Ok(())
}

/// Recursively copies the contents of one directory to another. This function will also copy the
/// permission bits of the original files to the destination files.
///
/// This function will **overwrite** the contents of `to`, if files have the same name.
///
/// On success, the total number of files and directories copied is returned.
///
/// ## Errors
///
/// This function will return an error in the following situations, but is not limited to just
/// these cases:
///
/// - The `from` path is not a directory
/// - The `from` path does not exist
/// - The current process does not have the permission rights to access `from` or write `to`
pub fn copy_dir(from: &PathBuf, to: &PathBuf) -> Result<(), std::io::Error> {
    //let to_buf = to.as_ref().to_path_buf();
    // println!("cargo:warning=copy_dir entered! from: {:?}",from.clone().display());
    // println!("cargo:warning=copy_dir entered! to  : {:?}",to.clone().display());

    //First check if the path even exists
    if !from.as_path().exists() {
        //println!("cargo:warning=copy from directory doesn't exist! Exists?'{:?}' ", from.as_path().exists());
        return Ok(());
    }

    let mut from_directory_name: Option<PathBuf> = None;
    //If we are copying a directory, we need to create the directory on the target, and change the copy command
    if from.as_path().is_dir() {
        let directory_to_create = from
            .as_path()
            .file_name()
            .expect("unable to get directory name!");
        // println!("cargo:warning=directory_to_create {:?} to.as_path() {:?}", directory_to_create, to.as_path().file_name());
        let directory_path_to_create: PathBuf =
            [to.to_path_buf().as_os_str(), &directory_to_create]
                .iter()
                .collect();
        // println!("cargo:warning=directory_path_to_create {:?}", directory_path_to_create);
        //Check if directory already exists
        match std::fs::metadata(&directory_path_to_create) as std::io::Result<std::fs::Metadata> {
            Ok(ref _directory_metadata) => {
                // println!("cargo:warning=NOT creating: {:?} MetaData: {:?}", &directory_path_to_create, &directory_metadata);
                from_directory_name = Some(PathBuf::from(directory_to_create));
            }
            _ => {
                // println!("cargo:warning=YES creating: {:?}", &directory_path_to_create);
                std::fs::create_dir_all(&directory_path_to_create)
                    .expect("failed to create director(y/ies)!");
                from_directory_name = Some(PathBuf::from(directory_to_create));
            }
        }
    } else {
        // println!("cargo:warning=from.as_path().is_dir() {:?}", &from.as_path().is_dir());
    }

    //If we are copying from a directory we need to use the new path
    match from_directory_name {
        Some(ref directory_name) => {
            // println!("cargo:warning=match directory_created SOME to:{:?}",
            //         &[to.to_path_buf(), directory_name.clone().to_path_buf()].iter().collect::<PathBuf>()
            // );

            recursive_copy_dir(
                from,
                &[to.to_path_buf(), directory_name.to_path_buf()]
                    .iter()
                    .collect::<PathBuf>(),
            )
            .expect("Failed to recursive_copy_dir:Some!");
        }
        None => {
            // println!("cargo:warning=match directory_created NONE to:{:?}",
            //         &[to.to_path_buf()].iter().collect::<PathBuf>()
            // );
            recursive_copy_dir(from, to).expect("failed to recursive_copy_dir:None!");
        }
    };
    Ok(())
}

/// Recursively copies the contents of one directory to another. This function will also copy the
/// permission bits of the original files to the destination files.
///
/// This function will **overwrite** the contents of `to`, if files have the same name.
///
/// On success, the total number of files and directories copied is returned.
///
/// ## Errors
///
/// This function will return an error in the following situations, but is not limited to just
/// these cases:
///
/// - The `from` path is not a directory
/// - The `from` path does not exist
/// - The current process does not have the permission rights to access `from` or write `to`
pub fn copy_files_with_extension(from: &PathBuf, to: &PathBuf, file_extension_limiter: &str) -> Result<(), std::io::Error> {
    //let to_buf = to.as_ref().to_path_buf();
    // println!("cargo:warning=copy_dir entered! from: {:?}",from.clone().display());
    // println!("cargo:warning=copy_dir entered! to  : {:?}",to.clone().display());

    //First check if the path even exists
    if !from.as_path().exists() {
        //println!("cargo:warning=copy from directory doesn't exist! Exists?'{:?}' ", from.as_path().exists());
        return Ok(());
    }

    for entry in std::fs::read_dir(from)? {
        let path = entry.expect("failed to read entry").path();

        // let metadata = std::fs::metadata(&path)?;
        // let last_modified = metadata
        //     .modified()?
        //     .elapsed()
        //     .expect("std::io::Error")
        //     .as_secs();

        // if last_modified < 24 * 3600 && metadata.is_file() {
        //     println!(
        //         "cargo:warning=Last modified: {:?} seconds, is read only: {:?}, size: {:?} bytes, filename: {:?}",
        //         last_modified,
        //         metadata.permissions().readonly(),
        //         metadata.len(),
        //         path.file_name().ok_or("No filename").expect("No filename!")
        //     );
        // }

        if path.is_file() && path.display().to_string().ends_with(file_extension_limiter) {
            let from_filename = path.file_name().expect("filename not found!");
            let to_filepath = [to, &PathBuf::from(from_filename)].iter().collect::<PathBuf>();
            // println!("cargo:warning=path.is_file()");
            // println!("cargo:warning=from{:?} to filepath{:?}",&path,&filepath);
            let from_attr = std::fs::metadata(&path)?;
            //Check if the file already exists            
            match std::fs::metadata(&to_filepath) {
                Ok(to_attr) => {
                    //File exists in the target location
                    match from_attr.modified() {
                        Ok(from_modified) => {
                            match to_attr.modified() {
                                Ok(to_modified) => {
                                    //Only copy files over where the modification date is different
                                    if from_modified != to_modified {
                                        std::fs::copy(path, &to_filepath)?;
                                    }
                                },
                                Err(err) => {
                                    println!("cargo:warning=Unable to determine to's modified value: {:?}. Cannot continue. Error: {:?}",&to_attr,&err);        
                                }
                            }
                        },
                        Err(err) => {
                            println!("cargo:warning=Unable to determine from's modified value: {:?}. Cannot continue. Error: {:?}",&from_attr,&err);
                        },
                    }
                },
                Err(err)=> {
                    //File does not exist or there is an error reading the to location path

                    if err.kind() == ErrorKind::NotFound {
                        std::fs::copy(path, &to_filepath)?;
                    } else {
                        println!("cargo:warning=Unable to determine to_filepath's modified value: {:?}. Cannot continue. Error: {:?}",&from_attr,&err);
                    }
                }
            }

            //std::fs::copy(path, &filepath)?;
        } else if path.is_dir() {
            //If the current item we are copying is a directory, we need to create it
            //println!("cargo:warning=path.is_dir()");
            let dirname = path.file_name().expect("filename not found!");
            match  dirname.to_str() {
                Some(dirname_str) => {
                    //Don't copy over .hidden directories
                    if dirname_str.starts_with(".") == false {
                        //let directory_path_to_create = [to, &PathBuf::from(dirname)].iter().collect();
                        //println!("cargo:warning=from {:?} to directory_path_to_create {:?}",&path,&directory_path_to_create);
                        // std::fs::create_dir_all(&directory_path_to_create)
                        //     .expect("failed to create director(y/ies)!");
                        // recursive_copy_dir_files_with_extension(&path, &directory_path_to_create, file_extension_limiter)?;
                    }
                },
                None => {

                },
            }

        } else {
            // Skip other content
        }
        //println!("cargo:warning=Counter is: {:?}", counter.clone());
    }

    Ok(())
}
0xqd commented 3 months ago

Thanks for sharing!

My approach is simpler, it would be something like this: cp -R a /b; cd b/; ./sailfish-cli .

You can of sailfish-cli is a general tool just like tera-cli https://github.com/chevdor/tera-cli

PaulDotSH commented 3 months ago

Features: I need a sailfish-cli tool to transform a nested folder a to folder b, will transform all files with ext.stpl to b same folder structure with files without .stpl. I have looked into sailfish and compiler, it mostly support macro for now which is a bit hard to navigate around. The dictionaries data to do the translation can be environment variable or config file through --config, -c If there is any hint to get it done? Thanks

Would this bash script suffice?

#!/bin/bash

if [ "$#" -ne 2 ]; then
    echo "Usage: $0 path_a path_b"
    exit 1
fi

path_a="$1"
path_b="$2"

if [ ! -d "$path_b" ]; then
    mkdir -p "$path_b"
fi

for file in "$path_a"/*; do
    filename=$(basename "$file")
    # Check if file has .stpl extension
    if [[ "$filename" == *.stpl ]]; then
        new_filename="${filename%.stpl}"
        mv "$file" "$path_b/$new_filename"
    else
        mv "$file" "$path_b/$filename"
    fi
done
0xqd commented 3 months ago

This doesn't include the part of translating stpl file. The copy part with script is easy, hard part is doing translate recursively the whole directory without writing macro like what we normally do with sailfish. That's why I ask about how to use sailfish compiler directly.