deps_gen/
lib.rs

1#![allow(non_ascii_idents)]
2
3//!
4//! # deps-gen
5//! Allows to generate files from `Cargo.lock` and a provided template at build-time
6//! ## Example
7//! The following will build a file named `src/deps.rs`.
8//!
9//! In `Cargo.toml`, add the following line:
10//! ```toml
11//! [build-dependencies]
12//! deps-gen = "*"
13//! ```
14//! then in your `build.rs`:
15//! ```rust-notest
16//! use deps_gen::gen_deps;
17//! 
18//! fn main() {
19//!     gen_deps();
20//! }
21//! ```
22//! Add `src/deps.template.rs`:
23//!
24//! ```rust
25//! #[allow(dead_code)]
26//!
27//! pub struct License {
28//!     pub name: &'static str,
29//!     pub version: &'static str,
30//! }
31//!
32//! impl License {
33//!     pub fn all() -> Vec<Self> {
34//!         vec![
35//!             //{}{{#each dependencies}}
36//!             Self {
37//!                 name: "{{name}}",
38//!                 version: "{{version}}",
39//!             },
40//!             //{}{{/each}}
41//!         ]
42//!     }
43//! }
44//! ```
45//! See [readme](https://crates.io/crates/deps-gen) for more details
46
47#[allow(dead_code)]
48
49mod test;
50mod generator;
51mod data;
52
53use std::fs;
54use std::io::Error;
55use std::path::PathBuf;
56
57pub enum TemplateSource {
58    Text(String),
59    File(PathBuf)
60}
61
62/// `Configuration` struct
63/// - `template` can be either a `File` or `Text`
64/// - `cargo_lock_path` is the path to `Cargo.lock` (filled by default)
65/// - `target_path` if not specified is deduced for `template` (if `template is specified as `File`) by removing the `.template.` name part.
66/// - `post_template_search` / `post_template_replace` allows to perform a post template replacement (to perform cleanup)
67/// - `include_root` if the root crate (the one currently being built) has to be included
68/// - `maximum_depth` when specified allows to limit recursion (the only interest here is probably `1`)
69pub struct Configuration {
70    pub template: TemplateSource,
71    pub cargo_lock_path: PathBuf,
72    pub target_path: Option<PathBuf>,
73    pub post_template_search: Option<String>,
74    pub post_template_replace: String,
75    pub include_root: bool,
76    pub maximum_depth: Option<usize>,
77}
78
79impl Default for Configuration {
80    /// Default values for `Configuration` are
81    /// - `template`: TemplateSource::File("src/deps.template.rs".into()), read template from `src/deps.template.rs`
82    /// - `cargo_lock_path`: the `Cargo.lock` (how surprising πŸ˜…)
83    /// - `target_path`: `None` (will be deduced from source path)
84    /// - `post_template_search` / `post_template_replace`: `"//{}"` / `""` (meaning `//{}` is removed)
85    /// - `include_root`: `false`
86    /// - `maximum_depth`: `None`
87    fn default() -> Self {
88        Self {
89            template: TemplateSource::File("src/deps.template.rs".into()),
90            cargo_lock_path: "Cargo.lock".into(),
91            target_path: None,
92            post_template_search: Some("//{}".into()),
93            post_template_replace: "".into(),
94            include_root: false,
95            maximum_depth: None,
96        }
97    }
98}
99
100impl Configuration {
101    pub fn target_path(&self) -> PathBuf {
102        if let Some(target_path) = &self.target_path {
103            target_path.clone()
104        } else if let TemplateSource::File(template_file) = &self.template {
105            let template_file_path = template_file.to_str().unwrap();
106            if !template_file_path.contains(".template.") {
107                panic!("When output is not specified, the input file must contain the pattern β€œ.template.”");
108            }
109            let target_path = template_file_path.replace(".template.", ".");
110            target_path.into()
111        } else {
112            panic!("Can’t guess output file!")
113        }
114    }
115
116    pub fn template_text(&self) -> String {
117        match &self.template {
118            TemplateSource::Text(text) => text.clone(),
119            TemplateSource::File(template_file) => {
120                fs::read_to_string(template_file).expect("Problem reading template file")
121            },
122        }
123    }
124}
125
126/// For lazy people (like me πŸ˜‰) the default configuration will take `src/deps.template.rs` to generate `src/deps.rs`
127pub fn gen_deps() -> Result<(), Error> {
128    gen_deps_with_conf(Configuration::default())
129}
130
131/// Detailed generator, allowing to customize configuration
132pub fn gen_deps_with_conf(configuration: Configuration) -> Result<(), Error> {
133    if let TemplateSource::File(source_path) = &configuration.template {
134        println!("cargo:rerun-if-changed={}", fs::canonicalize(source_path)?.to_str().unwrap());
135    }
136    generator::Generator::gen_with_configuration(configuration)
137}
138