<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

/// Test the functionality of device mapper devices.

extern crate devicemapper;
extern crate libstratis;
extern crate tempdir;
extern crate uuid;

use std::fs::OpenOptions;
use std::io::Write;
use std::path::{Path, PathBuf};

use self::tempdir::TempDir;
use self::uuid::Uuid;

use self::devicemapper::DM;
use self::devicemapper::LinearDev;
use self::devicemapper::Segment;
use self::devicemapper::{DataBlocks, Sectors};
use self::devicemapper::{ThinDev, ThinDevId, ThinPoolDev};

use libstratis::engine::strat_engine::blockdevmgr::{initialize, resolve_devices};
use libstratis::engine::strat_engine::device::{blkdev_size, wipe_sectors};
use libstratis::engine::strat_engine::filesystem::{create_fs, mount_fs, unmount_fs};
use libstratis::engine::strat_engine::metadata::MIN_MDA_SECTORS;

/// Verify that the sum of the lengths of the available range of the
/// blockdevs in the linear device equals the size of the linear device.
pub fn test_linear_device(paths: &[&Path]) -> () {
    let unique_devices = resolve_devices(&paths).unwrap();
    let initialized = initialize(&Uuid::new_v4(),
                                 unique_devices.clone(),
                                 MIN_MDA_SECTORS,
                                 false)
            .unwrap();
    let total_blockdev_size = initialized
        .iter()
        .fold(Sectors(0), |s, i| s + i.avail_range().length);

    let segments = initialized
        .iter()
        .map(|block_dev| block_dev.avail_range())
        .collect::<Vec<Segment>>();

    let device_name = "stratis_testing_linear";
    let dm = DM::new().unwrap();
    let lineardev = LinearDev::new(&device_name, &dm, segments).unwrap();

    let mut linear_dev_path = PathBuf::from("/dev/mapper");
    linear_dev_path.push(device_name);

    let lineardev_size = blkdev_size(&OpenOptions::new()
                                          .read(true)
                                          .open(linear_dev_path)
                                          .unwrap())
            .unwrap();
    assert!(total_blockdev_size.bytes() == lineardev_size);
    lineardev.teardown(&dm).unwrap();
}

/// Verify no errors when writing files to a mounted filesystem on a thin
/// device.
pub fn test_thinpool_device(paths: &[&Path]) -> () {
    let initialized = initialize(&Uuid::new_v4(),
                                 resolve_devices(&paths).unwrap(),
                                 MIN_MDA_SECTORS,
                                 false)
            .unwrap();

    let (metadata_blockdev, data_blockdev) = (initialized.first().unwrap(),
                                              initialized.last().unwrap());

    let dm = DM::new().unwrap();
    let metadata_dev = LinearDev::new("stratis_testing_thinpool_metadata",
                                      &dm,
                                      vec![metadata_blockdev.avail_range()])
            .unwrap();

    // Clear the meta data device.  If the first block is not all zeros - the
    // stale data will cause the device to appear as an existing meta rather
    // than a new one.  Clear the entire device to be safe.  Stratis implements
    // the same approach when constructing a thin pool.
    wipe_sectors(&metadata_dev.devnode().unwrap(),
                 Sectors(0),
                 metadata_dev.size().unwrap())
            .unwrap();

    let data_dev = LinearDev::new("stratis_testing_thinpool_datadev",
                                  &dm,
                                  vec![data_blockdev.avail_range()])
            .unwrap();
    let thinpool_dev = ThinPoolDev::new("stratis_testing_thinpool",
                                        &dm,
                                        data_dev.size().unwrap(),
                                        Sectors(1024),
                                        DataBlocks(256000),
                                        metadata_dev,
                                        data_dev)
            .unwrap();
    let thin_dev = ThinDev::new("stratis_testing_thindev",
                                &dm,
                                &thinpool_dev,
                                ThinDevId::new_u64(7).expect("7 is small enough"),
                                Sectors(300000))
            .unwrap();

    create_fs(&thin_dev.devnode().unwrap()).unwrap();

    let tmp_dir = TempDir::new("stratis_testing").unwrap();
    mount_fs(&thin_dev.devnode().unwrap(), tmp_dir.path()).unwrap();
    for i in 0..100 {
        let file_path = tmp_dir.path().join(format!("stratis_test{}.txt", i));
        writeln!(&OpenOptions::new()
                      .create(true)
                      .write(true)
                      .open(file_path)
                      .unwrap(),
                 "data")
                .unwrap();
    }
    // The -d (detach-loop) is passed for both loopback and real devs,
    // it helps with loopback devs and does no harm for real devs.
    unmount_fs(tmp_dir.path(), &["-d"]).unwrap();
    thin_dev.teardown(&dm).unwrap();
    thinpool_dev.teardown(&dm).unwrap();
}
<commit_msg>Use the devnode instead of the /dev/mapper path<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

/// Test the functionality of device mapper devices.

extern crate devicemapper;
extern crate libstratis;
extern crate tempdir;
extern crate uuid;

use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;

use self::tempdir::TempDir;
use self::uuid::Uuid;

use self::devicemapper::DM;
use self::devicemapper::LinearDev;
use self::devicemapper::Segment;
use self::devicemapper::{DataBlocks, Sectors};
use self::devicemapper::{ThinDev, ThinDevId, ThinPoolDev};

use libstratis::engine::strat_engine::blockdevmgr::{initialize, resolve_devices};
use libstratis::engine::strat_engine::device::{blkdev_size, wipe_sectors};
use libstratis::engine::strat_engine::filesystem::{create_fs, mount_fs, unmount_fs};
use libstratis::engine::strat_engine::metadata::MIN_MDA_SECTORS;

/// Verify that the sum of the lengths of the available range of the
/// blockdevs in the linear device equals the size of the linear device.
pub fn test_linear_device(paths: &[&Path]) -> () {
    let unique_devices = resolve_devices(&paths).unwrap();
    let initialized = initialize(&Uuid::new_v4(),
                                 unique_devices.clone(),
                                 MIN_MDA_SECTORS,
                                 false)
            .unwrap();
    let total_blockdev_size = initialized
        .iter()
        .fold(Sectors(0), |s, i| s + i.avail_range().length);

    let segments = initialized
        .iter()
        .map(|block_dev| block_dev.avail_range())
        .collect::<Vec<Segment>>();

    let dm = DM::new().unwrap();
    let lineardev = LinearDev::new("stratis_testing_linear", &dm, segments).unwrap();
    let lineardev_size = blkdev_size(&OpenOptions::new()
                                          .read(true)
                                          .open(lineardev.devnode().unwrap())
                                          .unwrap())
            .unwrap();
    assert!(total_blockdev_size.bytes() == lineardev_size);
    lineardev.teardown(&dm).unwrap();
}

/// Verify no errors when writing files to a mounted filesystem on a thin
/// device.
pub fn test_thinpool_device(paths: &[&Path]) -> () {
    let initialized = initialize(&Uuid::new_v4(),
                                 resolve_devices(&paths).unwrap(),
                                 MIN_MDA_SECTORS,
                                 false)
            .unwrap();

    let (metadata_blockdev, data_blockdev) = (initialized.first().unwrap(),
                                              initialized.last().unwrap());

    let dm = DM::new().unwrap();
    let metadata_dev = LinearDev::new("stratis_testing_thinpool_metadata",
                                      &dm,
                                      vec![metadata_blockdev.avail_range()])
            .unwrap();

    // Clear the meta data device.  If the first block is not all zeros - the
    // stale data will cause the device to appear as an existing meta rather
    // than a new one.  Clear the entire device to be safe.  Stratis implements
    // the same approach when constructing a thin pool.
    wipe_sectors(&metadata_dev.devnode().unwrap(),
                 Sectors(0),
                 metadata_dev.size().unwrap())
            .unwrap();

    let data_dev = LinearDev::new("stratis_testing_thinpool_datadev",
                                  &dm,
                                  vec![data_blockdev.avail_range()])
            .unwrap();
    let thinpool_dev = ThinPoolDev::new("stratis_testing_thinpool",
                                        &dm,
                                        data_dev.size().unwrap(),
                                        Sectors(1024),
                                        DataBlocks(256000),
                                        metadata_dev,
                                        data_dev)
            .unwrap();
    let thin_dev = ThinDev::new("stratis_testing_thindev",
                                &dm,
                                &thinpool_dev,
                                ThinDevId::new_u64(7).expect("7 is small enough"),
                                Sectors(300000))
            .unwrap();

    create_fs(&thin_dev.devnode().unwrap()).unwrap();

    let tmp_dir = TempDir::new("stratis_testing").unwrap();
    mount_fs(&thin_dev.devnode().unwrap(), tmp_dir.path()).unwrap();
    for i in 0..100 {
        let file_path = tmp_dir.path().join(format!("stratis_test{}.txt", i));
        writeln!(&OpenOptions::new()
                      .create(true)
                      .write(true)
                      .open(file_path)
                      .unwrap(),
                 "data")
                .unwrap();
    }
    // The -d (detach-loop) is passed for both loopback and real devs,
    // it helps with loopback devs and does no harm for real devs.
    unmount_fs(tmp_dir.path(), &["-d"]).unwrap();
    thin_dev.teardown(&dm).unwrap();
    thinpool_dev.teardown(&dm).unwrap();
}
<|endoftext|>


<commit_before>//! We use code generation heavily in rust-analyzer.
//!
//! Rather then doing it via proc-macros, we use old-school way of just dumping
//! the source code.
//!
//! This module's submodules define specific bits that we generate.

mod gen_syntax;
mod gen_parser_tests;
mod gen_assists_docs;

use std::{
    fs,
    io::Write,
    mem,
    path::Path,
    process::{Command, Stdio},
};

use crate::{project_root, Result};

pub use self::{
    gen_assists_docs::generate_assists_docs, gen_parser_tests::generate_parser_tests,
    gen_syntax::generate_syntax,
};

pub const GRAMMAR: &str = "crates/ra_syntax/src/grammar.ron";
const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar";
const OK_INLINE_TESTS_DIR: &str = "crates/ra_syntax/test_data/parser/inline/ok";
const ERR_INLINE_TESTS_DIR: &str = "crates/ra_syntax/test_data/parser/inline/err";

pub const SYNTAX_KINDS: &str = "crates/ra_parser/src/syntax_kind/generated.rs";
pub const AST: &str = "crates/ra_syntax/src/ast/generated.rs";

const ASSISTS_DIR: &str = "crates/ra_assists/src/assists";
const ASSISTS_TESTS: &str = "crates/ra_assists/src/doc_tests/generated.rs";
const ASSISTS_DOCS: &str = "docs/user/assists.md";

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Mode {
    Overwrite,
    Verify,
}

/// A helper to update file on disk if it has changed.
/// With verify = false,
fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> {
    match fs::read_to_string(path) {
        Ok(ref old_contents) if old_contents == contents => {
            return Ok(());
        }
        _ => (),
    }
    if mode == Mode::Verify {
        Err(format!("`{}` is not up-to-date", path.display()))?;
    }
    eprintln!("updating {}", path.display());
    fs::write(path, contents)?;
    Ok(())
}

fn reformat(text: impl std::fmt::Display) -> Result<String> {
    let mut rustfmt = Command::new("rustfmt")
        .arg("--config-path")
        .arg(project_root().join("rustfmt.toml"))
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()?;
    write!(rustfmt.stdin.take().unwrap(), "{}", text)?;
    let output = rustfmt.wait_with_output()?;
    let stdout = String::from_utf8(output.stdout)?;
    let preamble = "Generated file, do not edit by hand, see `crate/ra_tools/src/codegen`";
    Ok(format!("//! {}\n\n{}", preamble, stdout))
}

fn extract_comment_blocks(text: &str) -> Vec<Vec<String>> {
    do_extract_comment_blocks(text, false)
}

fn extract_comment_blocks_with_empty_lines(text: &str) -> Vec<Vec<String>> {
    do_extract_comment_blocks(text, true)
}

fn do_extract_comment_blocks(text: &str, allow_blocks_with_empty_lins: bool) -> Vec<Vec<String>> {
    let mut res = Vec::new();

    let prefix = "// ";
    let lines = text.lines().map(str::trim_start);

    let mut block = vec![];
    for line in lines {
        if line == "//" && allow_blocks_with_empty_lins {
            block.push(String::new());
            continue;
        }

        let is_comment = line.starts_with(prefix);
        if is_comment {
            block.push(line[prefix.len()..].to_string());
        } else {
            if !block.is_empty() {
                res.push(mem::replace(&mut block, Vec::new()))
            }
        }
    }
    if !block.is_empty() {
        res.push(mem::replace(&mut block, Vec::new()))
    }
    res
}
<commit_msg>Ignore line-endings when checking generated files for freshness<commit_after>//! We use code generation heavily in rust-analyzer.
//!
//! Rather then doing it via proc-macros, we use old-school way of just dumping
//! the source code.
//!
//! This module's submodules define specific bits that we generate.

mod gen_syntax;
mod gen_parser_tests;
mod gen_assists_docs;

use std::{
    fs,
    io::Write,
    mem,
    path::Path,
    process::{Command, Stdio},
};

use crate::{project_root, Result};

pub use self::{
    gen_assists_docs::generate_assists_docs, gen_parser_tests::generate_parser_tests,
    gen_syntax::generate_syntax,
};

pub const GRAMMAR: &str = "crates/ra_syntax/src/grammar.ron";
const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar";
const OK_INLINE_TESTS_DIR: &str = "crates/ra_syntax/test_data/parser/inline/ok";
const ERR_INLINE_TESTS_DIR: &str = "crates/ra_syntax/test_data/parser/inline/err";

pub const SYNTAX_KINDS: &str = "crates/ra_parser/src/syntax_kind/generated.rs";
pub const AST: &str = "crates/ra_syntax/src/ast/generated.rs";

const ASSISTS_DIR: &str = "crates/ra_assists/src/assists";
const ASSISTS_TESTS: &str = "crates/ra_assists/src/doc_tests/generated.rs";
const ASSISTS_DOCS: &str = "docs/user/assists.md";

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Mode {
    Overwrite,
    Verify,
}

/// A helper to update file on disk if it has changed.
/// With verify = false,
fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> {
    match fs::read_to_string(path) {
        Ok(ref old_contents) if normalize(old_contents) == normalize(contents) => {
            return Ok(());
        }
        _ => (),
    }
    if mode == Mode::Verify {
        Err(format!("`{}` is not up-to-date", path.display()))?;
    }
    eprintln!("updating {}", path.display());
    fs::write(path, contents)?;
    return Ok(());

    fn normalize(s: &str) -> String {
        s.replace("\r\n", "\n")
    }
}

fn reformat(text: impl std::fmt::Display) -> Result<String> {
    let mut rustfmt = Command::new("rustfmt")
        .arg("--config-path")
        .arg(project_root().join("rustfmt.toml"))
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()?;
    write!(rustfmt.stdin.take().unwrap(), "{}", text)?;
    let output = rustfmt.wait_with_output()?;
    let stdout = String::from_utf8(output.stdout)?;
    let preamble = "Generated file, do not edit by hand, see `crate/ra_tools/src/codegen`";
    Ok(format!("//! {}\n\n{}", preamble, stdout))
}

fn extract_comment_blocks(text: &str) -> Vec<Vec<String>> {
    do_extract_comment_blocks(text, false)
}

fn extract_comment_blocks_with_empty_lines(text: &str) -> Vec<Vec<String>> {
    do_extract_comment_blocks(text, true)
}

fn do_extract_comment_blocks(text: &str, allow_blocks_with_empty_lins: bool) -> Vec<Vec<String>> {
    let mut res = Vec::new();

    let prefix = "// ";
    let lines = text.lines().map(str::trim_start);

    let mut block = vec![];
    for line in lines {
        if line == "//" && allow_blocks_with_empty_lins {
            block.push(String::new());
            continue;
        }

        let is_comment = line.starts_with(prefix);
        if is_comment {
            block.push(line[prefix.len()..].to_string());
        } else {
            if !block.is_empty() {
                res.push(mem::replace(&mut block, Vec::new()))
            }
        }
    }
    if !block.is_empty() {
        res.push(mem::replace(&mut block, Vec::new()))
    }
    res
}
<|endoftext|>


<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 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)?;

        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 10 seconds for the release to make it into the crates.io index…");
                sleep(Duration::from_secs(10));
            }
        }

        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![];
            if next == *version {
                return true;
            }
        } 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();
            if next == *version {
                return true;
            }
        }

        false
    }
}
<commit_msg>xtask: Double the sleep after macro crate release<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();

        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)?;

        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![];
            if next == *version {
                return true;
            }
        } 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();
            if next == *version {
                return true;
            }
        }

        false
    }
}
<|endoftext|>


<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 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)?;

        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![];
            if next == *version {
                return true;
            }
        } 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();
            if next == *version {
                return true;
            }
        }

        false
    }
}
<commit_msg>xtask: Fix git command syntax<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();

        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)?;

        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![];
            if next == *version {
                return true;
            }
        } 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();
            if next == *version {
                return true;
            }
        }

        false
    }
}
<|endoftext|>


<commit_before>// run-pass
#![allow(dead_code)]

use std::mem::{size_of, transmute};

union U1<A: Copy> {
    a: A,
}

union U2<A: Copy, B: Copy> {
    a: A,
    b: B,
}

fn main() {
    // Unions do not participate in niche-filling/non-zero optimization...
    assert!(size_of::<Option<U2<&u8, u8>>>() > size_of::<U2<&u8, u8>>());
    assert!(size_of::<Option<U2<&u8, ()>>>() > size_of::<U2<&u8, ()>>());

    // ...even when theoretically possible:
    assert!(size_of::<Option<U1<&u8>>>() > size_of::<U1<&u8>>());
    assert!(size_of::<Option<U2<&u8, &u8>>>() > size_of::<U2<&u8, &u8>>());

    // The unused bits of the () variant can have any value.
    let zeroed: U2<&u8, ()> = unsafe { transmute(std::ptr::null::<u8>()) };

    if let None = Some(zeroed) {
        panic!()
    }
}
<commit_msg>Document purpose of union-nonzero test<commit_after>// run-pass
#![allow(dead_code)]

// Tests that unions aren't subject to unsafe non-zero/niche-filling optimizations.
//
// For example, if a union `U` can contain both a `&T` and a `*const T`, there's definitely no
// bit-value that an `Option<U>` could reuse as `None`; this test makes sure that isn't done.
//
// Secondly, this tests the status quo to not apply such optimizations to types containing unions
// even if they're theoretically possible. (discussion: https://github.com/rust-lang/rust/issues/36394)
//
// Notably this nails down part of the behavior that `MaybeUninit` assumes: that a
// `Option<MaybeUninit<&u8>>` does not take advantage of non-zero optimization, and thus is a safe
// construct.

use std::mem::{size_of, transmute};

union U1<A: Copy> {
    a: A,
}

union U2<A: Copy, B: Copy> {
    a: A,
    b: B,
}

fn main() {
    // Unions do not participate in niche-filling/non-zero optimization...
    assert!(size_of::<Option<U2<&u8, u8>>>() > size_of::<U2<&u8, u8>>());
    assert!(size_of::<Option<U2<&u8, ()>>>() > size_of::<U2<&u8, ()>>());

    // ...even when theoretically possible:
    assert!(size_of::<Option<U1<&u8>>>() > size_of::<U1<&u8>>());
    assert!(size_of::<Option<U2<&u8, &u8>>>() > size_of::<U2<&u8, &u8>>());

    // The unused bits of the () variant can have any value.
    let zeroed: U2<&u8, ()> = unsafe { transmute(std::ptr::null::<u8>()) };

    if let None = Some(zeroed) {
        panic!()
    }
}
<|endoftext|>


<commit_before>use fall_tree::{File, AstNode, NodeType, Node, dump_file, TextRange, TextUnit};
use fall_tree::visitor::{Visitor, NodeVisitor};
use fall_tree::search::{child_of_type, ancestors, find_leaf_at_offset};
use fall_tree::edit::TextEdit;
use ::{ast, LANG_FALL, RefKind, CallKind};
use ::syntax::*;

mod actions;

use self::actions::{ACTIONS, ContextActionId};

pub fn parse(text: String) -> File {
    LANG_FALL.parse(text)
}

pub fn tree_as_text(file: &File) -> String {
    dump_file(file)
}

type Spans = Vec<(TextRange, &'static str)>;

pub fn highlight(file: &File) -> Spans {
    let file = ast(file);
    let mut spans = vec![];
    Visitor(&mut spans)
        .visit_nodes(&[EOL_COMMENT], |spans, node| {
            colorize_node(node, "comment", spans)
        })
        .visit_nodes(&[HASH_STRING, SIMPLE_STRING], |spans, node| {
            colorize_node(node, "string", spans)
        })
        .visit_nodes(&[RULE, VERBATIM, TOKENIZER, AST, NODE, CLASS, PUB, EXAMPLE], |spans, node| {
            colorize_node(node, "keyword", spans)
        })
        .visit_nodes(&[ERROR], |spans, node| {
            let range = if node.range().is_empty() {
                TextRange::from_len(node.range().start(), TextUnit::from_usize(1))
            } else {
                node.range()
            };
            spans.push((range, "error"))
        })
        .visit_nodes(&[PARAMETER], |spans, node| colorize_node(node, "value_parameter", spans))
        .visit::<LexRule, _>(|spans, rule| colorize_child(rule.node(), IDENT, "token", spans))
        .visit::<SynRule, _>(|spans, rule| colorize_child(rule.node(), IDENT, "rule", spans))
        .visit::<AstNodeDef, _>(|spans, rule| colorize_child(rule.node(), IDENT, "rule", spans))
        .visit::<RefExpr, _>(|spans, ref_| {
            let color = match ref_.resolve() {
                Some(RefKind::Token(_)) => "token",
                Some(RefKind::RuleReference { .. }) => "rule",
                Some(RefKind::Param(..)) => "value_parameter",
                None => "unresolved"
            };
            colorize_node(ref_.node(), color, spans)
        })
        .visit::<CallExpr, _>(|spans, call| {
            let color = match call.kind() {
                Err(_) => "unresolved",
                Ok(CallKind::RuleCall(..)) => "rule",
                _ => "builtin"
            };

            colorize_child(call.node(), IDENT, color, spans);
            colorize_child(call.node(), LANGLE, color, spans);
            colorize_child(call.node(), RANGLE, color, spans);
        })
        .visit::<Attributes, _>(|spans, attrs| {
            colorize_node(attrs.node(), "meta", spans)
        })
        .walk_recursively_children_first(file.node());
    return spans;

    fn colorize_node(node: Node, color: &'static str, spans: &mut Spans) {
        spans.push((node.range(), color))
    }

    fn colorize_child(node: Node, child: NodeType, color: &'static str, spans: &mut Spans) {
        if let Some(child) = child_of_type(node, child) {
            colorize_node(child, color, spans);
        }
    }
}

pub fn extend_selection(file: &File, range: TextRange) -> Option<TextRange> {
    let node = match find_node_at_range(&file, range) {
        Some(node) => node,
        None => return None,
    };

    match ancestors(node).skip_while(|n| n.range() == range).next() {
        None => None,
        Some(parent) => Some(parent.range()),
    }
}

pub fn collect_applicable_context_actions(file: &File, offset: TextUnit) -> Vec<ContextActionId> {
    ACTIONS.iter()
        .filter(|action| action.apply(file, offset).is_some())
        .map(|action| action.id())
        .collect()
}

pub fn apply_context_action(file: &File, offset: TextUnit, action_id: &str) -> TextEdit {
    let action = ACTIONS.iter().find(|action| action.id().0 == action_id).unwrap();
    action.apply(file, offset).unwrap().into_text_edit()
}

pub struct FileStructureNode {
    pub name: String,
    pub range: TextRange,
    pub children: Vec<FileStructureNode>
}

pub fn file_structure(file: &File) -> Vec<FileStructureNode> {
    let mut nodes = Vec::new();
    Visitor(&mut nodes)
        .visit::<SynRule, _>(|nodes, rule| {
            if let Some(name) = rule.name() {
                nodes.push(FileStructureNode {
                    name: name.to_string(),
                    range: rule.node().range(),
                    children: Vec::new(),
                })
            }
        })
        .walk_recursively_children_first(file.root());

    nodes
}

fn find_node_at_range(file: &File, range: TextRange) -> Option<Node> {
    if range.is_empty() {
        return try_find_non_ws_node_at_offset(file, range.start())
    }

    let root = file.root();
    let (left, right) = match (
        find_leaf_at_offset(root, range.start()).right_biased(),
        find_leaf_at_offset(root, range.end()).left_biased()
    ) {
        (Some(l), Some(r)) => (l, r),
        _ => return None
    };

    Some(common_ancestor(left, right))
}

fn try_find_non_ws_node_at_offset(file: &File, offset: TextUnit) -> Option<Node> {
    let leaves = find_leaf_at_offset(file.root(), offset);
    if let Some(leaf) = leaves.left_biased() {
        if file.language().node_type_info(leaf.ty()).whitespace_like {
            return leaves.right_biased()
        }
    }

    leaves.left_biased()
}

fn common_ancestor<'f>(n1: Node<'f>, n2: Node<'f>) -> Node<'f> {
    for p in ancestors(n1) {
        if ancestors(n2).any(|a| a == p) {
            return p
        }
    }
    panic!("Can't find common ancestor of {:?} and {:?}", n1, n2)
}
<commit_msg>Start diagnostics<commit_after>use fall_tree::{File, AstNode, NodeType, Node, dump_file, TextRange, TextUnit};
use fall_tree::visitor::{Visitor, NodeVisitor};
use fall_tree::search::{child_of_type, ancestors, find_leaf_at_offset};
use fall_tree::edit::TextEdit;
use ::{ast, LANG_FALL, RefKind, CallKind};
use ::syntax::*;

mod actions;

use self::actions::{ACTIONS, ContextActionId};

pub fn parse(text: String) -> File {
    LANG_FALL.parse(text)
}

pub fn tree_as_text(file: &File) -> String {
    dump_file(file)
}

type Spans = Vec<(TextRange, &'static str)>;

pub fn highlight(file: &File) -> Spans {
    let file = ast(file);
    let mut spans = vec![];
    Visitor(&mut spans)
        .visit_nodes(&[EOL_COMMENT], |spans, node| {
            colorize_node(node, "comment", spans)
        })
        .visit_nodes(&[HASH_STRING, SIMPLE_STRING], |spans, node| {
            colorize_node(node, "string", spans)
        })
        .visit_nodes(&[RULE, VERBATIM, TOKENIZER, AST, NODE, CLASS, PUB, EXAMPLE], |spans, node| {
            colorize_node(node, "keyword", spans)
        })
        .visit_nodes(&[ERROR], |spans, node| {
            let range = if node.range().is_empty() {
                TextRange::from_len(node.range().start(), TextUnit::from_usize(1))
            } else {
                node.range()
            };
            spans.push((range, "error"))
        })
        .visit_nodes(&[PARAMETER], |spans, node| colorize_node(node, "value_parameter", spans))
        .visit::<LexRule, _>(|spans, rule| colorize_child(rule.node(), IDENT, "token", spans))
        .visit::<SynRule, _>(|spans, rule| colorize_child(rule.node(), IDENT, "rule", spans))
        .visit::<AstNodeDef, _>(|spans, rule| colorize_child(rule.node(), IDENT, "rule", spans))
        .visit::<RefExpr, _>(|spans, ref_| {
            let color = match ref_.resolve() {
                Some(RefKind::Token(_)) => "token",
                Some(RefKind::RuleReference { .. }) => "rule",
                Some(RefKind::Param(..)) => "value_parameter",
                None => "rule"
            };
            colorize_node(ref_.node(), color, spans)
        })
        .visit::<CallExpr, _>(|spans, call| {
            let color = match call.kind() {
                Err(_) => "unresolved",
                Ok(CallKind::RuleCall(..)) => "rule",
                _ => "builtin"
            };

            colorize_child(call.node(), IDENT, color, spans);
            colorize_child(call.node(), LANGLE, color, spans);
            colorize_child(call.node(), RANGLE, color, spans);
        })
        .visit::<Attributes, _>(|spans, attrs| {
            colorize_node(attrs.node(), "meta", spans)
        })
        .walk_recursively_children_first(file.node());
    return spans;

    fn colorize_node(node: Node, color: &'static str, spans: &mut Spans) {
        spans.push((node.range(), color))
    }

    fn colorize_child(node: Node, child: NodeType, color: &'static str, spans: &mut Spans) {
        if let Some(child) = child_of_type(node, child) {
            colorize_node(child, color, spans);
        }
    }
}

pub fn extend_selection(file: &File, range: TextRange) -> Option<TextRange> {
    let node = match find_node_at_range(&file, range) {
        Some(node) => node,
        None => return None,
    };

    match ancestors(node).skip_while(|n| n.range() == range).next() {
        None => None,
        Some(parent) => Some(parent.range()),
    }
}

pub fn collect_applicable_context_actions(file: &File, offset: TextUnit) -> Vec<ContextActionId> {
    ACTIONS.iter()
        .filter(|action| action.apply(file, offset).is_some())
        .map(|action| action.id())
        .collect()
}

pub fn apply_context_action(file: &File, offset: TextUnit, action_id: &str) -> TextEdit {
    let action = ACTIONS.iter().find(|action| action.id().0 == action_id).unwrap();
    action.apply(file, offset).unwrap().into_text_edit()
}

pub struct FileStructureNode {
    pub name: String,
    pub range: TextRange,
    pub children: Vec<FileStructureNode>
}

pub fn file_structure(file: &File) -> Vec<FileStructureNode> {
    let mut nodes = Vec::new();
    Visitor(&mut nodes)
        .visit::<SynRule, _>(|nodes, rule| {
            if let Some(name) = rule.name() {
                nodes.push(FileStructureNode {
                    name: name.to_string(),
                    range: rule.node().range(),
                    children: Vec::new(),
                })
            }
        })
        .walk_recursively_children_first(file.root());

    nodes
}

pub enum Severity {
    Error,
    Warning
}

pub struct Diagnostic {
    pub range: TextRange,
    pub severity: Severity,
    pub text: String
}

pub fn diagnostics(file: &File) -> Vec<Diagnostic> {
    let mut result: Vec<Diagnostic> = Vec::new();
    Visitor(&mut result)
        .visit::<RefExpr, _>(|acc, ref_| {
            if ref_.resolve().is_none() {
                acc.push(Diagnostic {
                    range: ref_.node().range(),
                    severity: Severity::Error,
                    text: "Unresolved reference".to_string(),
                })
            }
        })
        .walk_recursively_children_first(file.root());

    result
}

fn find_node_at_range(file: &File, range: TextRange) -> Option<Node> {
    if range.is_empty() {
        return try_find_non_ws_node_at_offset(file, range.start())
    }

    let root = file.root();
    let (left, right) = match (
        find_leaf_at_offset(root, range.start()).right_biased(),
        find_leaf_at_offset(root, range.end()).left_biased()
    ) {
        (Some(l), Some(r)) => (l, r),
        _ => return None
    };

    Some(common_ancestor(left, right))
}

fn try_find_non_ws_node_at_offset(file: &File, offset: TextUnit) -> Option<Node> {
    let leaves = find_leaf_at_offset(file.root(), offset);
    if let Some(leaf) = leaves.left_biased() {
        if file.language().node_type_info(leaf.ty()).whitespace_like {
            return leaves.right_biased()
        }
    }

    leaves.left_biased()
}

fn common_ancestor<'f>(n1: Node<'f>, n2: Node<'f>) -> Node<'f> {
    for p in ancestors(n1) {
        if ancestors(n2).any(|a| a == p) {
            return p
        }
    }
    panic!("Can't find common ancestor of {:?} and {:?}", n1, n2)
}
<|endoftext|>


<commit_before>//! Loading executable binaries into Falcon
//!
//! ```
//! # use falcon::error::*;
//! use falcon::loader::Elf;
//! use falcon::loader::Loader;
//! use std::path::Path;
//!
//! # fn example () -> Result<()> {
//! // Load an elf for analysis
//! let elf = Elf::from_file(Path::new("test_binaries/simple-0/simple-0"))?;
//! // Lift a program from the elf
//! let program = elf.program()?;
//! for function in program.functions() {
//!     println!("0x{:08x}: {}", function.address(), function.name());
//! }
//! # Ok(())
//! # }
//! ```

use error::*;
use il;
use memory;
use std::fmt;
use types::Architecture;

mod elf;
mod json;
mod pe;

pub use self::elf::*;
pub use self::json::*;
pub use self::pe::*;

/// A declared entry point for a function.
#[derive(Clone, Debug, PartialEq)]
pub struct FunctionEntry {
    address: u64,
    name: Option<String>
}

impl FunctionEntry {
    /// Create a new `FunctionEntry`.
    ///
    /// If no name is provided: `sup_{:X}` will be used to name the function.
    pub fn new(address: u64, name: Option<String>) -> FunctionEntry {
        FunctionEntry {
            address: address,
            name: name
        }
    }

    /// Get the address for this `FunctionEntry`.
    pub fn address(&self) -> u64 {
        self.address
    }

    /// Get the name for this `FunctionEntry`.
    pub fn name(&self) -> Option<&str> {
        self.name.as_ref().map(|s| s.as_str())
    }
}

impl fmt::Display for FunctionEntry {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.name {
            Some(ref name) => write!(f, "{} -> {:X}", name, self.address),
            None => write!(f, "{:X}", self.address)
        }
    }
}

/// Generic trait for all loaders
pub trait Loader: Clone {
    /// Get a model of the memory contained in the binary
    fn memory(&self) -> Result<memory::backing::Memory>;

    /// Get addresses for known function entries
    fn function_entries(&self) -> Result<Vec<FunctionEntry>>;

    /// The address program execution should begin at
    fn program_entry(&self) -> u64;

    /// Get the architecture of the binary
    fn architecture(&self) -> Result<Architecture>;

    /// Lift just one function from the executable
    fn function(&self, address: u64) -> Result<il::Function> {
        let translator = self.architecture()?.translator();
        let memory = self.memory()?;
        Ok(translator.translate_function(&memory, address)?)
    }

    /// Lift executable into an il::Program
    fn program(&self) -> Result<il::Program> {
        // Get out architecture-specific translator
        let translator = self.architecture()?.translator();

        // Create a mapping of the file memory
        let memory = self.memory()?;

        let mut program = il::Program::new();

        for function_entry in self.function_entries()? {
            let address = function_entry.address();
            // Ensure this memory is marked executable
            if memory.permissions(address)
                     .map_or(false, |p|
                        p.contains(memory::MemoryPermissions::EXECUTE)) {
                let mut function = translator.translate_function(&memory, address)?;
                function.set_name(function_entry.name().map(|n| n.to_string()));
                program.add_function(function);
            }
        }

        Ok(program)
    }
}<commit_msg>Adding program_recursive() loader method<commit_after>//! Loading executable binaries into Falcon
//!
//! ```
//! # use falcon::error::*;
//! use falcon::loader::Elf;
//! use falcon::loader::Loader;
//! use std::path::Path;
//!
//! # fn example () -> Result<()> {
//! // Load an elf for analysis
//! let elf = Elf::from_file(Path::new("test_binaries/simple-0/simple-0"))?;
//! // Lift a program from the elf
//! let program = elf.program()?;
//! for function in program.functions() {
//!     println!("0x{:08x}: {}", function.address(), function.name());
//! }
//! # Ok(())
//! # }
//! ```

use error::*;
use executor::eval;
use il;
use memory;
use std::collections::HashSet;
use std::fmt;
use types::Architecture;

mod elf;
mod json;
mod pe;

pub use self::elf::*;
pub use self::json::*;
pub use self::pe::*;

/// A declared entry point for a function.
#[derive(Clone, Debug, PartialEq)]
pub struct FunctionEntry {
    address: u64,
    name: Option<String>
}

impl FunctionEntry {
    /// Create a new `FunctionEntry`.
    ///
    /// If no name is provided: `sup_{:X}` will be used to name the function.
    pub fn new(address: u64, name: Option<String>) -> FunctionEntry {
        FunctionEntry {
            address: address,
            name: name
        }
    }

    /// Get the address for this `FunctionEntry`.
    pub fn address(&self) -> u64 {
        self.address
    }

    /// Get the name for this `FunctionEntry`.
    pub fn name(&self) -> Option<&str> {
        self.name.as_ref().map(|s| s.as_str())
    }
}

impl fmt::Display for FunctionEntry {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.name {
            Some(ref name) => write!(f, "FunctionEntry({} -> 0x{:X})", name, self.address),
            None => write!(f, "FunctionEntry(0x{:X})", self.address)
        }
    }
}

/// Generic trait for all loaders
pub trait Loader: Clone {
    /// Get a model of the memory contained in the binary
    fn memory(&self) -> Result<memory::backing::Memory>;

    /// Get addresses for known function entries
    fn function_entries(&self) -> Result<Vec<FunctionEntry>>;

    /// The address program execution should begin at
    fn program_entry(&self) -> u64;

    /// Get the architecture of the binary
    fn architecture(&self) -> Result<Architecture>;

    /// Lift just one function from the executable
    fn function(&self, address: u64) -> Result<il::Function> {
        let translator = self.architecture()?.translator();
        let memory = self.memory()?;
        Ok(translator.translate_function(&memory, address)?)
    }

    /// Lift executable into an il::Program
    fn program(&self) -> Result<il::Program> {
        // Get out architecture-specific translator
        let translator = self.architecture()?.translator();

        // Create a mapping of the file memory
        let memory = self.memory()?;

        let mut program = il::Program::new();

        for function_entry in self.function_entries()? {
            let address = function_entry.address();
            // Ensure this memory is marked executable
            if memory.permissions(address)
                     .map_or(false, |p|
                        p.contains(memory::MemoryPermissions::EXECUTE)) {
                let mut function = translator.translate_function(&memory, address)?;
                function.set_name(function_entry.name().map(|n| n.to_string()));
                program.add_function(function);
            }
        }

        Ok(program)
    }

    /// Lift executable into an `il::Program`, while recursively resolving branch
    /// targets into functions.
    fn program_recursive(&self) -> Result<il::Program> {
        fn call_targets(function: &il::Function) -> Result<Vec<u64>> {
            let call_targets =
                function.blocks().iter().fold(Vec::new(), |mut call_targets, block| {
                    block.instructions().iter().for_each(|instruction|
                        match *instruction.operation() {
                            il::Operation::Branch { ref target } => {
                                eval(target).ok().map(|constant|
                                    call_targets.push(constant.value()));
                            }
                            _ => {}
                        });
                    call_targets
                });
            Ok(call_targets)
        }

        let mut program = self.program()?;

        let mut processed = HashSet::new();

        loop {
            let function_addresses =
                program.functions_map()
                    .into_iter()
                    .map(|(_, function)| function.address())
                    .collect::<Vec<u64>>();

            let addresses = {
                let functions =
                    program.functions()
                        .into_iter()
                        .filter(|function| !processed.contains(&function.address()))
                        .collect::<Vec<&il::Function>>();

                functions.iter()
                    .for_each(|function| {
                        processed.insert(function.address());
                    });

                let addresses =
                    functions.into_iter()
                        .fold(HashSet::new(), |mut targets, function| {
                            call_targets(function).unwrap()
                                .into_iter()
                                .for_each(|target| {
                                    targets.insert(target);
                                });
                            targets
                        })
                        .into_iter()
                        .filter(|address|
                            !function_addresses.contains(address))
                        .collect::<Vec<u64>>();

                if addresses.is_empty() {
                    break;
                }

                addresses
            };

            for address in addresses {
                program.add_function(self.function(address)?);
            }
        }

        Ok(program)
    }
}<|endoftext|>