1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]

//! A collection of utility functions and command line interfaces for
//! [cargo-xtask].
//!
//! This crate provides the following utilities:
//!
//! * **[`cargo xtask dist`]** and related subcommands
//!   * Builds a distributable tar.gz package for your bin crate.
//! * **[`cargo xtask lint`]** and related subcommands
//!   * Runs the lints for your bin/lib crate.
//!   * Integrated with [`rustdoc`], [`rustfmt`], [`clippy`],
//!     [`cargo-sync-rdme`], [`cargo-udeps`].
//! * **[`cargo xtask tidy`]** and related subcommands
//!   * Fixes the problems on your bin/lib crate.
//!   * Integrated with  [`rustfmt`], [`clippy`], [`cargo-sync-rdme`].
//! * **[`cargo xtask pre-release`]**
//!   * Checks if your bin/lib crate is ready for a release.
//! * **[`cargo xtask build`], [`clippy`][`cargo xtask clippy`], [`doc`][`cargo
//!   xtask doc`], [`fmt`][`cargo xtask fmt`], [`test`][`cargo xtask test`]**
//!   * Runs the cargo commands with options useful for testing and continuous
//!     integration.
//!     * **`--all-workspaces`** - Runs the cargo commands for all workspaces.
//!     * **`--workspace`** - Runs the cargo commands for all packages in the
//!       workspace.
//!     * **`--each-features`** - Repeats to runs the cargo commands for each
//!       feature enabled.
//!     * **`--exhaustive`** - Same as `--all-workspaces --workspace
//!       --each-features`.
//! * **[`cargo xtask docsrs`]**
//!   * Builds the documentation for your lib crate with configuration for
//!     [docs.rs].
//! * **[`cargo xtask exec`]**
//!   * Runs a command in the gicontext of all workspaces.
//!
//! # Usage
//!
//! First, create an `xtask` crate following the [instructions on the
//! cargo-xtask website][xtask-setup].
//!
//! Then, run the following command to add `cli-xtask` to the dependencies.
//!
//! * For bin crates:
//!
//!     ```console
//!     cargo add -p xtask cli-xtask --features main,bin-crate
//!     ```
//!
//!     If you want to use extra tools such as `cargo-sync-rdme` and
//! `cargo-udeps`,     add the `bin-crate-extra` feature.
//!
//!     ```console
//!     cargo add -p xtask cli-xtask --features main,bin-crate,bin-crate-extra
//!     ```
//!
//! * For lib crates:
//!
//!     ```console
//!     cargo add -p xtask cli-xtask --features main,lib-crate
//!     ```
//!
//!     If you want to use extra tools such as `cargo-sync-rdme` and
//! `cargo-udeps`,     add the `lib-crate-extra` feature.
//!
//!     ```console
//!     cargo add -p xtask cli-xtask --features main,lib-crate,lib-crate-extra
//!     ```
//!
//! Finally, edit `xtask/src/main.rs` as follows
//!
//! ```rust
//! # #[cfg(all(feature = "main"))]
//! # {
//! use cli_xtask::{Result, Xtask};
//!
//! fn main() -> Result<()> {
//!     <Xtask>::main()
//! }
//! # }
//! ```
//!
//! Now you can run various workflows with `cargo xtask`.
//!
//! [xtask-setup]: https://github.com/matklad/cargo-xtask#defining-xtasks
//!
//! # Customizing
//!
//! If you want to remove the subcommands that are not useful for your project,
//! you can remove them by disabling the corresponding cargo features.
//! See the [Feature flags section](#feature-flags) for more information.
//!
//! If you want to add the subcommands that are not included in this crate,
//! you can add them by creating a new data structure that implements the
//! [`clap::Subcommand`] and [`Run`].
//! See [the documentation of `Xtask`](Xtask) for more
//! information.
//!
//! # Feature flags
//!
//! By using the features flags of cli-xtask, you can enable only the features
//! and commands you need. By default, all features are disabled.
//!
//! The following section contains a list of available features:
//!
//! ## CLI features
//!
//! * **`main`** - Enables [`Xtask::main`] function and
//!   [`Xtask::main_with_config`] function that are the premade entry point for
//!   the CLI.
//! * **`error-handler`** - Enables functions for error handling in
//!   [`error_handler`] module.
//! * **`logger`** - Enables functions for logging in [`logger`] module.
//!
//! ## Subcommand features
//!
//! There are two types of features that enable subcommands:
//!
//! * **Combined features** - features that enable several useful subcommands at
//!   once, depending on the type of crate
//! * **Separated features** - features that enable each subcommand separately
//!
//! ### Combined features
//!
//! * **`bin-crate`**:- Enables useful subcommands for bin crates.
//! * **`lib-crate`** - Enables useful subcommands for lib crates.
//! * **`bin-crate-extra`** - Enables the additional subcommands useful for bin
//!   crates.
//! * **`lib-crate-extra`** - Enables the additional subcommands useful for lib
//!   crates.
//!
//! The `{bin,lib}-crate` feature requires only the standard Rust tools that can
//! be installed with `rustup`. The `{bin,lib}-crate-extra` feature may require
//! third-party tools.
//!
//! ### Separated features
//!
//! The following features require only the standard Rust tools:
//!
//! * **`subcommand-build`** - Enables [`cargo xtask build`].
//! * **`subcommand-clippy`** - Enables [`cargo xtask clippy`].
//! * **`subcommand-dist`** - Enables [`cargo xtask dist`].
//! * **`subcommand-dist-archive`** - Enables [`cargo xtask dist-archive`].
//! * **`subcommand-dist-build-bin`** - Enables [`cargo xtask dist-build-bin`].
//! * **`subcommand-dist-build-completion`** - Enables [`cargo xtask
//!   dist-build-completion`].
//! * **`subcommand-dist-build-doc`** - Enables [`cargo xtask dist-build-doc`].
//! * **`subcommand-dist-build-license`** - Enables [`cargo xtask
//!   dist-build-license`].
//! * **`subcommand-dist-build-man`** - Enables [`cargo xtask dist-build-man`].
//! * **`subcommand-dist-build-readme`** - Enables [`cargo xtask
//!   dist-build-readme`].
//! * **`subcommand-dist-clean`** - Enables [`cargo xtask dist-clean`].
//! * **`subcommand-doc`** - Enables [`cargo xtask doc`].
//! * **`subcommand-docsrs`** - Enables [`cargo xtask docsrs`].
//! * **`subcommand-exec`** - Enables [`cargo xtask exec`].
//! * **`subcommand-fmt`** - Enables [`cargo xtask fmt`].
//! * **`subcommand-lint`** - Enables [`cargo xtask lint`].
//! * **`subcommand-pre-release`** - Enables [`cargo xtask pre-release`].
//! * **`subcommand-test`** - Enables [`cargo xtask test`].
//! * **`subcommand-tidy`** - Enables [`cargo xtask tidy`].
//!
//! The following features require third-party tools:
//!
//! * **`subcommand-sync-rdme`** - Enables [`cargo xtask sync-rdme`]. Requires
//!   [`cargo-sync-rdme`] installed.
//! * **`subcommand-udeps`** - Enables [`cargo xtask udeps`]. Requires
//!   [`cargo-udeps`] installed.
//!
//! ## Other features
//!
//! * **`archive`** - Enables [`archive`] module which provides the
//!   functionality to create the archive file for distribution.
//!
//! # Minimum supported Rust version (MSRV)
//!
//! The minimum supported Rust version is **Rust 1.70.0**.
//! At least the last 3 versions of stable Rust are supported at any given time.
//!
//! While a crate is a pre-release status (0.x.x) it may have its MSRV bumped in
//! a patch release. Once a crate has reached 1.x, any MSRV bump will be
//! accompanied by a new minor version.
//!
//! # License
//!
//! This project is licensed under either of
//!
//! * Apache License, Version 2.0 ([LICENSE-APACHE] or <http://www.apache.org/licenses/LICENSE-2.0>)
//! * MIT license ([LICENSE-MIT] or <http://opensource.org/licenses/MIT>)
//!
//! at your option.
//!
//! # Contribution
//!
//! Unless you explicitly state otherwise, any contribution intentionally
//! submitted for inclusion in the work by you, as defined in the Apache-2.0
//! license, shall be dual licensed as above, without any additional terms or
//! conditions.
//!
//! See [CONTRIBUTING.md].
//!
//! [cargo-xtask]: https://github.com/matklad/cargo-xtask
//! [`cargo xtask build`]: subcommand::Build
//! [`cargo xtask clippy`]: subcommand::Clippy
//! [`cargo xtask dist`]: subcommand::Dist
//! [`cargo xtask dist-archive`]: subcommand::DistArchive
//! [`cargo xtask dist-build-bin`]: subcommand::DistBuildBin
//! [`cargo xtask dist-build-completion`]: subcommand::DistBuildCompletion
//! [`cargo xtask dist-build-doc`]: subcommand::DistBuildDoc
//! [`cargo xtask dist-build-license`]: subcommand::DistBuildLicense
//! [`cargo xtask dist-build-man`]: subcommand::DistBuildMan
//! [`cargo xtask dist-build-readme`]: subcommand::DistBuildReadme
//! [`cargo xtask dist-clean`]: subcommand::DistClean
//! [`cargo xtask doc`]: subcommand::Doc
//! [`cargo xtask docsrs`]: subcommand::Docsrs
//! [`cargo xtask exec`]: subcommand::Exec
//! [`cargo xtask fmt`]: subcommand::Fmt
//! [`cargo xtask lint`]: subcommand::Lint
//! [`cargo xtask pre-release`]: subcommand::PreRelease
//! [`cargo xtask sync-rdme`]: subcommand::SyncRdme
//! [`cargo xtask test`]: subcommand::Test
//! [`cargo xtask tidy`]: subcommand::Tidy
//! [`cargo xtask udeps`]: subcommand::Udeps
//! [`rustdoc`]: https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html
//! [`rustfmt`]: https://github.com/rust-lang/rustfmt
//! [`clippy`]: https://github.com/rust-lang/rust-clippy
//! [`cargo-sync-rdme`]: https://github.com/gifnksm/cargo-sync-rdme
//! [`cargo-udeps`]: https://github.com/est31/cargo-udeps
//! [docs.rs]: https://docs.rs/
//! [LICENSE-APACHE]: https://github.com/gifnksm/cli-xtask/blob/main/LICENSE-APACHE
//! [LICENSE-MIT]: https://github.com/gifnksm/cli-xtask/blob/main/LICENSE-MIT
//! [CONTRIBUTING.md]: https://github.com/gifnksm/cli-xtask/blob/main/CONTRIBUTING.md

#![doc(html_root_url = "https://docs.rs/cli-xtask/0.8.0")]

use std::{any::Any, fmt};

pub use cargo_metadata::{self, camino};
pub use clap;
#[cfg(feature = "error-handler")]
#[cfg_attr(docsrs, doc(cfg(feature = "error-handler")))]
pub use color_eyre;
pub use eyre;
pub use tracing;

#[cfg(feature = "error-handler")]
#[cfg_attr(docsrs, doc(cfg(feature = "error-handler")))]
pub mod error_handler;

#[cfg(feature = "logger")]
#[cfg_attr(docsrs, doc(cfg(feature = "logger")))]
pub use tracing_subscriber;

#[cfg(feature = "logger")]
#[cfg_attr(docsrs, doc(cfg(feature = "logger")))]
pub mod logger;

#[cfg(feature = "archive")]
#[cfg_attr(docsrs, doc(cfg(feature = "archive")))]
pub mod archive;

pub mod args;
pub mod cargo;
mod command;
pub mod config;
pub mod fs;
pub mod process;
pub mod subcommand;
pub mod workspace;

pub use self::command::Xtask;

/// Error type for this crate.
pub type Error = eyre::Error;
/// Result type for this crate.
pub type Result<T> = eyre::Result<T>;

/// Runs the command or subcommand.
pub trait Run: Any {
    /// Runs the command or subcommand.
    fn run(&self, config: &config::Config) -> Result<()>;

    /// Returns the subcommands that this command will run.
    fn to_subcommands(&self) -> Option<SubcommandRun> {
        None
    }

    /// Converts the `Box<dyn Run>` to `Box<dyn Any>`.
    fn into_any(self: Box<Self>) -> Box<dyn Any>;

    /// Converts the `&dyn Run` trait object to a concrete type.
    fn as_any(&self) -> &dyn Any;

    /// Converts the `&dyn Run` trait object to a mutable concrete type.
    fn as_any_mut(&mut self) -> &mut dyn Any;
}

/// Subcommands that this command will run.
pub struct SubcommandRun {
    subcommands: Vec<Box<dyn Run>>,
}

impl fmt::Debug for SubcommandRun {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Subcommand")
            //.field("subcommands", &self.subcommands)
            .finish()
    }
}

impl SubcommandRun {
    /// Creates a new `SubcommandRun`.
    pub fn new(subcommands: Vec<Box<dyn Run>>) -> Self {
        Self { subcommands }
    }

    /// Returns the subcommands that this command will run.
    pub fn subcommands(&self) -> &[Box<dyn Run>] {
        &self.subcommands
    }

    /// Returns the subcommands that this command will run.
    pub fn subcommands_mut(&mut self) -> &mut Vec<Box<dyn Run>> {
        &mut self.subcommands
    }

    /// Runs the subcommands.
    pub fn run(&self, config: &config::Config) -> Result<()> {
        for subcommand in &self.subcommands {
            subcommand.run(config)?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    struct S(i32);
    impl Run for S {
        fn run(&self, _config: &config::Config) -> Result<()> {
            Ok(())
        }

        fn into_any(self: Box<Self>) -> Box<dyn Any> {
            self
        }

        fn as_any(&self) -> &dyn Any {
            self
        }

        fn as_any_mut(&mut self) -> &mut dyn Any {
            self
        }
    }

    #[test]
    fn run_downcast() {
        let s = S(42);
        let s = &s as &(dyn Run);
        let s = s.as_any().downcast_ref::<S>().unwrap();
        assert_eq!(s.0, 42);

        let mut s = S(42);
        let s = &mut s as &mut (dyn Run);
        let s = s.as_any_mut().downcast_mut::<S>().unwrap();
        assert_eq!(s.0, 42);

        let s = S(42);
        let s = Box::new(s) as Box<dyn Run>;
        let s = s.into_any().downcast::<S>().unwrap();
        assert_eq!(s.0, 42);
    }
}