Skip to main content

soup_rs/
lib.rs

1//! [![url-github]](https://github.com/averted/soup-rs)
2//! [![url-crates]](https://crates.io/crates/soup-rs)
3//!
4//! [url-github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
5//! [url-crates]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
6//!
7//! `soup-rs` is a cli tool to help you manage your [Zola](https://github.com/getzola/zola) site.
8//!
9//! ## Usage:
10//! ```sh
11//! $ soup-rs <COMMAND = "add">
12//!
13//! Arguments:
14//!   <COMMAND>             Command to execute (i.e. "add")
15//! ```
16
17pub mod config;
18pub mod errors;
19
20use std::io::Write;
21use std::path::PathBuf;
22
23use std::{
24    env::{temp_dir, var},
25    fs::File,
26    io::Read,
27    process::Command,
28};
29
30use chrono::Local;
31use config::Config;
32
33/// Gets title from user input, and updates config
34pub fn get_title(config: &mut Config) {
35    println!("\nEnter title: ");
36    println!("------------");
37
38    let mut input = String::new();
39
40    std::io::stdin()
41        .read_line(&mut input)
42        .expect("Failed to read title");
43
44    if input.trim().is_empty() {
45        eprintln!("Error: Title cannot be empty");
46        panic!("Empty title");
47    }
48
49    println!("");
50
51    config.title = input.trim().to_string();
52}
53
54/// Gets tags from user input, and updates config
55pub fn get_tags(config: &mut Config) {
56    if let Some(output_dir) = &config.zola.output_dir {
57        let mut out_path = PathBuf::from(output_dir);
58
59        if out_path.is_relative() {
60            out_path = config.zola.dir.join(out_path);
61        }
62
63        let output = Command::new("ls")
64            .arg(out_path.join("tags"))
65            .output()
66            .expect("Failed to list tags");
67
68        // Parse available tags from output directory
69        let tags = String::from_utf8(output.stdout).unwrap();
70        let tags = parse_invalid_tags(tags.split('\n').collect());
71
72        if tags.len() > 0 {
73            println!("Enter tags [{}]:", tags.join(", "));
74            println!("----------");
75        } else {
76            println!("Enter tags (comma separated): ");
77            println!("----------");
78        }
79    }
80
81    let mut input = String::new();
82
83    std::io::stdin()
84        .read_line(&mut input)
85        .expect("Failed to read tags");
86
87    if !input.trim().is_empty() {
88        config.tags = input.split(',').map(|tag| tag.trim().to_string()).collect();
89    }
90}
91
92/// Opens default editor with a temporary file
93pub fn get_content(config: &mut Config) {
94    let mut file_path = temp_dir();
95    let mut content = String::new();
96    let file_name = config.title.to_lowercase().replace(" ", "-") + ".md";
97    let editor = var("EDITOR").unwrap();
98
99    file_path.push(file_name);
100    File::create(&file_path).expect("Could not create temp file");
101
102    Command::new(editor)
103        .arg(&file_path)
104        .status()
105        .expect("Failed opening editor");
106
107    let _result = File::open(file_path)
108        .expect("Could not open file")
109        .read_to_string(&mut content);
110
111    config.content = content;
112}
113
114/// Writes note to Zola content directory
115pub fn write(config: &Config) {
116    let file_name = config.title.to_lowercase().replace(" ", "-") + ".md";
117    let file_path = config.zola.dir.join("content").join(file_name);
118
119    let mut file =
120        File::create(&file_path).expect("Could not create file at zola content directory");
121
122    // append date & title prefix to content
123    let mut result = String::new();
124    let mut prefix = String::new();
125    prefix.push_str("+++\n");
126    prefix.push_str(&format!("title = \"{}\"\n", config.title));
127    prefix.push_str(&format!("date = {}\n", Local::now().format("%Y-%m-%d")));
128
129    if config.tags.len() > 0 {
130        prefix.push_str(&format!("\n[taxonomies]\ntags = {:?}\n", config.tags));
131    }
132
133    prefix.push_str("+++\n\n");
134
135    result.insert_str(0, &prefix);
136    result.insert_str(prefix.len(), &config.content);
137
138    match file.write_all(result.as_bytes()) {
139        Ok(_) => println!(
140            "\n> Successfully wrote to file:\n> {}\n",
141            file_path.display()
142        ),
143        Err(e) => eprintln!("Error: {}", e),
144    }
145}
146
147/// Builds the site using Zola
148pub fn build(config: &Config) {
149    let mut zola = Command::new("zola");
150
151    zola.arg("build")
152        .current_dir(&config.zola.dir)
153        .status()
154        .expect("Failed to build site");
155}
156
157/// Publishes changes to git
158pub fn publish(config: &Config) {
159    let mut git_add = Command::new("git");
160    let mut git_commit = Command::new("git");
161    let mut git_push = Command::new("git");
162
163    git_add
164        .arg("add")
165        .arg("-A")
166        .current_dir(&config.zola.dir)
167        .status()
168        .expect("Failed to add files");
169
170    git_commit
171        .arg("commit")
172        .arg("-m")
173        .arg(&format!("New post: \"{}\"", config.title))
174        .current_dir(&config.zola.dir)
175        .status()
176        .expect("Failed to commit changes");
177
178    match &config.zola.base_url {
179        Some(url) => {
180            println!("\n> Changes to be published at: {}", url);
181        }
182        None => (),
183    }
184
185    git_push
186        .arg("push")
187        .current_dir(&config.zola.dir)
188        .status()
189        .expect("Failed to publish changes");
190}
191
192// Removes invalid tags
193fn parse_invalid_tags(tags: Vec<&str>) -> Vec<String> {
194    const INVALID_TAGS: [&str; 5] = ["", ".", "..", "index.md", "index.html"];
195
196    tags.iter()
197        .filter(|tag| !INVALID_TAGS.contains(tag))
198        .map(|tag| tag.to_string())
199        .collect()
200}