1#![cfg_attr(docsrs, feature(doc_cfg))]
34#![warn(missing_docs)]
35#![warn(rust_2018_idioms)]
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum Target {
40 GitHubActions,
42}
43
44#[derive(Debug, Clone)]
55pub struct Generator {
56 target: Target,
57 clippy: bool,
58 fmt: bool,
59 msrv: Option<String>,
60}
61
62impl Default for Generator {
63 fn default() -> Self {
64 Self::new()
65 }
66}
67
68impl Generator {
69 pub fn new() -> Self {
71 Self {
72 target: Target::GitHubActions,
73 clippy: false,
74 fmt: false,
75 msrv: None,
76 }
77 }
78
79 pub fn target(mut self, target: Target) -> Self {
81 self.target = target;
82 self
83 }
84
85 pub fn with_clippy(mut self) -> Self {
87 self.clippy = true;
88 self
89 }
90
91 pub fn with_fmt(mut self) -> Self {
93 self.fmt = true;
94 self
95 }
96
97 pub fn with_msrv(mut self, version: impl Into<String>) -> Self {
99 self.msrv = Some(version.into());
100 self
101 }
102
103 pub fn target_kind(&self) -> Target {
105 self.target
106 }
107
108 pub fn generate(&self) -> String {
110 match self.target {
112 Target::GitHubActions => self.render_github_actions(),
113 }
114 }
115
116 fn render_github_actions(&self) -> String {
117 let mut out = String::new();
118 out.push_str("name: CI\n\n");
119 out.push_str(
120 "on:\n push:\n branches: [main]\n pull_request:\n branches: [main]\n\n",
121 );
122 out.push_str("env:\n CARGO_TERM_COLOR: always\n\n");
123 out.push_str("jobs:\n");
124 out.push_str(" test:\n runs-on: ubuntu-latest\n steps:\n");
125 out.push_str(" - uses: actions/checkout@v5\n");
126 out.push_str(" - uses: dtolnay/rust-toolchain@stable\n");
127 out.push_str(" - run: cargo test --all-features\n");
128 if self.clippy {
129 out.push_str(" clippy:\n runs-on: ubuntu-latest\n steps:\n");
130 out.push_str(" - uses: actions/checkout@v5\n");
131 out.push_str(
132 " - uses: dtolnay/rust-toolchain@stable\n with:\n components: clippy\n",
133 );
134 out.push_str(" - run: cargo clippy --all-targets -- -D warnings\n");
135 }
136 if self.fmt {
137 out.push_str(" fmt:\n runs-on: ubuntu-latest\n steps:\n");
138 out.push_str(" - uses: actions/checkout@v5\n");
139 out.push_str(
140 " - uses: dtolnay/rust-toolchain@stable\n with:\n components: rustfmt\n",
141 );
142 out.push_str(" - run: cargo fmt --all -- --check\n");
143 }
144 if let Some(msrv) = &self.msrv {
145 out.push_str(" msrv:\n runs-on: ubuntu-latest\n steps:\n");
146 out.push_str(" - uses: actions/checkout@v5\n");
147 out.push_str(&format!(" - uses: dtolnay/rust-toolchain@{msrv}\n"));
148 out.push_str(" - run: cargo build --all-features\n");
149 }
150 out
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn default_generates_a_test_job() {
160 let yaml = Generator::new().generate();
161 assert!(yaml.contains("jobs:"));
162 assert!(yaml.contains("test:"));
163 assert!(yaml.contains("actions/checkout@v5"));
164 }
165
166 #[test]
167 fn clippy_job_added_when_requested() {
168 let yaml = Generator::new().with_clippy().generate();
169 assert!(yaml.contains("clippy:"));
170 }
171
172 #[test]
173 fn fmt_job_added_when_requested() {
174 let yaml = Generator::new().with_fmt().generate();
175 assert!(yaml.contains("fmt:"));
176 }
177
178 #[test]
179 fn msrv_job_uses_pinned_toolchain() {
180 let yaml = Generator::new().with_msrv("1.85").generate();
181 assert!(yaml.contains("rust-toolchain@1.85"));
182 }
183}