trinja 0.7.1

HTML templating / SSG for RDF(S) resources
Documentation
use std::path::PathBuf;
use std::sync::{Arc, RwLock};

use clap::{Parser, Subcommand};

use clap_verbosity_flag::{InfoLevel, Verbosity};
use glob::Pattern;
use iref::IriBuf;
use language_tags::LanguageTag;
use taganak_core::graphs::Graph;
use taganak_core::terms::{Term, path_to_file_iri};
use taganak_framework::datasets::memory::iridataset::IriDataset;
use taganak_framework::graphs::local::memory::simple::SimpleGraph;
use taganak_orm::re::BasicPrefixMap;
use tokio::io::AsyncWriteExt;
use tokio::task::spawn_blocking;
use tracing::info;
use trinja::Params;
use trinja::res::GraphObject;
use trinja::site::Site;

#[derive(Parser, Clone)]
#[command(
    version,
    author,
    about,
    help_template = "\
{before-help}{name} {version} - {about}
{author}

{usage-heading} {usage}

{all-args}{after-help}
"
)]
struct Cli {
    #[command(flatten)]
    verbosity: Verbosity<InfoLevel>,

    #[arg(short, long)]
    graphs: Option<Vec<IriBuf>>,

    /// Language for translated strings
    #[arg(short, long)]
    lang: Option<LanguageTag>,

    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand, Clone)]
enum Commands {
    /// Handle a single resource from a graph
    Resource {
        /// Subject of resource to render (defaults to source base IRI)
        subject: Option<IriBuf>,
        #[command(subcommand)]
        command: ResourceCommands,
    },
    /// Generate full static site from a graph
    Site {
        /// Subject of site to render (defaults to source base IRI)
        subject: Option<IriBuf>,
        #[command(subcommand)]
        command: SiteCommands,
    },
}
#[derive(Subcommand, Clone)]
enum ResourceCommands {
    /// Render an HTML fragment from a resource
    Render {
        /// Path to output file
        #[arg(short, long)]
        output: Option<PathBuf>,
    },
}

pub fn default_subject() -> Result<IriBuf, std::io::Error> {
    let mut path = std::env::current_dir()?;
    path.push("");
    Ok(path_to_file_iri(path).unwrap())
}

#[derive(Subcommand, Clone)]
enum SiteCommands {
    /// Render an entire static site from its definition
    Build {
        /// Path to output directory
        #[arg(short, long)]
        output: Option<PathBuf>,
    },
    /// Analyze site and output overview of linked resources
    Analyze {
        /// Output format
        #[arg(short, long, value_enum, default_value_t)]
        format: AnalyzeFormat,
    },
}

#[derive(Clone, Default, clap::ValueEnum)]
enum AnalyzeFormat {
    #[default]
    Pretty,
    w,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let args = Cli::parse();
    tracing_subscriber::fmt()
        .with_writer(std::io::stderr)
        .with_file(true)
        .with_line_number(true)
        .with_max_level(args.verbosity)
        .init();

    let curdir = std::env::current_dir()?
        .to_str()
        .ok_or(anyhow::anyhow!("could not convert path to string"))?
        .to_string();

    let default_subject = default_subject()?;

    let dataset = match args.graphs {
        Some(ref args) => IriDataset::from_iris(args).await?,
        None => {
            let default_iri = format!("{curdir}/**/*.ttl");
            info!("Using default glob {default_iri}");
            IriDataset::from_glob(&Pattern::new(&default_iri)?).await?
        }
    };

    // FIXME remove once we get blank node stability for parsers
    let mut graph = SimpleGraph::default();
    graph.merge(dataset.view().await).await?;

    match &args.command {
        Commands::Resource { subject, command } => match &command {
            ResourceCommands::Render { output } => {
                let obj = GraphObject::new(Params {
                    graph: Arc::new(RwLock::new(graph)),
                    base: Some(Arc::new(Term::NamedNode(
                        subject.clone().unwrap_or(default_subject.clone()),
                    ))),
                    prefix_map: Arc::new(BasicPrefixMap::new()),
                    language: args.lang,
                    environment: None,
                });

                let rendered = spawn_blocking(move || obj.into_rendered()).await?.unwrap();
                if let Some(path) = output {
                    let mut file = tokio::fs::File::options()
                        .create(true)
                        .truncate(true)
                        .write(true)
                        .open(path)
                        .await?;
                    file.write_all(rendered.as_bytes()).await?;
                } else {
                    println!("{rendered}");
                }
            }
        },
        Commands::Site {
            subject: site_subject,
            command,
        } => match &command {
            SiteCommands::Build { output } => {
                let params = Site::make_params(
                    graph,
                    site_subject.clone().unwrap_or(default_subject.clone()),
                    args.lang,
                )
                .await;
                let (site, params) = Site::new(params).await?;
                site.build(
                    params,
                    &output
                        .clone()
                        .unwrap_or(std::env::current_dir().unwrap().join("output")),
                )
                .await?;
            }
            SiteCommands::Analyze { format } => {
                let params = Site::make_params(
                    graph,
                    site_subject.clone().unwrap_or(default_subject.clone()),
                    args.lang,
                )
                .await;
                let (site, params) = Site::new(params).await?;

                let pages = site.pages_map(&params).await?;
                let assets = site.assets(&params).await?;

                println!("Site:");
                println!("\t<{}>", params.base.as_ref().unwrap());
                println!(
                    "\t\tTitle:\t{}",
                    site.title.as_ref().unwrap_or(&"<none>".to_string())
                );
                println!();

                println!("Pages:");
                for (term, (page, generic_page)) in pages {
                    println!("\t{}", term);
                }
                println!();

                println!("Assets:");
                for term in assets {
                    println!("\t{}", term);
                }
            }
        },
    }

    Ok(())
}