<commit_before>use std::{
    io::{stdin, stdout, BufRead, Write},
    thread::sleep,
    time::Duration,
};

use isahc::{
    auth::{Authentication, Credentials},
    config::Configurable,
    http::StatusCode,
    HttpClient, ReadResponseExt, Request,
};
use semver::{Identifier, Version};
use serde::Deserialize;
use serde_json::json;

use crate::{cargo::Package, cmd, util::ask_yes_no, GithubConfig, Metadata, Result};

const GITHUB_API_RUMA: &str = "https://api.github.com/repos/ruma/ruma";

/// Task to create a new release of the given crate.
#[derive(Debug)]
pub struct ReleaseTask {
    /// The metadata of the cargo workspace.
    metadata: Metadata,

    /// The crate to release.
    package: Package,

    /// The new version of the crate.
    version: Version,

    /// The http client to use for requests.
    http_client: HttpClient,

    /// The github configuration required to publish a release.
    config: GithubConfig,
}

impl ReleaseTask {
    /// Create a new `ReleaseTask` with the given `name` and `version`.
    pub(crate) fn new(name: String, version: Version) -> Result<Self> {
        let metadata = Metadata::load()?;

        let package = metadata
            .packages
            .clone()
            .into_iter()
            .find(|p| p.name == name)
            .ok_or(format!("Package {} not found in cargo metadata", name))?;

        let config = crate::Config::load()?.github;

        let http_client = HttpClient::new()?;

        Ok(Self { metadata, package, version, http_client, config })
    }

    /// Run the task to effectively create a release.
    pub(crate) fn run(&mut self) -> Result<()> {
        let title = &self.title();
        let prerelease = self.version.is_prerelease();
        let publish_only = self.package.name == "ruma-identifiers-validation";

        if let Some(name) = self.package.name.strip_suffix("-macros") {
            return Err(format!(
                "Macro crates are always released together with their parent crate.\n\
                 To release both {main_cr} and {macro_cr}, simply run\n\
                 \n\
                 cargo xtask release {main_cr}",
                main_cr = name,
                macro_cr = self.package.name,
            )
            .into());
        }

        println!(
            "Starting {} for {}…",
            match prerelease {
                true => "pre-release",
                false => "release",
            },
            title
        );

        if self.is_released()? {
            return Err("This crate version is already released".into());
        }

        let remote = Self::git_remote()?;

        println!("Checking status of git repository…");
        if !cmd!("git status -s -uno").read()?.is_empty()
            && !ask_yes_no("This git repository contains uncommitted changes. Continue?")?
        {
            return Ok(());
        }

        if self.package.version != self.version
            && !self.package.version.is_next(&self.version)
            && !ask_yes_no(&format!(
                "Version {} should not follow version {}. Do you really want to continue?",
                self.version, self.package.version,
            ))?
        {
            return Ok(());
        }

        let mut macros = self.macros();

        if self.package.version != self.version {
            if let Some(m) = macros.as_mut() {
                println!("Found macros crate {}.", m.name);

                m.update_version(&self.version)?;
                m.update_dependants(&self.metadata)?;

                println!("Resuming release of {}…", self.title());
            }

            self.package.update_version(&self.version)?;
            self.package.update_dependants(&self.metadata)?;
        }

        let changes = &self.package.changes(!prerelease)?;

        if self.package.version != self.version {
            self.commit()?;
        } else if !ask_yes_no(&format!(
            "Package is already version {}. Skip creating a commit and continue?",
            &self.version
        ))? {
            return Ok(());
        }

        if let Some(m) = macros {
            let published = m.publish(&self.http_client)?;

            if published {
                // Crate was published, instead of publishing skipped (because release already
                // existed).
                println!("Waiting 20 seconds for the release to make it into the crates.io index…");
                sleep(Duration::from_secs(20));
            }
        }

        self.package.publish(&self.http_client)?;

        let branch = cmd!("git rev-parse --abbrev-ref HEAD").read()?;
        if publish_only {
            println!("Pushing to remote repository…");
            cmd!("git push {remote} {branch}").run()?;

            println!("Crate published successfully!");
            return Ok(());
        }

        let tag = &self.tag_name();

        println!("Creating git tag…");
        if cmd!("git tag -l {tag}").read()?.is_empty() {
            cmd!("git tag -s {tag} -m {title} -m {changes}").read()?;
        } else if !ask_yes_no("This tag already exists. Skip this step and continue?")? {
            return Ok(());
        }

        println!("Pushing to remote repository…");
        if cmd!("git ls-remote --tags {remote} {tag}").read()?.is_empty() {
            cmd!("git push {remote} {branch} {tag}").run()?;
        } else if !ask_yes_no("This tag has already been pushed. Skip this step and continue?")? {
            return Ok(());
        }

        if prerelease {
            println!("Pre-release created successfully!");
            return Ok(());
        }

        println!("Creating release on GitHub…");
        let request_body = &json!({
            "tag_name": tag,
            "name": title,
            "body": changes.trim_softbreaks(),
        })
        .to_string();

        self.release(request_body)?;

        println!("Release created successfully!");

        Ok(())
    }

    /// Get the associated `-macros` crate of the current crate, if any.
    fn macros(&self) -> Option<Package> {
        self.metadata
            .packages
            .clone()
            .into_iter()
            .find(|p| p.name == format!("{}-macros", self.package.name))
    }

    /// Get the title of this release.
    fn title(&self) -> String {
        format!("{} {}", self.package.name, self.version)
    }

    /// Get the tag name for this release.
    fn tag_name(&self) -> String {
        format!("{}-{}", self.package.name, self.version)
    }

    /// Load the GitHub config from the config file.
    fn git_remote() -> Result<String> {
        let branch = cmd!("git rev-parse --abbrev-ref HEAD").read()?;
        let remote = cmd!("git config branch.{branch}.remote").read()?;

        if remote.is_empty() {
            return Err("Could not get current git remote".into());
        }

        Ok(remote)
    }

    /// Commit and push all the changes in the git repository.
    fn commit(&self) -> Result<()> {
        let stdin = stdin();

        let instructions = "Ready to commit the changes. [continue/abort/diff]: ";
        print!("{}", instructions);
        stdout().flush()?;

        let mut handle = stdin.lock();

        let mut input = String::new();
        loop {
            let eof = handle.read_line(&mut input)? == 0;
            if eof {
                return Err("User aborted commit".into());
            }

            match input.trim().to_ascii_lowercase().as_str() {
                "c" | "con" | "continue" => {
                    break;
                }
                "a" | "abort" => {
                    return Err("User aborted commit".into());
                }
                "d" | "diff" => {
                    cmd!("git diff").run()?;
                }
                _ => {
                    println!("Unknown command.");
                }
            }
            print!("{}", instructions);
            stdout().flush()?;

            input.clear();
        }

        let message = format!("Release {}", self.title());

        println!("Creating commit…");
        cmd!("git commit -a -m {message}").read()?;

        Ok(())
    }

    /// Check if the tag for the current version of the crate has been pushed on GitHub.
    fn is_released(&self) -> Result<bool> {
        let response = self.http_client.get(format!(
            "{}/releases/tags/{}",
            GITHUB_API_RUMA,
            self.tag_name()
        ))?;

        Ok(response.status() == StatusCode::OK)
    }

    /// Create the release on GitHub with the given `config` and `credentials`.
    fn release(&self, body: &str) -> Result<()> {
        let request = Request::post(format!("{}/releases", GITHUB_API_RUMA))
            .authentication(Authentication::basic())
            .credentials(Credentials::new(&self.config.user, &self.config.token))
            .header("Accept", "application/vnd.github.v3+json")
            .body(body)?;

        let mut response = self.http_client.send(request)?;

        if response.status() == StatusCode::CREATED {
            Ok(())
        } else {
            Err(format!("{}: {}", response.status(), response.text()?).into())
        }
    }
}

/// A tag from the `GET /repos/{owner}/{repo}/tags` endpoint of GitHub REST API.
#[derive(Debug, Deserialize)]
struct GithubTag {
    /// The name of the tag.
    name: String,
}

/// String manipulations for crate release.
trait StrExt {
    /// Remove soft line breaks as defined in CommonMark spec.
    fn trim_softbreaks(&self) -> String;
}

impl StrExt for str {
    fn trim_softbreaks(&self) -> String {
        let mut string = String::new();
        let mut s = self;

        while let Some(pos) = s.find('\n') {
            string.push_str(&s[..pos]);
            let pos_s = &s[pos..];

            if pos_s.starts_with("\n\n") {
                // Keep new paragraphs (multiple `\n`s).
                let next = pos_s.find(|c: char| c != '\n').unwrap_or(0);
                let (push, next_s) = pos_s.split_at(next);

                string.push_str(push);
                s = next_s;
            } else if s[..pos].ends_with("  ") || s[..pos].ends_with('\\') {
                // Keep hard line breaks (two spaces or a backslash before the line break).
                string.push('\n');
                s = &pos_s[1..];
            } else if let Some(p) = pos_s.find(|c: char| !c.is_ascii_whitespace()) {
                // Keep line break before list items (`\n` + whitespaces + `*` + whitespaces).
                // Remove line break and keep one space otherwise.
                let mut chars = pos_s.char_indices();
                let (_, char) = chars.find(|(i, _)| *i == p).unwrap();

                if char == '*' || char == '-' {
                    match chars.next() {
                        Some((_, next_char)) if next_char.is_ascii_whitespace() => {
                            string.push('\n');
                            s = &pos_s[1..];
                            continue;
                        }
                        _ => {}
                    }
                }

                string.push(' ');
                s = &pos_s[p..];
            }
        }

        string + s
    }
}

/// Extra Version increment methods for crate release.
trait VersionExt {
    /// Adds a pre-release label and number if there is none.
    fn add_pre_release(&mut self);

    /// Increments the pre-release number, if this is a pre-release.
    fn increment_pre_number(&mut self);

    /// Increments the pre-release label from `alpha` to `beta` if this is a pre-release and it is
    /// possible, otherwise does nothing.
    fn increment_pre_label(&mut self);

    /// If the given version can be the next after this one.
    ///
    /// This checks all the version bumps of the format MAJOR.MINOR.PATCH-PRE_LABEL.PRE_NUMBER, with
    /// PRE_LABEL = alpha or beta.
    fn is_next(&self, version: &Version) -> bool;
}

impl VersionExt for Version {
    fn add_pre_release(&mut self) {
        if !self.is_prerelease() {
            self.pre = vec![Identifier::AlphaNumeric("alpha".into()), Identifier::Numeric(1)];
        }
    }

    fn increment_pre_number(&mut self) {
        if self.is_prerelease() {
            if let Identifier::Numeric(n) = self.pre[1] {
                self.pre[1] = Identifier::Numeric(n + 1);
            }
        }
    }

    fn increment_pre_label(&mut self) {
        if self.is_prerelease() {
            match &self.pre[0] {
                Identifier::AlphaNumeric(n) if n == "alpha" => {
                    self.pre =
                        vec![Identifier::AlphaNumeric("beta".into()), Identifier::Numeric(1)];
                }
                _ => {}
            }
        }
    }

    fn is_next(&self, version: &Version) -> bool {
        let mut next = self.clone();

        if self.is_prerelease() {
            next.increment_pre_number();
            if next == *version {
                return true;
            }

            next.increment_pre_label();
            if next == *version {
                return true;
            }

            next.pre = vec![];
        } else {
            next.increment_patch();
            if next == *version {
                return true;
            }

            next.add_pre_release();
            if next == *version {
                return true;
            }

            next.increment_minor();
            if next == *version {
                return true;
            }

            next.add_pre_release();
            if next == *version {
                return true;
            }

            next.increment_major();
            if next == *version {
                return true;
            }

            next.add_pre_release();
        }
        next == *version
    }
}
<commit_msg>xtask: Fix wrong release version comparison<commit_after>use std::{
    io::{stdin, stdout, BufRead, Write},
    thread::sleep,
    time::Duration,
};

use isahc::{
    auth::{Authentication, Credentials},
    config::Configurable,
    http::StatusCode,
    HttpClient, ReadResponseExt, Request,
};
use semver::{Identifier, Version};
use serde::Deserialize;
use serde_json::json;

use crate::{cargo::Package, cmd, util::ask_yes_no, GithubConfig, Metadata, Result};

const GITHUB_API_RUMA: &str = "https://api.github.com/repos/ruma/ruma";

/// Task to create a new release of the given crate.
#[derive(Debug)]
pub struct ReleaseTask {
    /// The metadata of the cargo workspace.
    metadata: Metadata,

    /// The crate to release.
    package: Package,

    /// The new version of the crate.
    version: Version,

    /// The http client to use for requests.
    http_client: HttpClient,

    /// The github configuration required to publish a release.
    config: GithubConfig,
}

impl ReleaseTask {
    /// Create a new `ReleaseTask` with the given `name` and `version`.
    pub(crate) fn new(name: String, version: Version) -> Result<Self> {
        let metadata = Metadata::load()?;

        let package = metadata
            .packages
            .clone()
            .into_iter()
            .find(|p| p.name == name)
            .ok_or(format!("Package {} not found in cargo metadata", name))?;

        let config = crate::Config::load()?.github;

        let http_client = HttpClient::new()?;

        Ok(Self { metadata, package, version, http_client, config })
    }

    /// Run the task to effectively create a release.
    pub(crate) fn run(&mut self) -> Result<()> {
        let title = &self.title();
        let prerelease = self.version.is_prerelease();
        let publish_only = self.package.name == "ruma-identifiers-validation";

        if let Some(name) = self.package.name.strip_suffix("-macros") {
            return Err(format!(
                "Macro crates are always released together with their parent crate.\n\
                 To release both {main_cr} and {macro_cr}, simply run\n\
                 \n\
                 cargo xtask release {main_cr}",
                main_cr = name,
                macro_cr = self.package.name,
            )
            .into());
        }

        println!(
            "Starting {} for {}…",
            match prerelease {
                true => "pre-release",
                false => "release",
            },
            title
        );

        if self.is_released()? {
            return Err("This crate version is already released".into());
        }

        let remote = Self::git_remote()?;

        println!("Checking status of git repository…");
        if !cmd!("git status -s -uno").read()?.is_empty()
            && !ask_yes_no("This git repository contains uncommitted changes. Continue?")?
        {
            return Ok(());
        }

        if self.package.version != self.version
            && !self.package.version.is_next(&self.version)
            && !ask_yes_no(&format!(
                "Version {} should not follow version {}. Do you really want to continue?",
                self.version, self.package.version,
            ))?
        {
            return Ok(());
        }

        let mut macros = self.macros();

        let create_commit = if self.package.version != self.version {
            if let Some(m) = macros.as_mut() {
                println!("Found macros crate {}.", m.name);

                m.update_version(&self.version)?;
                m.update_dependants(&self.metadata)?;

                println!("Resuming release of {}…", self.title());
            }

            self.package.update_version(&self.version)?;
            self.package.update_dependants(&self.metadata)?;
            true
        } else if !ask_yes_no(&format!(
            "Package is already version {}. Skip creating a commit and continue?",
            &self.version
        ))? {
            return Ok(());
        } else {
            false
        };

        let changes = &self.package.changes(!prerelease)?;

        if create_commit {
            self.commit()?;
        }

        if let Some(m) = macros {
            let published = m.publish(&self.http_client)?;

            if published {
                // Crate was published, instead of publishing skipped (because release already
                // existed).
                println!("Waiting 20 seconds for the release to make it into the crates.io index…");
                sleep(Duration::from_secs(20));
            }
        }

        self.package.publish(&self.http_client)?;

        let branch = cmd!("git rev-parse --abbrev-ref HEAD").read()?;
        if publish_only {
            println!("Pushing to remote repository…");
            cmd!("git push {remote} {branch}").run()?;

            println!("Crate published successfully!");
            return Ok(());
        }

        let tag = &self.tag_name();

        println!("Creating git tag…");
        if cmd!("git tag -l {tag}").read()?.is_empty() {
            cmd!("git tag -s {tag} -m {title} -m {changes}").read()?;
        } else if !ask_yes_no("This tag already exists. Skip this step and continue?")? {
            return Ok(());
        }

        println!("Pushing to remote repository…");
        if cmd!("git ls-remote --tags {remote} {tag}").read()?.is_empty() {
            cmd!("git push {remote} {branch} {tag}").run()?;
        } else if !ask_yes_no("This tag has already been pushed. Skip this step and continue?")? {
            return Ok(());
        }

        if prerelease {
            println!("Pre-release created successfully!");
            return Ok(());
        }

        println!("Creating release on GitHub…");
        let request_body = &json!({
            "tag_name": tag,
            "name": title,
            "body": changes.trim_softbreaks(),
        })
        .to_string();

        self.release(request_body)?;

        println!("Release created successfully!");

        Ok(())
    }

    /// Get the associated `-macros` crate of the current crate, if any.
    fn macros(&self) -> Option<Package> {
        self.metadata
            .packages
            .clone()
            .into_iter()
            .find(|p| p.name == format!("{}-macros", self.package.name))
    }

    /// Get the title of this release.
    fn title(&self) -> String {
        format!("{} {}", self.package.name, self.version)
    }

    /// Get the tag name for this release.
    fn tag_name(&self) -> String {
        format!("{}-{}", self.package.name, self.version)
    }

    /// Load the GitHub config from the config file.
    fn git_remote() -> Result<String> {
        let branch = cmd!("git rev-parse --abbrev-ref HEAD").read()?;
        let remote = cmd!("git config branch.{branch}.remote").read()?;

        if remote.is_empty() {
            return Err("Could not get current git remote".into());
        }

        Ok(remote)
    }

    /// Commit and push all the changes in the git repository.
    fn commit(&self) -> Result<()> {
        let stdin = stdin();

        let instructions = "Ready to commit the changes. [continue/abort/diff]: ";
        print!("{}", instructions);
        stdout().flush()?;

        let mut handle = stdin.lock();

        let mut input = String::new();
        loop {
            let eof = handle.read_line(&mut input)? == 0;
            if eof {
                return Err("User aborted commit".into());
            }

            match input.trim().to_ascii_lowercase().as_str() {
                "c" | "con" | "continue" => {
                    break;
                }
                "a" | "abort" => {
                    return Err("User aborted commit".into());
                }
                "d" | "diff" => {
                    cmd!("git diff").run()?;
                }
                _ => {
                    println!("Unknown command.");
                }
            }
            print!("{}", instructions);
            stdout().flush()?;

            input.clear();
        }

        let message = format!("Release {}", self.title());

        println!("Creating commit…");
        cmd!("git commit -a -m {message}").read()?;

        Ok(())
    }

    /// Check if the tag for the current version of the crate has been pushed on GitHub.
    fn is_released(&self) -> Result<bool> {
        let response = self.http_client.get(format!(
            "{}/releases/tags/{}",
            GITHUB_API_RUMA,
            self.tag_name()
        ))?;

        Ok(response.status() == StatusCode::OK)
    }

    /// Create the release on GitHub with the given `config` and `credentials`.
    fn release(&self, body: &str) -> Result<()> {
        let request = Request::post(format!("{}/releases", GITHUB_API_RUMA))
            .authentication(Authentication::basic())
            .credentials(Credentials::new(&self.config.user, &self.config.token))
            .header("Accept", "application/vnd.github.v3+json")
            .body(body)?;

        let mut response = self.http_client.send(request)?;

        if response.status() == StatusCode::CREATED {
            Ok(())
        } else {
            Err(format!("{}: {}", response.status(), response.text()?).into())
        }
    }
}

/// A tag from the `GET /repos/{owner}/{repo}/tags` endpoint of GitHub REST API.
#[derive(Debug, Deserialize)]
struct GithubTag {
    /// The name of the tag.
    name: String,
}

/// String manipulations for crate release.
trait StrExt {
    /// Remove soft line breaks as defined in CommonMark spec.
    fn trim_softbreaks(&self) -> String;
}

impl StrExt for str {
    fn trim_softbreaks(&self) -> String {
        let mut string = String::new();
        let mut s = self;

        while let Some(pos) = s.find('\n') {
            string.push_str(&s[..pos]);
            let pos_s = &s[pos..];

            if pos_s.starts_with("\n\n") {
                // Keep new paragraphs (multiple `\n`s).
                let next = pos_s.find(|c: char| c != '\n').unwrap_or(0);
                let (push, next_s) = pos_s.split_at(next);

                string.push_str(push);
                s = next_s;
            } else if s[..pos].ends_with("  ") || s[..pos].ends_with('\\') {
                // Keep hard line breaks (two spaces or a backslash before the line break).
                string.push('\n');
                s = &pos_s[1..];
            } else if let Some(p) = pos_s.find(|c: char| !c.is_ascii_whitespace()) {
                // Keep line break before list items (`\n` + whitespaces + `*` + whitespaces).
                // Remove line break and keep one space otherwise.
                let mut chars = pos_s.char_indices();
                let (_, char) = chars.find(|(i, _)| *i == p).unwrap();

                if char == '*' || char == '-' {
                    match chars.next() {
                        Some((_, next_char)) if next_char.is_ascii_whitespace() => {
                            string.push('\n');
                            s = &pos_s[1..];
                            continue;
                        }
                        _ => {}
                    }
                }

                string.push(' ');
                s = &pos_s[p..];
            }
        }

        string + s
    }
}

/// Extra Version increment methods for crate release.
trait VersionExt {
    /// Adds a pre-release label and number if there is none.
    fn add_pre_release(&mut self);

    /// Increments the pre-release number, if this is a pre-release.
    fn increment_pre_number(&mut self);

    /// Increments the pre-release label from `alpha` to `beta` if this is a pre-release and it is
    /// possible, otherwise does nothing.
    fn increment_pre_label(&mut self);

    /// If the given version can be the next after this one.
    ///
    /// This checks all the version bumps of the format MAJOR.MINOR.PATCH-PRE_LABEL.PRE_NUMBER, with
    /// PRE_LABEL = alpha or beta.
    fn is_next(&self, version: &Version) -> bool;
}

impl VersionExt for Version {
    fn add_pre_release(&mut self) {
        if !self.is_prerelease() {
            self.pre = vec![Identifier::AlphaNumeric("alpha".into()), Identifier::Numeric(1)];
        }
    }

    fn increment_pre_number(&mut self) {
        if self.is_prerelease() {
            if let Identifier::Numeric(n) = self.pre[1] {
                self.pre[1] = Identifier::Numeric(n + 1);
            }
        }
    }

    fn increment_pre_label(&mut self) {
        if self.is_prerelease() {
            match &self.pre[0] {
                Identifier::AlphaNumeric(n) if n == "alpha" => {
                    self.pre =
                        vec![Identifier::AlphaNumeric("beta".into()), Identifier::Numeric(1)];
                }
                _ => {}
            }
        }
    }

    fn is_next(&self, version: &Version) -> bool {
        let mut next = self.clone();

        if self.is_prerelease() {
            next.increment_pre_number();
            if next == *version {
                return true;
            }

            next.increment_pre_label();
            if next == *version {
                return true;
            }

            next.pre = vec![];
        } else {
            next.increment_patch();
            if next == *version {
                return true;
            }

            next.add_pre_release();
            if next == *version {
                return true;
            }

            next.increment_minor();
            if next == *version {
                return true;
            }

            next.add_pre_release();
            if next == *version {
                return true;
            }

            next.increment_major();
            if next == *version {
                return true;
            }

            next.add_pre_release();
        }
        next == *version
    }
}
<|endoftext|>


<commit_before>use std::string::ToString;

use chrono::{Duration, Local};
use comfy_table::presets::UTF8_HORIZONTAL_ONLY;
use comfy_table::*;

use pueue_lib::settings::Settings;
use pueue_lib::state::{State, PUEUE_DEFAULT_GROUP};
use pueue_lib::task::{Task, TaskResult, TaskStatus};

use super::{helper::*, OutputStyle};
use crate::cli::SubCommand;

/// Print the current state of the daemon in a nicely formatted table.
/// We pass the tasks as a separate parameter and as a list.
/// This allows us to print the tasks in any user-defined order.
pub fn print_state(
    state: State,
    tasks: Vec<Task>,
    cli_command: &SubCommand,
    style: &OutputStyle,
    settings: &Settings,
) {
    let (json, group_only) = match cli_command {
        SubCommand::Status { json, group } => (*json, group.clone()),
        SubCommand::FormatStatus { group } => (false, group.clone()),
        _ => panic!("Got wrong Subcommand {cli_command:?} in print_state. This shouldn't happen!"),
    };

    // If the json flag is specified, print the state as json and exit.
    if json {
        println!("{}", serde_json::to_string(&state).unwrap());
        return;
    }

    if let Some(group) = group_only {
        print_single_group(state, tasks, settings, style, group);
        return;
    }

    print_all_groups(state, tasks, settings, style);
}

fn print_single_group(
    state: State,
    tasks: Vec<Task>,
    settings: &Settings,
    style: &OutputStyle,
    group_name: String,
) {
    // Sort all tasks by their respective group;
    let mut sorted_tasks = sort_tasks_by_group(tasks);

    let group = if let Some(group) = state.groups.get(&group_name) {
        group
    } else {
        eprintln!("There exists no group \"{group_name}\"");
        return;
    };

    // Only a single group is requested. Print that group and return.
    let tasks = sorted_tasks.entry(group_name.clone()).or_default();
    let headline = get_group_headline(&group_name, group, style);
    println!("{headline}");

    // Show a message if the requested group doesn't have any tasks.
    if tasks.is_empty() {
        println!("Task list is empty. Add tasks with `pueue add -g {group_name} -- [cmd]`");
        return;
    }
    print_table(tasks, style, settings);
}

fn print_all_groups(state: State, tasks: Vec<Task>, settings: &Settings, style: &OutputStyle) {
    // Early exit and hint if there are no tasks in the queue
    // Print the state of the default group anyway, since this is information one wants to
    // see most of the time anyway.
    if state.tasks.is_empty() {
        let headline = get_group_headline(
            PUEUE_DEFAULT_GROUP,
            state.groups.get(PUEUE_DEFAULT_GROUP).unwrap(),
            style,
        );
        println!("{headline}\n");
        println!("Task list is empty. Add tasks with `pueue add -- [cmd]`");
        return;
    }

    // Sort all tasks by their respective group;
    let sorted_tasks = sort_tasks_by_group(tasks);

    // Always print the default queue at the very top, if no specific group is requested.
    if sorted_tasks.get(PUEUE_DEFAULT_GROUP).is_some() {
        let tasks = sorted_tasks.get(PUEUE_DEFAULT_GROUP).unwrap();
        let headline = get_group_headline(
            PUEUE_DEFAULT_GROUP,
            state.groups.get(PUEUE_DEFAULT_GROUP).unwrap(),
            style,
        );
        println!("{headline}");
        print_table(tasks, style, settings);

        // Add a newline if there are further groups to be printed
        if sorted_tasks.len() > 1 {
            println!();
        }
    }

    // Print a table for every other group that has any tasks
    let mut sorted_iter = sorted_tasks.iter().peekable();
    while let Some((group, tasks)) = sorted_iter.next() {
        // We always want to print the default group at the very top.
        // That's why we print it before this loop and skip it in here.
        if group.eq(PUEUE_DEFAULT_GROUP) {
            continue;
        }

        let headline = get_group_headline(group, state.groups.get(group).unwrap(), style);
        println!("{headline}");
        print_table(tasks, style, settings);

        // Add a newline between groups
        if sorted_iter.peek().is_some() {
            println!();
        }
    }
}

/// Print some tasks into a nicely formatted table
fn print_table(tasks: &[Task], style: &OutputStyle, settings: &Settings) {
    let (has_delayed_tasks, has_dependencies, has_labels) = has_special_columns(tasks);

    // Create table header row
    let mut headers = vec![Cell::new("Id"), Cell::new("Status")];

    if has_delayed_tasks {
        headers.push(Cell::new("Enqueue At"));
    }
    if has_dependencies {
        headers.push(Cell::new("Deps"));
    }
    if has_labels {
        headers.push(Cell::new("Label"));
    }

    headers.append(&mut vec![
        Cell::new("Command"),
        Cell::new("Path"),
        Cell::new("Start"),
        Cell::new("End"),
    ]);

    // Initialize comfy table.
    let mut table = Table::new();
    table
        .set_content_arrangement(ContentArrangement::Dynamic)
        .load_preset(UTF8_HORIZONTAL_ONLY)
        .set_header(headers);

    // Explicitly force styling, in case we aren't on a tty, but `--color=always` is set.
    if style.enabled {
        table.enforce_styling();
    }

    // Add rows one by one.
    for task in tasks.iter() {
        let mut row = Row::new();
        if let Some(height) = settings.client.max_status_lines {
            row.max_height(height);
        }
        row.add_cell(Cell::new(&task.id));

        // Determine the human readable task status representation and the respective color.
        let status_string = task.status.to_string();
        let (status_text, color) = match &task.status {
            TaskStatus::Running => (status_string, Color::Green),
            TaskStatus::Paused | TaskStatus::Locked => (status_string, Color::White),
            TaskStatus::Done(result) => match result {
                TaskResult::Success => (TaskResult::Success.to_string(), Color::Green),
                TaskResult::DependencyFailed => ("Dependency failed".to_string(), Color::Red),
                TaskResult::FailedToSpawn(_) => ("Failed to spawn".to_string(), Color::Red),
                TaskResult::Failed(code) => (format!("Failed ({code})"), Color::Red),
                _ => (result.to_string(), Color::Red),
            },
            _ => (status_string, Color::Yellow),
        };
        row.add_cell(style.styled_cell(status_text, Some(color), None));

        if has_delayed_tasks {
            if let TaskStatus::Stashed {
                enqueue_at: Some(enqueue_at),
            } = task.status
            {
                // Only show the date if the task is supposed to be enqueued today.
                let enqueue_today =
                    enqueue_at <= Local::today().and_hms(0, 0, 0) + Duration::days(1);
                let formatted_enqueue_at = if enqueue_today {
                    enqueue_at.format(&settings.client.status_time_format)
                } else {
                    enqueue_at.format(&settings.client.status_datetime_format)
                };
                row.add_cell(Cell::new(formatted_enqueue_at));
            } else {
                row.add_cell(Cell::new(""));
            }
        }

        if has_dependencies {
            let text = task
                .dependencies
                .iter()
                .map(|id| id.to_string())
                .collect::<Vec<String>>()
                .join(", ");
            row.add_cell(Cell::new(text));
        }

        if has_labels {
            row.add_cell(Cell::new(&task.label.as_deref().unwrap_or_default()));
        }

        // Add command and path.
        if settings.client.show_expanded_aliases {
            row.add_cell(Cell::new(&task.command));
        } else {
            row.add_cell(Cell::new(&task.original_command));
        }
        row.add_cell(Cell::new(&task.path.to_string_lossy()));

        // Add start and end info
        let (start, end) = formatted_start_end(task, settings);
        row.add_cell(Cell::new(start));
        row.add_cell(Cell::new(end));

        table.add_row(row);
    }

    // Print the table.
    println!("{table}");
}

/// Returns the formatted `start` and `end` text for a given task.
///
/// 1. If the start || end is today, skip the date.
/// 2. Otherwise show the date in both.
///
/// If the task doesn't have a start and/or end yet, an empty string will be returned
/// for the respective field.
fn formatted_start_end(task: &Task, settings: &Settings) -> (String, String) {
    // Get the start time.
    // If the task didn't start yet, just return two empty strings.
    let start = match task.start {
        Some(start) => start,
        None => return ("".into(), "".into()),
    };

    // If the task started today, just show the time.
    // Otherwise show the full date and time.
    let started_today = start >= Local::today().and_hms(0, 0, 0);
    let formatted_start = if started_today {
        start
            .format(&settings.client.status_time_format)
            .to_string()
    } else {
        start
            .format(&settings.client.status_datetime_format)
            .to_string()
    };

    // Get finish time, if already set. Otherwise only return the formatted start.
    let end = match task.end {
        Some(end) => end,
        None => return (formatted_start, "".into()),
    };

    // If the task ended today we only show the time.
    // In all other circumstances, we show the full date.
    let finished_today = end >= Local::today().and_hms(0, 0, 0);
    let formatted_end = if finished_today {
        end.format(&settings.client.status_time_format).to_string()
    } else {
        end.format(&settings.client.status_datetime_format)
            .to_string()
    };

    (formatted_start, formatted_end)
}
<commit_msg>fix: Comment<commit_after>use std::string::ToString;

use chrono::{Duration, Local};
use comfy_table::presets::UTF8_HORIZONTAL_ONLY;
use comfy_table::*;

use pueue_lib::settings::Settings;
use pueue_lib::state::{State, PUEUE_DEFAULT_GROUP};
use pueue_lib::task::{Task, TaskResult, TaskStatus};

use super::{helper::*, OutputStyle};
use crate::cli::SubCommand;

/// Print the current state of the daemon in a nicely formatted table.
/// We pass the tasks as a separate parameter and as a list.
/// This allows us to print the tasks in any user-defined order.
pub fn print_state(
    state: State,
    tasks: Vec<Task>,
    cli_command: &SubCommand,
    style: &OutputStyle,
    settings: &Settings,
) {
    let (json, group_only) = match cli_command {
        SubCommand::Status { json, group } => (*json, group.clone()),
        SubCommand::FormatStatus { group } => (false, group.clone()),
        _ => panic!("Got wrong Subcommand {cli_command:?} in print_state. This shouldn't happen!"),
    };

    // If the json flag is specified, print the state as json and exit.
    if json {
        println!("{}", serde_json::to_string(&state).unwrap());
        return;
    }

    if let Some(group) = group_only {
        print_single_group(state, tasks, settings, style, group);
        return;
    }

    print_all_groups(state, tasks, settings, style);
}

fn print_single_group(
    state: State,
    tasks: Vec<Task>,
    settings: &Settings,
    style: &OutputStyle,
    group_name: String,
) {
    // Sort all tasks by their respective group;
    let mut sorted_tasks = sort_tasks_by_group(tasks);

    let group = if let Some(group) = state.groups.get(&group_name) {
        group
    } else {
        eprintln!("There exists no group \"{group_name}\"");
        return;
    };

    // Only a single group is requested. Print that group and return.
    let tasks = sorted_tasks.entry(group_name.clone()).or_default();
    let headline = get_group_headline(&group_name, group, style);
    println!("{headline}");

    // Show a message if the requested group doesn't have any tasks.
    if tasks.is_empty() {
        println!("Task list is empty. Add tasks with `pueue add -g {group_name} -- [cmd]`");
        return;
    }
    print_table(tasks, style, settings);
}

fn print_all_groups(state: State, tasks: Vec<Task>, settings: &Settings, style: &OutputStyle) {
    // Early exit and hint if there are no tasks in the queue
    // Print the state of the default group anyway, since this is information one wants to
    // see most of the time anyway.
    if state.tasks.is_empty() {
        let headline = get_group_headline(
            PUEUE_DEFAULT_GROUP,
            state.groups.get(PUEUE_DEFAULT_GROUP).unwrap(),
            style,
        );
        println!("{headline}\n");
        println!("Task list is empty. Add tasks with `pueue add -- [cmd]`");
        return;
    }

    // Sort all tasks by their respective group;
    let sorted_tasks = sort_tasks_by_group(tasks);

    // Always print the default queue at the very top, if no specific group is requested.
    if sorted_tasks.get(PUEUE_DEFAULT_GROUP).is_some() {
        let tasks = sorted_tasks.get(PUEUE_DEFAULT_GROUP).unwrap();
        let headline = get_group_headline(
            PUEUE_DEFAULT_GROUP,
            state.groups.get(PUEUE_DEFAULT_GROUP).unwrap(),
            style,
        );
        println!("{headline}");
        print_table(tasks, style, settings);

        // Add a newline if there are further groups to be printed
        if sorted_tasks.len() > 1 {
            println!();
        }
    }

    // Print a table for every other group that has any tasks
    let mut sorted_iter = sorted_tasks.iter().peekable();
    while let Some((group, tasks)) = sorted_iter.next() {
        // We always want to print the default group at the very top.
        // That's why we print it before this loop and skip it in here.
        if group.eq(PUEUE_DEFAULT_GROUP) {
            continue;
        }

        let headline = get_group_headline(group, state.groups.get(group).unwrap(), style);
        println!("{headline}");
        print_table(tasks, style, settings);

        // Add a newline between groups
        if sorted_iter.peek().is_some() {
            println!();
        }
    }
}

/// Print some tasks into a nicely formatted table
fn print_table(tasks: &[Task], style: &OutputStyle, settings: &Settings) {
    let (has_delayed_tasks, has_dependencies, has_labels) = has_special_columns(tasks);

    // Create table header row
    let mut headers = vec![Cell::new("Id"), Cell::new("Status")];

    if has_delayed_tasks {
        headers.push(Cell::new("Enqueue At"));
    }
    if has_dependencies {
        headers.push(Cell::new("Deps"));
    }
    if has_labels {
        headers.push(Cell::new("Label"));
    }

    headers.append(&mut vec![
        Cell::new("Command"),
        Cell::new("Path"),
        Cell::new("Start"),
        Cell::new("End"),
    ]);

    // Initialize comfy table.
    let mut table = Table::new();
    table
        .set_content_arrangement(ContentArrangement::Dynamic)
        .load_preset(UTF8_HORIZONTAL_ONLY)
        .set_header(headers);

    // Explicitly force styling, in case we aren't on a tty, but `--color=always` is set.
    if style.enabled {
        table.enforce_styling();
    }

    // Add rows one by one.
    for task in tasks.iter() {
        let mut row = Row::new();
        if let Some(height) = settings.client.max_status_lines {
            row.max_height(height);
        }
        row.add_cell(Cell::new(&task.id));

        // Determine the human readable task status representation and the respective color.
        let status_string = task.status.to_string();
        let (status_text, color) = match &task.status {
            TaskStatus::Running => (status_string, Color::Green),
            TaskStatus::Paused | TaskStatus::Locked => (status_string, Color::White),
            TaskStatus::Done(result) => match result {
                TaskResult::Success => (TaskResult::Success.to_string(), Color::Green),
                TaskResult::DependencyFailed => ("Dependency failed".to_string(), Color::Red),
                TaskResult::FailedToSpawn(_) => ("Failed to spawn".to_string(), Color::Red),
                TaskResult::Failed(code) => (format!("Failed ({code})"), Color::Red),
                _ => (result.to_string(), Color::Red),
            },
            _ => (status_string, Color::Yellow),
        };
        row.add_cell(style.styled_cell(status_text, Some(color), None));

        if has_delayed_tasks {
            if let TaskStatus::Stashed {
                enqueue_at: Some(enqueue_at),
            } = task.status
            {
                // Only show the date if the task is not supposed to be enqueued today.
                let enqueue_today =
                    enqueue_at <= Local::today().and_hms(0, 0, 0) + Duration::days(1);
                let formatted_enqueue_at = if enqueue_today {
                    enqueue_at.format(&settings.client.status_time_format)
                } else {
                    enqueue_at.format(&settings.client.status_datetime_format)
                };
                row.add_cell(Cell::new(formatted_enqueue_at));
            } else {
                row.add_cell(Cell::new(""));
            }
        }

        if has_dependencies {
            let text = task
                .dependencies
                .iter()
                .map(|id| id.to_string())
                .collect::<Vec<String>>()
                .join(", ");
            row.add_cell(Cell::new(text));
        }

        if has_labels {
            row.add_cell(Cell::new(&task.label.as_deref().unwrap_or_default()));
        }

        // Add command and path.
        if settings.client.show_expanded_aliases {
            row.add_cell(Cell::new(&task.command));
        } else {
            row.add_cell(Cell::new(&task.original_command));
        }
        row.add_cell(Cell::new(&task.path.to_string_lossy()));

        // Add start and end info
        let (start, end) = formatted_start_end(task, settings);
        row.add_cell(Cell::new(start));
        row.add_cell(Cell::new(end));

        table.add_row(row);
    }

    // Print the table.
    println!("{table}");
}

/// Returns the formatted `start` and `end` text for a given task.
///
/// 1. If the start || end is today, skip the date.
/// 2. Otherwise show the date in both.
///
/// If the task doesn't have a start and/or end yet, an empty string will be returned
/// for the respective field.
fn formatted_start_end(task: &Task, settings: &Settings) -> (String, String) {
    // Get the start time.
    // If the task didn't start yet, just return two empty strings.
    let start = match task.start {
        Some(start) => start,
        None => return ("".into(), "".into()),
    };

    // If the task started today, just show the time.
    // Otherwise show the full date and time.
    let started_today = start >= Local::today().and_hms(0, 0, 0);
    let formatted_start = if started_today {
        start
            .format(&settings.client.status_time_format)
            .to_string()
    } else {
        start
            .format(&settings.client.status_datetime_format)
            .to_string()
    };

    // Get finish time, if already set. Otherwise only return the formatted start.
    let end = match task.end {
        Some(end) => end,
        None => return (formatted_start, "".into()),
    };

    // If the task ended today we only show the time.
    // In all other circumstances, we show the full date.
    let finished_today = end >= Local::today().and_hms(0, 0, 0);
    let formatted_end = if finished_today {
        end.format(&settings.client.status_time_format).to_string()
    } else {
        end.format(&settings.client.status_datetime_format)
            .to_string()
    };

    (formatted_start, formatted_end)
}
<|endoftext|>


<commit_before>use crate::utils::{constants, snippet, snippet_opt, span_help_and_lint, span_lint, span_lint_and_then};
use if_chain::if_chain;
use rustc::lint::{in_external_macro, EarlyContext, EarlyLintPass, LintArray, LintContext, LintPass};
use rustc::{declare_lint_pass, declare_tool_lint};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use std::char;
use syntax::ast::*;
use syntax::source_map::Span;
use syntax::visit::{walk_expr, FnKind, Visitor};

declare_clippy_lint! {
    /// **What it does:** Checks for structure field patterns bound to wildcards.
    ///
    /// **Why is this bad?** Using `..` instead is shorter and leaves the focus on
    /// the fields that are actually bound.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```ignore
    /// let { a: _, b: ref b, c: _ } = ..
    /// ```
    pub UNNEEDED_FIELD_PATTERN,
    style,
    "struct fields bound to a wildcard instead of using `..`"
}

declare_clippy_lint! {
    /// **What it does:** Checks for function arguments having the similar names
    /// differing by an underscore.
    ///
    /// **Why is this bad?** It affects code readability.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```rust
    /// fn foo(a: i32, _a: i32) {}
    /// ```
    pub DUPLICATE_UNDERSCORE_ARGUMENT,
    style,
    "function arguments having names which only differ by an underscore"
}

declare_clippy_lint! {
    /// **What it does:** Detects closures called in the same expression where they
    /// are defined.
    ///
    /// **Why is this bad?** It is unnecessarily adding to the expression's
    /// complexity.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```rust,ignore
    /// (|| 42)()
    /// ```
    pub REDUNDANT_CLOSURE_CALL,
    complexity,
    "throwaway closures called in the expression they are defined"
}

declare_clippy_lint! {
    /// **What it does:** Detects expressions of the form `--x`.
    ///
    /// **Why is this bad?** It can mislead C/C++ programmers to think `x` was
    /// decremented.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```rust
    /// let mut x = 3;
    /// --x;
    /// ```
    pub DOUBLE_NEG,
    style,
    "`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++"
}

declare_clippy_lint! {
    /// **What it does:** Warns on hexadecimal literals with mixed-case letter
    /// digits.
    ///
    /// **Why is this bad?** It looks confusing.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```rust
    /// let y = 0x1a9BAcD;
    /// ```
    pub MIXED_CASE_HEX_LITERALS,
    style,
    "hex literals whose letter digits are not consistently upper- or lowercased"
}

declare_clippy_lint! {
    /// **What it does:** Warns if literal suffixes are not separated by an
    /// underscore.
    ///
    /// **Why is this bad?** It is much less readable.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```rust
    /// let y = 123832i32;
    /// ```
    pub UNSEPARATED_LITERAL_SUFFIX,
    pedantic,
    "literals whose suffix is not separated by an underscore"
}

declare_clippy_lint! {
    /// **What it does:** Warns if an integral constant literal starts with `0`.
    ///
    /// **Why is this bad?** In some languages (including the infamous C language
    /// and most of its
    /// family), this marks an octal constant. In Rust however, this is a decimal
    /// constant. This could
    /// be confusing for both the writer and a reader of the constant.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    ///
    /// In Rust:
    /// ```rust
    /// fn main() {
    ///     let a = 0123;
    ///     println!("{}", a);
    /// }
    /// ```
    ///
    /// prints `123`, while in C:
    ///
    /// ```c
    /// #include <stdio.h>
    ///
    /// int main() {
    ///     int a = 0123;
    ///     printf("%d\n", a);
    /// }
    /// ```
    ///
    /// prints `83` (as `83 == 0o123` while `123 == 0o173`).
    pub ZERO_PREFIXED_LITERAL,
    complexity,
    "integer literals starting with `0`"
}

declare_clippy_lint! {
    /// **What it does:** Warns if a generic shadows a built-in type.
    ///
    /// **Why is this bad?** This gives surprising type errors.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    ///
    /// ```ignore
    /// impl<u32> Foo<u32> {
    ///     fn impl_func(&self) -> u32 {
    ///         42
    ///     }
    /// }
    /// ```
    pub BUILTIN_TYPE_SHADOW,
    style,
    "shadowing a builtin type"
}

declare_lint_pass!(MiscEarlyLints => [
    UNNEEDED_FIELD_PATTERN,
    DUPLICATE_UNDERSCORE_ARGUMENT,
    REDUNDANT_CLOSURE_CALL,
    DOUBLE_NEG,
    MIXED_CASE_HEX_LITERALS,
    UNSEPARATED_LITERAL_SUFFIX,
    ZERO_PREFIXED_LITERAL,
    BUILTIN_TYPE_SHADOW
]);

// Used to find `return` statements or equivalents e.g., `?`
struct ReturnVisitor {
    found_return: bool,
}

impl ReturnVisitor {
    fn new() -> Self {
        Self { found_return: false }
    }
}

impl<'ast> Visitor<'ast> for ReturnVisitor {
    fn visit_expr(&mut self, ex: &'ast Expr) {
        if let ExprKind::Ret(_) = ex.node {
            self.found_return = true;
        } else if let ExprKind::Try(_) = ex.node {
            self.found_return = true;
        }

        walk_expr(self, ex)
    }
}

impl EarlyLintPass for MiscEarlyLints {
    fn check_generics(&mut self, cx: &EarlyContext<'_>, gen: &Generics) {
        for param in &gen.params {
            if let GenericParamKind::Type { .. } = param.kind {
                let name = param.ident.as_str();
                if constants::BUILTIN_TYPES.contains(&&*name) {
                    span_lint(
                        cx,
                        BUILTIN_TYPE_SHADOW,
                        param.ident.span,
                        &format!("This generic shadows the built-in type `{}`", name),
                    );
                }
            }
        }
    }

    fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) {
        if let PatKind::Struct(ref npat, ref pfields, _) = pat.node {
            let mut wilds = 0;
            let type_name = npat
                .segments
                .last()
                .expect("A path must have at least one segment")
                .ident
                .name;

            for field in pfields {
                if let PatKind::Wild = field.pat.node {
                    wilds += 1;
                }
            }
            if !pfields.is_empty() && wilds == pfields.len() {
                span_help_and_lint(
                    cx,
                    UNNEEDED_FIELD_PATTERN,
                    pat.span,
                    "All the struct fields are matched to a wildcard pattern, consider using `..`.",
                    &format!("Try with `{} {{ .. }}` instead", type_name),
                );
                return;
            }
            if wilds > 0 {
                let mut normal = vec![];

                for field in pfields {
                    match field.pat.node {
                        PatKind::Wild => {},
                        _ => {
                            if let Ok(n) = cx.sess().source_map().span_to_snippet(field.span) {
                                normal.push(n);
                            }
                        },
                    }
                }
                for field in pfields {
                    if let PatKind::Wild = field.pat.node {
                        wilds -= 1;
                        if wilds > 0 {
                            span_lint(
                                cx,
                                UNNEEDED_FIELD_PATTERN,
                                field.span,
                                "You matched a field with a wildcard pattern. Consider using `..` instead",
                            );
                        } else {
                            span_help_and_lint(
                                cx,
                                UNNEEDED_FIELD_PATTERN,
                                field.span,
                                "You matched a field with a wildcard pattern. Consider using `..` \
                                 instead",
                                &format!("Try with `{} {{ {}, .. }}`", type_name, normal[..].join(", ")),
                            );
                        }
                    }
                }
            }
        }
    }

    fn check_fn(&mut self, cx: &EarlyContext<'_>, _: FnKind<'_>, decl: &FnDecl, _: Span, _: NodeId) {
        let mut registered_names: FxHashMap<String, Span> = FxHashMap::default();

        for arg in &decl.inputs {
            if let PatKind::Ident(_, ident, None) = arg.pat.node {
                let arg_name = ident.to_string();

                if arg_name.starts_with('_') {
                    if let Some(correspondence) = registered_names.get(&arg_name[1..]) {
                        span_lint(
                            cx,
                            DUPLICATE_UNDERSCORE_ARGUMENT,
                            *correspondence,
                            &format!(
                                "`{}` already exists, having another argument having almost the same \
                                 name makes code comprehension and documentation more difficult",
                                arg_name[1..].to_owned()
                            ),
                        );
                    }
                } else {
                    registered_names.insert(arg_name, arg.pat.span);
                }
            }
        }
    }

    fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
        if in_external_macro(cx.sess(), expr.span) {
            return;
        }
        match expr.node {
            ExprKind::Call(ref paren, _) => {
                if let ExprKind::Paren(ref closure) = paren.node {
                    if let ExprKind::Closure(_, _, _, ref decl, ref block, _) = closure.node {
                        let mut visitor = ReturnVisitor::new();
                        visitor.visit_expr(block);
                        if !visitor.found_return {
                            span_lint_and_then(
                                cx,
                                REDUNDANT_CLOSURE_CALL,
                                expr.span,
                                "Try not to call a closure in the expression where it is declared.",
                                |db| {
                                    if decl.inputs.is_empty() {
                                        let hint = snippet(cx, block.span, "..").into_owned();
                                        db.span_suggestion(
                                            expr.span,
                                            "Try doing something like: ",
                                            hint,
                                            Applicability::MachineApplicable, // snippet
                                        );
                                    }
                                },
                            );
                        }
                    }
                }
            },
            ExprKind::Unary(UnOp::Neg, ref inner) => {
                if let ExprKind::Unary(UnOp::Neg, _) = inner.node {
                    span_lint(
                        cx,
                        DOUBLE_NEG,
                        expr.span,
                        "`--x` could be misinterpreted as pre-decrement by C programmers, is usually a no-op",
                    );
                }
            },
            ExprKind::Lit(ref lit) => self.check_lit(cx, lit),
            _ => (),
        }
    }

    fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) {
        for w in block.stmts.windows(2) {
            if_chain! {
                if let StmtKind::Local(ref local) = w[0].node;
                if let Option::Some(ref t) = local.init;
                if let ExprKind::Closure(..) = t.node;
                if let PatKind::Ident(_, ident, _) = local.pat.node;
                if let StmtKind::Semi(ref second) = w[1].node;
                if let ExprKind::Assign(_, ref call) = second.node;
                if let ExprKind::Call(ref closure, _) = call.node;
                if let ExprKind::Path(_, ref path) = closure.node;
                then {
                    if ident == path.segments[0].ident {
                        span_lint(
                            cx,
                            REDUNDANT_CLOSURE_CALL,
                            second.span,
                            "Closure called just once immediately after it was declared",
                        );
                    }
                }
            }
        }
    }
}

impl MiscEarlyLints {
    fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
        if_chain! {
            if let LitKind::Int(value, ..) = lit.node;
            if let Some(src) = snippet_opt(cx, lit.span);
            if let Some(firstch) = src.chars().next();
            if char::to_digit(firstch, 10).is_some();
            then {
                let mut prev = '\0';
                for (idx, ch) in src.chars().enumerate() {
                    if ch == 'i' || ch == 'u' {
                        if prev != '_' {
                            span_lint_and_then(
                                cx,
                                UNSEPARATED_LITERAL_SUFFIX,
                                lit.span,
                                "integer type suffix should be separated by an underscore",
                                |db| {
                                    db.span_suggestion(
                                        lit.span,
                                        "add an underscore",
                                        format!("{}_{}", &src[0..idx], &src[idx..]),
                                        Applicability::MachineApplicable,
                                    );
                                },
                            );
                        }
                        break;
                    }
                    prev = ch;
                }
                if src.starts_with("0x") {
                    let mut seen = (false, false);
                    for ch in src.chars() {
                        match ch {
                            'a' ..= 'f' => seen.0 = true,
                            'A' ..= 'F' => seen.1 = true,
                            'i' | 'u'   => break,   // start of suffix already
                            _ => ()
                        }
                    }
                    if seen.0 && seen.1 {
                        span_lint(cx, MIXED_CASE_HEX_LITERALS, lit.span,
                                    "inconsistent casing in hexadecimal literal");
                    }
                } else if src.starts_with("0b") || src.starts_with("0o") {
                    /* nothing to do */
                } else if value != 0 && src.starts_with('0') {
                    span_lint_and_then(cx,
                                        ZERO_PREFIXED_LITERAL,
                                        lit.span,
                                        "this is a decimal constant",
                                        |db| {
                        db.span_suggestion(
                            lit.span,
                            "if you mean to use a decimal constant, remove the `0` to remove confusion",
                            src.trim_start_matches(|c| c == '_' || c == '0').to_string(),
                            Applicability::MaybeIncorrect,
                        );
                        db.span_suggestion(
                            lit.span,
                            "if you mean to use an octal constant, use `0o`",
                            format!("0o{}", src.trim_start_matches(|c| c == '_' || c == '0')),
                            Applicability::MaybeIncorrect,
                        );
                    });
                }
            }
        }
        if_chain! {
            if let LitKind::Float(..) = lit.node;
            if let Some(src) = snippet_opt(cx, lit.span);
            if let Some(firstch) = src.chars().next();
            if char::to_digit(firstch, 10).is_some();
            then {
                let mut prev = '\0';
                for (idx, ch) in src.chars().enumerate() {
                    if ch == 'f' {
                        if prev != '_' {
                            span_lint_and_then(
                                cx,
                                UNSEPARATED_LITERAL_SUFFIX,
                                lit.span,
                                "float type suffix should be separated by an underscore",
                                |db| {
                                    db.span_suggestion(
                                        lit.span,
                                        "add an underscore",
                                        format!("{}_{}", &src[0..idx], &src[idx..]),
                                        Applicability::MachineApplicable,
                                    );
                                },
                            );
                        }
                        break;
                    }
                    prev = ch;
                }
            }
        }
    }
}
<commit_msg>Requested changes<commit_after>use crate::utils::{
    constants, snippet, snippet_opt, span_help_and_lint, span_lint, span_lint_and_sugg, span_lint_and_then,
};
use if_chain::if_chain;
use rustc::lint::{in_external_macro, EarlyContext, EarlyLintPass, LintArray, LintContext, LintPass};
use rustc::{declare_lint_pass, declare_tool_lint};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use std::char;
use syntax::ast::*;
use syntax::source_map::Span;
use syntax::visit::{walk_expr, FnKind, Visitor};

declare_clippy_lint! {
    /// **What it does:** Checks for structure field patterns bound to wildcards.
    ///
    /// **Why is this bad?** Using `..` instead is shorter and leaves the focus on
    /// the fields that are actually bound.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```ignore
    /// let { a: _, b: ref b, c: _ } = ..
    /// ```
    pub UNNEEDED_FIELD_PATTERN,
    style,
    "struct fields bound to a wildcard instead of using `..`"
}

declare_clippy_lint! {
    /// **What it does:** Checks for function arguments having the similar names
    /// differing by an underscore.
    ///
    /// **Why is this bad?** It affects code readability.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```rust
    /// fn foo(a: i32, _a: i32) {}
    /// ```
    pub DUPLICATE_UNDERSCORE_ARGUMENT,
    style,
    "function arguments having names which only differ by an underscore"
}

declare_clippy_lint! {
    /// **What it does:** Detects closures called in the same expression where they
    /// are defined.
    ///
    /// **Why is this bad?** It is unnecessarily adding to the expression's
    /// complexity.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```rust,ignore
    /// (|| 42)()
    /// ```
    pub REDUNDANT_CLOSURE_CALL,
    complexity,
    "throwaway closures called in the expression they are defined"
}

declare_clippy_lint! {
    /// **What it does:** Detects expressions of the form `--x`.
    ///
    /// **Why is this bad?** It can mislead C/C++ programmers to think `x` was
    /// decremented.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```rust
    /// let mut x = 3;
    /// --x;
    /// ```
    pub DOUBLE_NEG,
    style,
    "`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++"
}

declare_clippy_lint! {
    /// **What it does:** Warns on hexadecimal literals with mixed-case letter
    /// digits.
    ///
    /// **Why is this bad?** It looks confusing.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```rust
    /// let y = 0x1a9BAcD;
    /// ```
    pub MIXED_CASE_HEX_LITERALS,
    style,
    "hex literals whose letter digits are not consistently upper- or lowercased"
}

declare_clippy_lint! {
    /// **What it does:** Warns if literal suffixes are not separated by an
    /// underscore.
    ///
    /// **Why is this bad?** It is much less readable.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```rust
    /// let y = 123832i32;
    /// ```
    pub UNSEPARATED_LITERAL_SUFFIX,
    pedantic,
    "literals whose suffix is not separated by an underscore"
}

declare_clippy_lint! {
    /// **What it does:** Warns if an integral constant literal starts with `0`.
    ///
    /// **Why is this bad?** In some languages (including the infamous C language
    /// and most of its
    /// family), this marks an octal constant. In Rust however, this is a decimal
    /// constant. This could
    /// be confusing for both the writer and a reader of the constant.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    ///
    /// In Rust:
    /// ```rust
    /// fn main() {
    ///     let a = 0123;
    ///     println!("{}", a);
    /// }
    /// ```
    ///
    /// prints `123`, while in C:
    ///
    /// ```c
    /// #include <stdio.h>
    ///
    /// int main() {
    ///     int a = 0123;
    ///     printf("%d\n", a);
    /// }
    /// ```
    ///
    /// prints `83` (as `83 == 0o123` while `123 == 0o173`).
    pub ZERO_PREFIXED_LITERAL,
    complexity,
    "integer literals starting with `0`"
}

declare_clippy_lint! {
    /// **What it does:** Warns if a generic shadows a built-in type.
    ///
    /// **Why is this bad?** This gives surprising type errors.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    ///
    /// ```ignore
    /// impl<u32> Foo<u32> {
    ///     fn impl_func(&self) -> u32 {
    ///         42
    ///     }
    /// }
    /// ```
    pub BUILTIN_TYPE_SHADOW,
    style,
    "shadowing a builtin type"
}

declare_lint_pass!(MiscEarlyLints => [
    UNNEEDED_FIELD_PATTERN,
    DUPLICATE_UNDERSCORE_ARGUMENT,
    REDUNDANT_CLOSURE_CALL,
    DOUBLE_NEG,
    MIXED_CASE_HEX_LITERALS,
    UNSEPARATED_LITERAL_SUFFIX,
    ZERO_PREFIXED_LITERAL,
    BUILTIN_TYPE_SHADOW
]);

// Used to find `return` statements or equivalents e.g., `?`
struct ReturnVisitor {
    found_return: bool,
}

impl ReturnVisitor {
    fn new() -> Self {
        Self { found_return: false }
    }
}

impl<'ast> Visitor<'ast> for ReturnVisitor {
    fn visit_expr(&mut self, ex: &'ast Expr) {
        if let ExprKind::Ret(_) = ex.node {
            self.found_return = true;
        } else if let ExprKind::Try(_) = ex.node {
            self.found_return = true;
        }

        walk_expr(self, ex)
    }
}

impl EarlyLintPass for MiscEarlyLints {
    fn check_generics(&mut self, cx: &EarlyContext<'_>, gen: &Generics) {
        for param in &gen.params {
            if let GenericParamKind::Type { .. } = param.kind {
                let name = param.ident.as_str();
                if constants::BUILTIN_TYPES.contains(&&*name) {
                    span_lint(
                        cx,
                        BUILTIN_TYPE_SHADOW,
                        param.ident.span,
                        &format!("This generic shadows the built-in type `{}`", name),
                    );
                }
            }
        }
    }

    fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) {
        if let PatKind::Struct(ref npat, ref pfields, _) = pat.node {
            let mut wilds = 0;
            let type_name = npat
                .segments
                .last()
                .expect("A path must have at least one segment")
                .ident
                .name;

            for field in pfields {
                if let PatKind::Wild = field.pat.node {
                    wilds += 1;
                }
            }
            if !pfields.is_empty() && wilds == pfields.len() {
                span_help_and_lint(
                    cx,
                    UNNEEDED_FIELD_PATTERN,
                    pat.span,
                    "All the struct fields are matched to a wildcard pattern, consider using `..`.",
                    &format!("Try with `{} {{ .. }}` instead", type_name),
                );
                return;
            }
            if wilds > 0 {
                let mut normal = vec![];

                for field in pfields {
                    match field.pat.node {
                        PatKind::Wild => {},
                        _ => {
                            if let Ok(n) = cx.sess().source_map().span_to_snippet(field.span) {
                                normal.push(n);
                            }
                        },
                    }
                }
                for field in pfields {
                    if let PatKind::Wild = field.pat.node {
                        wilds -= 1;
                        if wilds > 0 {
                            span_lint(
                                cx,
                                UNNEEDED_FIELD_PATTERN,
                                field.span,
                                "You matched a field with a wildcard pattern. Consider using `..` instead",
                            );
                        } else {
                            span_help_and_lint(
                                cx,
                                UNNEEDED_FIELD_PATTERN,
                                field.span,
                                "You matched a field with a wildcard pattern. Consider using `..` \
                                 instead",
                                &format!("Try with `{} {{ {}, .. }}`", type_name, normal[..].join(", ")),
                            );
                        }
                    }
                }
            }
        }
    }

    fn check_fn(&mut self, cx: &EarlyContext<'_>, _: FnKind<'_>, decl: &FnDecl, _: Span, _: NodeId) {
        let mut registered_names: FxHashMap<String, Span> = FxHashMap::default();

        for arg in &decl.inputs {
            if let PatKind::Ident(_, ident, None) = arg.pat.node {
                let arg_name = ident.to_string();

                if arg_name.starts_with('_') {
                    if let Some(correspondence) = registered_names.get(&arg_name[1..]) {
                        span_lint(
                            cx,
                            DUPLICATE_UNDERSCORE_ARGUMENT,
                            *correspondence,
                            &format!(
                                "`{}` already exists, having another argument having almost the same \
                                 name makes code comprehension and documentation more difficult",
                                arg_name[1..].to_owned()
                            ),
                        );
                    }
                } else {
                    registered_names.insert(arg_name, arg.pat.span);
                }
            }
        }
    }

    fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
        if in_external_macro(cx.sess(), expr.span) {
            return;
        }
        match expr.node {
            ExprKind::Call(ref paren, _) => {
                if let ExprKind::Paren(ref closure) = paren.node {
                    if let ExprKind::Closure(_, _, _, ref decl, ref block, _) = closure.node {
                        let mut visitor = ReturnVisitor::new();
                        visitor.visit_expr(block);
                        if !visitor.found_return {
                            span_lint_and_then(
                                cx,
                                REDUNDANT_CLOSURE_CALL,
                                expr.span,
                                "Try not to call a closure in the expression where it is declared.",
                                |db| {
                                    if decl.inputs.is_empty() {
                                        let hint = snippet(cx, block.span, "..").into_owned();
                                        db.span_suggestion(
                                            expr.span,
                                            "Try doing something like: ",
                                            hint,
                                            Applicability::MachineApplicable, // snippet
                                        );
                                    }
                                },
                            );
                        }
                    }
                }
            },
            ExprKind::Unary(UnOp::Neg, ref inner) => {
                if let ExprKind::Unary(UnOp::Neg, _) = inner.node {
                    span_lint(
                        cx,
                        DOUBLE_NEG,
                        expr.span,
                        "`--x` could be misinterpreted as pre-decrement by C programmers, is usually a no-op",
                    );
                }
            },
            ExprKind::Lit(ref lit) => self.check_lit(cx, lit),
            _ => (),
        }
    }

    fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) {
        for w in block.stmts.windows(2) {
            if_chain! {
                if let StmtKind::Local(ref local) = w[0].node;
                if let Option::Some(ref t) = local.init;
                if let ExprKind::Closure(..) = t.node;
                if let PatKind::Ident(_, ident, _) = local.pat.node;
                if let StmtKind::Semi(ref second) = w[1].node;
                if let ExprKind::Assign(_, ref call) = second.node;
                if let ExprKind::Call(ref closure, _) = call.node;
                if let ExprKind::Path(_, ref path) = closure.node;
                then {
                    if ident == path.segments[0].ident {
                        span_lint(
                            cx,
                            REDUNDANT_CLOSURE_CALL,
                            second.span,
                            "Closure called just once immediately after it was declared",
                        );
                    }
                }
            }
        }
    }
}

impl MiscEarlyLints {
    fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
        if_chain! {
            if let LitKind::Int(value, ..) = lit.node;
            if let Some(src) = snippet_opt(cx, lit.span);
            if let Some(firstch) = src.chars().next();
            if char::to_digit(firstch, 10).is_some();
            then {
                let mut prev = '\0';
                for (idx, ch) in src.chars().enumerate() {
                    if ch == 'i' || ch == 'u' {
                        if prev != '_' {
                            span_lint_and_sugg(
                                cx,
                                UNSEPARATED_LITERAL_SUFFIX,
                                lit.span,
                                "integer type suffix should be separated by an underscore",
                                "add an underscore",
                                format!("{}_{}", &src[0..idx], &src[idx..]),
                                Applicability::MachineApplicable,
                            );
                        }
                        break;
                    }
                    prev = ch;
                }
                if src.starts_with("0x") {
                    let mut seen = (false, false);
                    for ch in src.chars() {
                        match ch {
                            'a' ..= 'f' => seen.0 = true,
                            'A' ..= 'F' => seen.1 = true,
                            'i' | 'u'   => break,   // start of suffix already
                            _ => ()
                        }
                    }
                    if seen.0 && seen.1 {
                        span_lint(cx, MIXED_CASE_HEX_LITERALS, lit.span,
                                    "inconsistent casing in hexadecimal literal");
                    }
                } else if src.starts_with("0b") || src.starts_with("0o") {
                    /* nothing to do */
                } else if value != 0 && src.starts_with('0') {
                    span_lint_and_then(cx,
                                        ZERO_PREFIXED_LITERAL,
                                        lit.span,
                                        "this is a decimal constant",
                                        |db| {
                        db.span_suggestion(
                            lit.span,
                            "if you mean to use a decimal constant, remove the `0` to remove confusion",
                            src.trim_start_matches(|c| c == '_' || c == '0').to_string(),
                            Applicability::MaybeIncorrect,
                        );
                        db.span_suggestion(
                            lit.span,
                            "if you mean to use an octal constant, use `0o`",
                            format!("0o{}", src.trim_start_matches(|c| c == '_' || c == '0')),
                            Applicability::MaybeIncorrect,
                        );
                    });
                }
            }
        }
        if_chain! {
            if let LitKind::Float(..) = lit.node;
            if let Some(src) = snippet_opt(cx, lit.span);
            if let Some(firstch) = src.chars().next();
            if char::to_digit(firstch, 10).is_some();
            then {
                let mut prev = '\0';
                for (idx, ch) in src.chars().enumerate() {
                    if ch == 'f' {
                        if prev != '_' {
                            span_lint_and_sugg(
                                cx,
                                UNSEPARATED_LITERAL_SUFFIX,
                                lit.span,
                                "float type suffix should be separated by an underscore",
                                "add an underscore",
                                format!("{}_{}", &src[0..idx], &src[idx..]),
                                Applicability::MachineApplicable,
                            );
                        }
                        break;
                    }
                    prev = ch;
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>// based on define_flags by lexs
#[macro_escape];

macro_rules! define_flags (
    (
        $name:ident {
            $($flag:ident = $value:expr),+
        }
    ) => {
        define_flags!($name: u32 { $($flag = $value),+ })
    };

    (
        $name:ident: $t:ty {
            $($flag:ident $(= $v:expr)*),*
        }
    ) => {
        #[packed]
        pub struct $name($t);

        impl core::ops::BitOr<$name, $name> for $name {
            #[inline(always)]
            fn bitor(&self, other: &$name) -> $name {
                match (self, other) {
                    (&$name(flags1), &$name(flags2)) => $name(flags1 | flags2)
                }
            }
        }

        impl core::ops::BitAnd<$name, bool> for $name {
            #[inline(always)]
            fn bitand(&self, other: &$name) -> bool {
                match (self, other) {
                    (&$name(flags1), &$name(flags2)) => flags1 & flags2 != 0
                }
            }
        }

        impl core::ops::Not<$name> for $name {
            #[inline(always)]
            fn not(&self) -> $name {
                match self {
                    &$name(flags) => $name(!flags)
                }
            }
        }

        define_flags_rec!($name, 1, $( $flag $(= $v)* ),+)
    };
)
macro_rules! define_flags_rec (
    // full list (original behavior)
    (
        $name:ident,
        $default:expr,
        $($flag:ident = $value:expr),*
    ) => (
        $( pub static $flag: $name = $name($value); )+
    );
    // ----------
    // only one default value
    (
        $name:ident,
        $default:expr,
        $flag:ident
    ) => (
        pub static $flag: $name = $name($default);
    );
    // only one value
    (
        $name:ident,
        $default:expr,
        $flag:ident = $value:expr
    ) => (
        pub static $flag: $name = $name($value);
    );
    // ----------
    // without value (default)
    (
        $name:ident,
        $default:expr,
        $flag:ident,
        $($f:ident $(= $v:expr)*),*
    ) => (
        pub static $flag: $name = $name($default);
        define_flags_rec!($name, $default << 1, $($f $(= $v)*),+)
    );
    // with value
    (
        $name:ident,
        $default:expr,
        $flag:ident = $value:expr,
        $($f:ident $(= $v:expr)*),*
    ) => (
        pub static $flag: $name = $name($value);
        define_flags_rec!($name, $value << 1, $( $f $(= $v)* ),+)
    );
)
<commit_msg>Implement get, map with define_flags<commit_after>// based on define_flags by lexs
#[macro_escape];

macro_rules! define_flags (
    (
        $name:ident {
            $($flag:ident = $value:expr),+
        }
    ) => {
        define_flags!($name: u32 { $($flag = $value),+ })
    };

    (
        $name:ident: $T:ty {
            $($flag:ident $(= $v:expr)*),*
        }
    ) => {
        #[packed]
        pub struct $name($T);

        impl $name {
            /// Return the value.
            pub fn get(self) -> $T {
                match self { $name(x) => x }
            }

            /// Maps by applying a function to a contained value.
            pub fn map(self, f: |$T| -> $T) -> $name {
                match self { $name(x) => $name(f(x)) }
            }
        }

        impl core::ops::BitOr<$name, $name> for $name {
            #[inline(always)]
            fn bitor(&self, other: &$name) -> $name {
                match (self, other) {
                    (&$name(flags1), &$name(flags2)) => $name(flags1 | flags2)
                }
            }
        }

        impl core::ops::BitAnd<$name, bool> for $name {
            #[inline(always)]
            fn bitand(&self, other: &$name) -> bool {
                match (self, other) {
                    (&$name(flags1), &$name(flags2)) => flags1 & flags2 != 0
                }
            }
        }

        impl core::ops::Not<$name> for $name {
            #[inline(always)]
            fn not(&self) -> $name {
                match self {
                    &$name(flags) => $name(!flags)
                }
            }
        }

        define_flags_rec!($name, 1, $( $flag $(= $v)* ),+)
    };
)
macro_rules! define_flags_rec (
    // full list (original behavior)
    (
        $name:ident,
        $default:expr,
        $($flag:ident = $value:expr),*
    ) => (
        $( pub static $flag: $name = $name($value); )+
    );
    // ----------
    // only one default value
    (
        $name:ident,
        $default:expr,
        $flag:ident
    ) => (
        pub static $flag: $name = $name($default);
    );
    // only one value
    (
        $name:ident,
        $default:expr,
        $flag:ident = $value:expr
    ) => (
        pub static $flag: $name = $name($value);
    );
    // ----------
    // without value (default)
    (
        $name:ident,
        $default:expr,
        $flag:ident,
        $($f:ident $(= $v:expr)*),*
    ) => (
        pub static $flag: $name = $name($default);
        define_flags_rec!($name, $default << 1, $($f $(= $v)*),+)
    );
    // with value
    (
        $name:ident,
        $default:expr,
        $flag:ident = $value:expr,
        $($f:ident $(= $v:expr)*),*
    ) => (
        pub static $flag: $name = $name($value);
        define_flags_rec!($name, $value << 1, $( $f $(= $v)* ),+)
    );
)
<|endoftext|>


<commit_before>#![feature(scoped)]

use std::thread;

static NTHREADS: i32 = 10;

// This is the `main` thread
fn main() {
    for i in 0..NTHREADS {
        // Spin up another thread
        let _ = thread::scoped(move || {
            println!("this is thread number {}", i)
        });
    }
}
<commit_msg>threads/threads.rs: change NTHREADS type to usize<commit_after>#![feature(scoped)]

use std::thread;

static NTHREADS: usize = 10;

// This is the `main` thread
fn main() {
    for i in 0..NTHREADS {
        // Spin up another thread
        let _ = thread::scoped(move || {
            println!("this is thread number {}", i)
        });
    }
}
<|endoftext|>