<commit_before>#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum XmlSpace {
    Default,
    Preserve
}

/// Implements `xml:space` handling per the SVG spec
///
/// Normalizes a string as it comes out of the XML parser's handler
/// for character data according to the SVG rules in
/// https://www.w3.org/TR/SVG/text.html#WhiteSpace
pub fn xml_space_normalize(mode: XmlSpace, s: &str) -> String {
    match mode {
        XmlSpace::Default => normalize_default(s),
        XmlSpace::Preserve => normalize_preserve(s)
    }
}

fn normalize_default(s: &str) -> String {
    #[derive(PartialEq)]
    enum State {
        SpacesAtStart,
        NonSpace,
        SpacesAtMiddle
    }

    let mut result = String::new();
    let mut state = State::SpacesAtStart;

    for ch in s.chars() {
        match ch {
            '\n' => continue,
            '\t' | ' ' => {
                match state {
                    State::SpacesAtStart  => continue,
                    State::NonSpace       => { state = State::SpacesAtMiddle; },
                    State::SpacesAtMiddle => continue,
                }
            },

            _ => {
                if state == State::SpacesAtMiddle {
                    result.push(' ');
                }

                result.push(ch);
                state = State::NonSpace;
            }
        }
    }

    result
}

fn normalize_preserve(s: &str) -> String {
    s.chars()
        .map(|ch| {
            match ch {
                '\n' | '\t' => ' ',

                c => c
            }
        })
        .collect()
}

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

    #[test]
    fn xml_space_default() {
        assert_eq!(xml_space_normalize(XmlSpace::Default, "\n    WS example\n    indented lines\n  "),
                   "WS example indented lines");
        assert_eq!(xml_space_normalize(XmlSpace::Default, "\n  \t  \tWS \t\t\texample\n  \t  indented lines\t\t  \n  "),
                   "WS example indented lines");

        assert_eq!(xml_space_normalize(XmlSpace::Default, "\nWS example\nnon-indented lines\n  "),
                   "WS examplenon-indented lines");
    }

    #[test]
    fn xml_space_preserve() {
        assert_eq!(xml_space_normalize(XmlSpace::Preserve, "\n    WS example\n    indented lines\n  "),
                   "     WS example     indented lines   ");
        assert_eq!(xml_space_normalize(XmlSpace::Preserve, "\n  \t  \tWS \t\t\texample\n  \t  indented lines\t\t  \n  "),
                   "       WS    example      indented lines       ");

    }
}
<commit_msg>space.rs: Include the relevant portions of the spec<commit_after>#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum XmlSpace {
    Default,
    Preserve
}

/// Implements `xml:space` handling per the SVG spec
///
/// Normalizes a string as it comes out of the XML parser's handler
/// for character data according to the SVG rules in
/// https://www.w3.org/TR/SVG/text.html#WhiteSpace
pub fn xml_space_normalize(mode: XmlSpace, s: &str) -> String {
    match mode {
        XmlSpace::Default => normalize_default(s),
        XmlSpace::Preserve => normalize_preserve(s)
    }
}

// From https://www.w3.org/TR/SVG/text.html#WhiteSpace
//
// When xml:space="default", the SVG user agent will do the following
// using a copy of the original character data content. First, it will
// remove all newline characters. Then it will convert all tab
// characters into space characters. Then, it will strip off all
// leading and trailing space characters. Then, all contiguous space
// characters will be consolidated.
fn normalize_default(s: &str) -> String {
    #[derive(PartialEq)]
    enum State {
        SpacesAtStart,
        NonSpace,
        SpacesAtMiddle
    }

    let mut result = String::new();
    let mut state = State::SpacesAtStart;

    for ch in s.chars() {
        match ch {
            '\n' => continue,
            '\t' | ' ' => {
                match state {
                    State::SpacesAtStart  => continue,
                    State::NonSpace       => { state = State::SpacesAtMiddle; },
                    State::SpacesAtMiddle => continue,
                }
            },

            _ => {
                if state == State::SpacesAtMiddle {
                    result.push(' ');
                }

                result.push(ch);
                state = State::NonSpace;
            }
        }
    }

    result
}

// From https://www.w3.org/TR/SVG/text.html#WhiteSpace
//
// When xml:space="preserve", the SVG user agent will do the following
// using a copy of the original character data content. It will
// convert all newline and tab characters into space characters. Then,
// it will draw all space characters, including leading, trailing and
// multiple contiguous space characters. Thus, when drawn with
// xml:space="preserve", the string "a   b" (three spaces between "a"
// and "b") will produce a larger separation between "a" and "b" than
// "a b" (one space between "a" and "b").
fn normalize_preserve(s: &str) -> String {
    s.chars()
        .map(|ch| {
            match ch {
                '\n' | '\t' => ' ',

                c => c
            }
        })
        .collect()
}

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

    #[test]
    fn xml_space_default() {
        assert_eq!(xml_space_normalize(XmlSpace::Default, "\n    WS example\n    indented lines\n  "),
                   "WS example indented lines");
        assert_eq!(xml_space_normalize(XmlSpace::Default, "\n  \t  \tWS \t\t\texample\n  \t  indented lines\t\t  \n  "),
                   "WS example indented lines");

        assert_eq!(xml_space_normalize(XmlSpace::Default, "\nWS example\nnon-indented lines\n  "),
                   "WS examplenon-indented lines");
    }

    #[test]
    fn xml_space_preserve() {
        assert_eq!(xml_space_normalize(XmlSpace::Preserve, "\n    WS example\n    indented lines\n  "),
                   "     WS example     indented lines   ");
        assert_eq!(xml_space_normalize(XmlSpace::Preserve, "\n  \t  \tWS \t\t\texample\n  \t  indented lines\t\t  \n  "),
                   "       WS    example      indented lines       ");

    }
}
<|endoftext|>


<commit_before>#[macro_escape];

use std::str::CharIterator;

struct ParseBranch {
    matches: ~[u8],
    result: Option<~str>,
    children: ~[ParseBranch],
}

impl ParseBranch {
    fn new() -> ParseBranch {
        ParseBranch {
            matches: ~[],
            result: None,
            children: ~[],
        }
    }
}

pub fn branchify(options: &[(&str, &str)], case_sensitive: bool) -> ~[ParseBranch] {
    let mut root = ParseBranch::new();

    fn go_down_moses(branch: &mut ParseBranch, mut chariter: CharIterator, result: &str, case_sensitive: bool) {
        match chariter.next() {
            Some(c) => {
                let first_case = if case_sensitive { c as u8 } else { c.to_ascii().to_upper().to_byte() };
                foreach next_branch in branch.children.mut_iter() {
                    if next_branch.matches[0] == first_case {
                        go_down_moses(next_branch, chariter, result, case_sensitive);
                        return;
                    }
                }
                let mut subbranch = ParseBranch::new();
                subbranch.matches.push(first_case);
                if !case_sensitive {
                    let second_case = c.to_ascii().to_lower().to_byte();
                    if first_case != second_case {
                        subbranch.matches.push(second_case);
                    }
                }
                branch.children.push(subbranch);
                go_down_moses(&mut branch.children[branch.children.len() - 1], chariter, result, case_sensitive);
            },
            None => {
                assert!(branch.result.is_none());
                branch.result = Some(result.to_owned());
            },
        }
    };

    foreach &(key, result) in options.iter() {
        go_down_moses(&mut root, key.iter(), result, case_sensitive);
    }

    root.children
}

macro_rules! branchify(
    (case sensitive, $($key:expr => $value:ident),*) => (
        ::branchify::branchify([$(($key, stringify!($value))),*], true)
    );
    (case insensitive, $($key:expr => $value:ident),*) => (
        branchify([$(($key, stringify!($value))),*], false)
    );
)

/// Prints the contents to stdout.
///
/// :param branches: the branches to search through
/// :param indent: the level of indentation (each level representing four leading spaces)
/// :param read_call: the function call to read a byte
/// :param end: the byte which marks the end of the sequence
/// :param max_len: the maximum length a value may be before giving up and returning ``None``
/// :param valid: the function call to if a byte ``b`` is valid
/// :param unknown: the expression to call for an unknown value; in this string, ``%s`` will be
///         replaced with an expression (literal or non-literal) evaluating to a ``~str`` (it is
///         ``%s`` only, not arbitrary format strings)
pub fn generate_branchified_method(
        writer: @Writer,
        branches: &[ParseBranch],
        indent: uint,
        read_call: &str,
        end: &str,
        max_len: &str,
        valid: &str,
        unknown: &str) {

    // Write Formatted
    macro_rules! wf(($($x:tt)*) => ({
        let s = fmt!($($x)*);
        writer.write(indentstr.as_bytes());
        writer.write(s.as_bytes());
        writer.write(bytes!("\n"));
    }))

    fn r(writer: @Writer, branch: &ParseBranch, prefix: &str, indent: uint, read_call: &str,
            end: &str, max_len: &str, valid: &str, unknown: &str) {
        let indentstr = " ".repeat(indent * 4);
        foreach &c in branch.matches.iter() {
            let next_prefix = fmt!("%s%c", prefix, c as char);
            wf!("Some(b) if b == '%c' as u8 => match %s {", c as char, read_call);
            foreach b in branch.children.iter() {
                r(writer, b, next_prefix, indent + 1, read_call, end, max_len, valid, unknown);
            }
            match branch.result {
                Some(ref result) => wf!("    Some(b) if b == SP => return Some(%s),", *result),
                None => wf!("    Some(b) if b == SP => return Some(%s),",
                                unknown.replace("%s", fmt!("~\"%s\"", next_prefix))),
            }
            wf!("    Some(b) if %s => (~\"%s\", b),", valid, next_prefix);
            wf!("    _ => return None,");
            wf!("},");
        }
    }
    let indentstr = " ".repeat(indent * 4);
    wf!("let (s, next_byte) = match %s {", read_call);
    foreach b in branches.iter() {
        r(writer, b, "", indent + 1, read_call, end, max_len, valid, unknown);
    }
    wf!("    Some(b) if %s => (~\"\", b),", valid);
    wf!("    _ => return None,");
    wf!("};");
    wf!("// OK, that didn't pan out. Let's read the rest and see what we get.");
    wf!("let mut s = s;");
    wf!("s.push_char(next_byte as char);");
    wf!("loop {");
    wf!("    match %s {", read_call);
    wf!("        Some(b) if b == %s => return Some(%s),", end, unknown.replace("%s", "s"));
    wf!("        Some(b) if %s => {", valid);
    wf!("            if s.len() == %s {", max_len);
    wf!("                // Too long; bad request");
    wf!("                return None;");
    wf!("            }");
    wf!("            s.push_char(b as char);");
    wf!("        },");
    wf!("        _ => return None,");
    wf!("    }");
    wf!("}");
}
<commit_msg>Do less allocation in the big generated match blocks.<commit_after>#[macro_escape];

use std::str::CharIterator;

struct ParseBranch {
    matches: ~[u8],
    result: Option<~str>,
    children: ~[ParseBranch],
}

impl ParseBranch {
    fn new() -> ParseBranch {
        ParseBranch {
            matches: ~[],
            result: None,
            children: ~[],
        }
    }
}

pub fn branchify(options: &[(&str, &str)], case_sensitive: bool) -> ~[ParseBranch] {
    let mut root = ParseBranch::new();

    fn go_down_moses(branch: &mut ParseBranch, mut chariter: CharIterator, result: &str, case_sensitive: bool) {
        match chariter.next() {
            Some(c) => {
                let first_case = if case_sensitive { c as u8 } else { c.to_ascii().to_upper().to_byte() };
                foreach next_branch in branch.children.mut_iter() {
                    if next_branch.matches[0] == first_case {
                        go_down_moses(next_branch, chariter, result, case_sensitive);
                        return;
                    }
                }
                let mut subbranch = ParseBranch::new();
                subbranch.matches.push(first_case);
                if !case_sensitive {
                    let second_case = c.to_ascii().to_lower().to_byte();
                    if first_case != second_case {
                        subbranch.matches.push(second_case);
                    }
                }
                branch.children.push(subbranch);
                go_down_moses(&mut branch.children[branch.children.len() - 1], chariter, result, case_sensitive);
            },
            None => {
                assert!(branch.result.is_none());
                branch.result = Some(result.to_owned());
            },
        }
    };

    foreach &(key, result) in options.iter() {
        go_down_moses(&mut root, key.iter(), result, case_sensitive);
    }

    root.children
}

macro_rules! branchify(
    (case sensitive, $($key:expr => $value:ident),*) => (
        ::branchify::branchify([$(($key, stringify!($value))),*], true)
    );
    (case insensitive, $($key:expr => $value:ident),*) => (
        branchify([$(($key, stringify!($value))),*], false)
    );
)

/// Prints the contents to stdout.
///
/// :param branches: the branches to search through
/// :param indent: the level of indentation (each level representing four leading spaces)
/// :param read_call: the function call to read a byte
/// :param end: the byte which marks the end of the sequence
/// :param max_len: the maximum length a value may be before giving up and returning ``None``
/// :param valid: the function call to if a byte ``b`` is valid
/// :param unknown: the expression to call for an unknown value; in this string, ``%s`` will be
///         replaced with an expression (literal or non-literal) evaluating to a ``~str`` (it is
///         ``%s`` only, not arbitrary format strings)
pub fn generate_branchified_method(
        writer: @Writer,
        branches: &[ParseBranch],
        indent: uint,
        read_call: &str,
        end: &str,
        max_len: &str,
        valid: &str,
        unknown: &str) {

    // Write Formatted
    macro_rules! wf(($($x:tt)*) => ({
        let s = fmt!($($x)*);
        writer.write(indentstr.as_bytes());
        writer.write(s.as_bytes());
        writer.write(bytes!("\n"));
    }))

    fn r(writer: @Writer, branch: &ParseBranch, prefix: &str, indent: uint, read_call: &str,
            end: &str, max_len: &str, valid: &str, unknown: &str) {
        let indentstr = " ".repeat(indent * 4);
        foreach &c in branch.matches.iter() {
            let next_prefix = fmt!("%s%c", prefix, c as char);
            wf!("Some(b) if b == '%c' as u8 => match %s {", c as char, read_call);
            foreach b in branch.children.iter() {
                r(writer, b, next_prefix, indent + 1, read_call, end, max_len, valid, unknown);
            }
            match branch.result {
                Some(ref result) => wf!("    Some(b) if b == SP => return Some(%s),", *result),
                None => wf!("    Some(b) if b == SP => return Some(%s),",
                                unknown.replace("%s", fmt!("~\"%s\"", next_prefix))),
            }
            wf!("    Some(b) if %s => (\"%s\", b),", valid, next_prefix);
            wf!("    _ => return None,");
            wf!("},");
        }
    }
    let indentstr = " ".repeat(indent * 4);
    wf!("let (s, next_byte) = match %s {", read_call);
    foreach b in branches.iter() {
        r(writer, b, "", indent + 1, read_call, end, max_len, valid, unknown);
    }
    wf!("    Some(b) if %s => (\"\", b),", valid);
    wf!("    _ => return None,");
    wf!("};");
    wf!("// OK, that didn't pan out. Let's read the rest and see what we get.");
    wf!("let mut s = s.to_owned();");
    wf!("s.push_char(next_byte as char);");
    wf!("loop {");
    wf!("    match %s {", read_call);
    wf!("        Some(b) if b == %s => return Some(%s),", end, unknown.replace("%s", "s"));
    wf!("        Some(b) if %s => {", valid);
    wf!("            if s.len() == %s {", max_len);
    wf!("                // Too long; bad request");
    wf!("                return None;");
    wf!("            }");
    wf!("            s.push_char(b as char);");
    wf!("        },");
    wf!("        _ => return None,");
    wf!("    }");
    wf!("}");
}
<|endoftext|>


<commit_before>// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use db::migration::Migrator;

use error::Result;

pub fn migrate(migrator: &mut Migrator) -> Result<()> {
    migrator
        .migrate("originsrv",
                 r#"CREATE SEQUENCE IF NOT EXISTS origin_package_id_seq;"#)?;
    migrator
        .migrate("originsrv",
                 r#"CREATE TABLE origin_packages (
                    id bigint PRIMARY KEY DEFAULT next_id_v1('origin_package_id_seq'),
                    origin_id bigint REFERENCES origins(id),
                    owner_id bigint,
                    name text,
                    ident text UNIQUE,
                    checksum text,
                    manifest text,
                    config text,
                    target text,
                    deps text,
                    tdeps text,
                    exposes text,
                    scheduler_sync bool DEFAULT false,
                    created_at timestamptz DEFAULT now(),
                    updated_at timestamptz
             )"#)?;
    migrator.migrate("originsrv",
                 r#"CREATE OR REPLACE FUNCTION insert_origin_package_v1 (
                    op_origin_id bigint,
                    op_owner_id bigint,
                    op_name text,
                    op_ident text,
                    op_checksum text,
                    op_manifest text,
                    op_config text,
                    op_target text,
                    op_deps text,
                    op_tdeps text,
                    op_exposes text
                 ) RETURNS SETOF origin_packages AS $$
                     DECLARE
                        inserted_package origin_packages;
                        channel_id bigint;
                     BEGIN
                         INSERT INTO origin_packages (origin_id, owner_id, name, ident, checksum, manifest, config, target, deps, tdeps, exposes)
                                VALUES (op_origin_id, op_owner_id, op_name, op_ident, op_checksum, op_manifest, op_config, op_target, op_deps, op_tdeps, op_exposes)
                                RETURNING * into inserted_package;

                         SELECT id FROM origin_channels WHERE origin_id = op_origin_id AND name = 'unstable' INTO channel_id;
                         PERFORM promote_origin_package_v1(channel_id, inserted_package.id);

                         RETURN NEXT inserted_package;
                         RETURN;
                     END
                 $$ LANGUAGE plpgsql VOLATILE"#)?;
    migrator
        .migrate("originsrv",
                 r#"CREATE OR REPLACE FUNCTION get_origin_package_v1 (
                    op_ident text
                 ) RETURNS SETOF origin_packages AS $$
                    BEGIN
                        RETURN QUERY SELECT * FROM origin_packages WHERE ident = op_ident;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                 r#"CREATE OR REPLACE FUNCTION get_origin_package_latest_v1 (
                    op_ident text,
                    op_target text
                 ) RETURNS SETOF origin_packages AS $$
                    BEGIN
                        RETURN QUERY SELECT * FROM origin_packages WHERE ident LIKE (op_ident  || '/%') AND target = op_target;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION get_origin_package_versions_for_origin_v1 (
                    op_origin text,
                    op_pkg text
                 ) RETURNS TABLE(version text, release_count bigint) AS $$
                    BEGIN
                        RETURN QUERY SELECT p.partial_ident[3] AS version, COUNT(p.partial_ident[4]) AS release_count FROM (SELECT regexp_split_to_array(op.ident, '/') AS partial_ident FROM origin_packages op INNER JOIN origins o ON o.id = op.origin_id WHERE o.name = op_origin AND op.name = op_pkg) AS p GROUP BY version;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION get_origin_packages_for_origin_v1 (
                    op_ident text,
                    op_limit bigint,
                    op_offset bigint
                 ) RETURNS TABLE(total_count bigint, ident text) AS $$
                    BEGIN
                        RETURN QUERY SELECT COUNT(*) OVER () AS total_count, origin_packages.ident FROM origin_packages WHERE origin_packages.ident LIKE (op_ident  || '%')
                          ORDER BY ident ASC
                          LIMIT op_limit OFFSET op_offset;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION get_origin_packages_for_origin_distinct_v1 (
                    op_ident text,
                    op_limit bigint,
                    op_offset bigint
                 ) RETURNS TABLE(total_count bigint, ident text) AS $$
                    BEGIN
                        RETURN QUERY SELECT COUNT(p.partial_ident[1] || '/' || p.partial_ident[2]) OVER () AS total_count, p.partial_ident[1] || '/' || p.partial_ident[2] AS ident
                        FROM (SELECT regexp_split_to_array(op.ident, '/') as partial_ident FROM origin_packages op WHERE op.ident LIKE ('%' || op_ident || '%')) AS p
                        GROUP BY (p.partial_ident[1] || '/' || p.partial_ident[2])
                        LIMIT op_limit OFFSET op_offset;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION get_origin_packages_unique_for_origin_v1 (
                   op_origin text,
                   op_limit bigint,
                   op_offset bigint
                 ) RETURNS TABLE(total_count bigint, name text) AS $$
                    BEGIN
                        RETURN QUERY SELECT COUNT(*) OVER () AS total_count, origin_packages.name FROM origins INNER JOIN origin_packages ON origins.id = origin_packages.origin_id WHERE origins.name = op_origin
                          GROUP BY origin_packages.name
                          ORDER BY origin_packages.name ASC
                          LIMIT op_limit OFFSET op_offset;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION search_origin_packages_for_origin_v1 (
                   op_origin text,
                   op_query text,
                   op_limit bigint,
                   op_offset bigint
                 ) RETURNS TABLE(total_count bigint, ident text) AS $$
                    BEGIN
                        RETURN QUERY SELECT COUNT(*) OVER () AS total_count, origin_packages.ident FROM origins INNER JOIN origin_packages ON origins.id = origin_packages.origin_id WHERE origins.name = op_origin and origin_packages.name LIKE ('%' || op_query || '%')
                          ORDER BY ident ASC
                          LIMIT op_limit OFFSET op_offset;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION search_origin_packages_for_origin_distinct_v1 (
                   op_origin text,
                   op_query text,
                   op_limit bigint,
                   op_offset bigint
                 ) RETURNS TABLE(total_count bigint, ident text) AS $$
                    BEGIN
                        RETURN QUERY SELECT COUNT(p.partial_ident[1] || '/' || p.partial_ident[2]) OVER () AS total_count, p.partial_ident[1] || '/' || p.partial_ident[2] AS ident
                        FROM (SELECT regexp_split_to_array(op.ident, '/') as partial_ident FROM origins o INNER JOIN origin_packages op ON o.id = op.origin_id WHERE o.name = op_origin AND op.name LIKE ('%' || op_query || '%')) AS p
                        GROUP BY (p.partial_ident[1] || '/' || p.partial_ident[2])
                        LIMIT op_limit OFFSET op_offset;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION search_all_origin_packages_dynamic_v1 (
                    op_query text,
                    op_limit bigint,
                    op_offset bigint
                    ) RETURNS TABLE(total_count bigint, ident text) AS $$
                    DECLARE
                      schema RECORD;
                    BEGIN
                      FOR schema IN EXECUTE
                        format(
                          'SELECT schema_name FROM information_schema.schemata WHERE left(schema_name, 6) = %L',
                          'shard_'
                        )
                      LOOP
                        RETURN QUERY EXECUTE
                        format('SELECT COUNT(p.partial_ident[1] || %L || p.partial_ident[2]) OVER () AS total_count, p.partial_ident[1] || %L || p.partial_ident[2] AS ident FROM (SELECT regexp_split_to_array(op.ident, %L) as partial_ident FROM %I.origins o INNER JOIN %I.origin_packages op ON o.id = op.origin_id WHERE o.name LIKE (%L || %L || %L) OR op.name LIKE (%L || %L || %L)) AS p GROUP BY (p.partial_ident[1] || %L || p.partial_ident[2]) LIMIT %L OFFSET %L', '/', '/', '/', schema.schema_name, schema.schema_name, '%', op_query, '%', '%', op_query, '%', '/', op_limit, op_offset);
                      END LOOP;
                    END;
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION sync_packages_v1() RETURNS TABLE(account_id bigint, package_id bigint, package_ident text, package_deps text) AS $$
                    BEGIN
                        RETURN QUERY SELECT origin_packages.owner_id, origin_packages.id, origin_packages.ident, origin_packages.deps FROM origin_packages WHERE origin_packages.scheduler_sync = false;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION set_packages_sync_v1(in_package_id bigint) RETURNS VOID AS $$
                    BEGIN
                        UPDATE origin_packages SET scheduler_sync = true WHERE id = in_package_id;
                    END
                    $$ LANGUAGE plpgsql VOLATILE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION get_origin_package_versions_for_origin_v2 (
                    op_origin text,
                    op_pkg text
                 ) RETURNS TABLE(version text, release_count bigint, latest text) AS $$
                    BEGIN
                        RETURN QUERY SELECT p.partial_ident[3] AS version, COUNT(p.partial_ident[4]) AS release_count, MAX(p.partial_ident[4]) as latest FROM (SELECT regexp_split_to_array(op.ident, '/') AS partial_ident FROM origin_packages op INNER JOIN origins o ON o.id = op.origin_id WHERE o.name = op_origin AND op.name = op_pkg) AS p GROUP BY version;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                 r#"CREATE OR REPLACE FUNCTION get_origin_package_latest_v2 (
                    op_ident text,
                    op_target text
                 ) RETURNS SETOF origin_packages AS $$
                    BEGIN
                        RETURN QUERY SELECT * FROM origin_packages WHERE ident LIKE (op_ident  || '%') AND target = op_target;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    Ok(())
}
<commit_msg>change migrator prefix for latest function change<commit_after>// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use db::migration::Migrator;

use error::Result;

pub fn migrate(migrator: &mut Migrator) -> Result<()> {
    migrator
        .migrate("originsrv",
                 r#"CREATE SEQUENCE IF NOT EXISTS origin_package_id_seq;"#)?;
    migrator
        .migrate("originsrv",
                 r#"CREATE TABLE origin_packages (
                    id bigint PRIMARY KEY DEFAULT next_id_v1('origin_package_id_seq'),
                    origin_id bigint REFERENCES origins(id),
                    owner_id bigint,
                    name text,
                    ident text UNIQUE,
                    checksum text,
                    manifest text,
                    config text,
                    target text,
                    deps text,
                    tdeps text,
                    exposes text,
                    scheduler_sync bool DEFAULT false,
                    created_at timestamptz DEFAULT now(),
                    updated_at timestamptz
             )"#)?;
    migrator.migrate("originsrv",
                 r#"CREATE OR REPLACE FUNCTION insert_origin_package_v1 (
                    op_origin_id bigint,
                    op_owner_id bigint,
                    op_name text,
                    op_ident text,
                    op_checksum text,
                    op_manifest text,
                    op_config text,
                    op_target text,
                    op_deps text,
                    op_tdeps text,
                    op_exposes text
                 ) RETURNS SETOF origin_packages AS $$
                     DECLARE
                        inserted_package origin_packages;
                        channel_id bigint;
                     BEGIN
                         INSERT INTO origin_packages (origin_id, owner_id, name, ident, checksum, manifest, config, target, deps, tdeps, exposes)
                                VALUES (op_origin_id, op_owner_id, op_name, op_ident, op_checksum, op_manifest, op_config, op_target, op_deps, op_tdeps, op_exposes)
                                RETURNING * into inserted_package;

                         SELECT id FROM origin_channels WHERE origin_id = op_origin_id AND name = 'unstable' INTO channel_id;
                         PERFORM promote_origin_package_v1(channel_id, inserted_package.id);

                         RETURN NEXT inserted_package;
                         RETURN;
                     END
                 $$ LANGUAGE plpgsql VOLATILE"#)?;
    migrator
        .migrate("originsrv",
                 r#"CREATE OR REPLACE FUNCTION get_origin_package_v1 (
                    op_ident text
                 ) RETURNS SETOF origin_packages AS $$
                    BEGIN
                        RETURN QUERY SELECT * FROM origin_packages WHERE ident = op_ident;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                 r#"CREATE OR REPLACE FUNCTION get_origin_package_latest_v1 (
                    op_ident text,
                    op_target text
                 ) RETURNS SETOF origin_packages AS $$
                    BEGIN
                        RETURN QUERY SELECT * FROM origin_packages WHERE ident LIKE (op_ident  || '/%') AND target = op_target;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION get_origin_package_versions_for_origin_v1 (
                    op_origin text,
                    op_pkg text
                 ) RETURNS TABLE(version text, release_count bigint) AS $$
                    BEGIN
                        RETURN QUERY SELECT p.partial_ident[3] AS version, COUNT(p.partial_ident[4]) AS release_count FROM (SELECT regexp_split_to_array(op.ident, '/') AS partial_ident FROM origin_packages op INNER JOIN origins o ON o.id = op.origin_id WHERE o.name = op_origin AND op.name = op_pkg) AS p GROUP BY version;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION get_origin_packages_for_origin_v1 (
                    op_ident text,
                    op_limit bigint,
                    op_offset bigint
                 ) RETURNS TABLE(total_count bigint, ident text) AS $$
                    BEGIN
                        RETURN QUERY SELECT COUNT(*) OVER () AS total_count, origin_packages.ident FROM origin_packages WHERE origin_packages.ident LIKE (op_ident  || '%')
                          ORDER BY ident ASC
                          LIMIT op_limit OFFSET op_offset;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION get_origin_packages_for_origin_distinct_v1 (
                    op_ident text,
                    op_limit bigint,
                    op_offset bigint
                 ) RETURNS TABLE(total_count bigint, ident text) AS $$
                    BEGIN
                        RETURN QUERY SELECT COUNT(p.partial_ident[1] || '/' || p.partial_ident[2]) OVER () AS total_count, p.partial_ident[1] || '/' || p.partial_ident[2] AS ident
                        FROM (SELECT regexp_split_to_array(op.ident, '/') as partial_ident FROM origin_packages op WHERE op.ident LIKE ('%' || op_ident || '%')) AS p
                        GROUP BY (p.partial_ident[1] || '/' || p.partial_ident[2])
                        LIMIT op_limit OFFSET op_offset;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION get_origin_packages_unique_for_origin_v1 (
                   op_origin text,
                   op_limit bigint,
                   op_offset bigint
                 ) RETURNS TABLE(total_count bigint, name text) AS $$
                    BEGIN
                        RETURN QUERY SELECT COUNT(*) OVER () AS total_count, origin_packages.name FROM origins INNER JOIN origin_packages ON origins.id = origin_packages.origin_id WHERE origins.name = op_origin
                          GROUP BY origin_packages.name
                          ORDER BY origin_packages.name ASC
                          LIMIT op_limit OFFSET op_offset;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION search_origin_packages_for_origin_v1 (
                   op_origin text,
                   op_query text,
                   op_limit bigint,
                   op_offset bigint
                 ) RETURNS TABLE(total_count bigint, ident text) AS $$
                    BEGIN
                        RETURN QUERY SELECT COUNT(*) OVER () AS total_count, origin_packages.ident FROM origins INNER JOIN origin_packages ON origins.id = origin_packages.origin_id WHERE origins.name = op_origin and origin_packages.name LIKE ('%' || op_query || '%')
                          ORDER BY ident ASC
                          LIMIT op_limit OFFSET op_offset;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION search_origin_packages_for_origin_distinct_v1 (
                   op_origin text,
                   op_query text,
                   op_limit bigint,
                   op_offset bigint
                 ) RETURNS TABLE(total_count bigint, ident text) AS $$
                    BEGIN
                        RETURN QUERY SELECT COUNT(p.partial_ident[1] || '/' || p.partial_ident[2]) OVER () AS total_count, p.partial_ident[1] || '/' || p.partial_ident[2] AS ident
                        FROM (SELECT regexp_split_to_array(op.ident, '/') as partial_ident FROM origins o INNER JOIN origin_packages op ON o.id = op.origin_id WHERE o.name = op_origin AND op.name LIKE ('%' || op_query || '%')) AS p
                        GROUP BY (p.partial_ident[1] || '/' || p.partial_ident[2])
                        LIMIT op_limit OFFSET op_offset;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION search_all_origin_packages_dynamic_v1 (
                    op_query text,
                    op_limit bigint,
                    op_offset bigint
                    ) RETURNS TABLE(total_count bigint, ident text) AS $$
                    DECLARE
                      schema RECORD;
                    BEGIN
                      FOR schema IN EXECUTE
                        format(
                          'SELECT schema_name FROM information_schema.schemata WHERE left(schema_name, 6) = %L',
                          'shard_'
                        )
                      LOOP
                        RETURN QUERY EXECUTE
                        format('SELECT COUNT(p.partial_ident[1] || %L || p.partial_ident[2]) OVER () AS total_count, p.partial_ident[1] || %L || p.partial_ident[2] AS ident FROM (SELECT regexp_split_to_array(op.ident, %L) as partial_ident FROM %I.origins o INNER JOIN %I.origin_packages op ON o.id = op.origin_id WHERE o.name LIKE (%L || %L || %L) OR op.name LIKE (%L || %L || %L)) AS p GROUP BY (p.partial_ident[1] || %L || p.partial_ident[2]) LIMIT %L OFFSET %L', '/', '/', '/', schema.schema_name, schema.schema_name, '%', op_query, '%', '%', op_query, '%', '/', op_limit, op_offset);
                      END LOOP;
                    END;
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION sync_packages_v1() RETURNS TABLE(account_id bigint, package_id bigint, package_ident text, package_deps text) AS $$
                    BEGIN
                        RETURN QUERY SELECT origin_packages.owner_id, origin_packages.id, origin_packages.ident, origin_packages.deps FROM origin_packages WHERE origin_packages.scheduler_sync = false;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION set_packages_sync_v1(in_package_id bigint) RETURNS VOID AS $$
                    BEGIN
                        UPDATE origin_packages SET scheduler_sync = true WHERE id = in_package_id;
                    END
                    $$ LANGUAGE plpgsql VOLATILE"#)?;
    migrator.migrate("originsrv",
                     r#"CREATE OR REPLACE FUNCTION get_origin_package_versions_for_origin_v2 (
                    op_origin text,
                    op_pkg text
                 ) RETURNS TABLE(version text, release_count bigint, latest text) AS $$
                    BEGIN
                        RETURN QUERY SELECT p.partial_ident[3] AS version, COUNT(p.partial_ident[4]) AS release_count, MAX(p.partial_ident[4]) as latest FROM (SELECT regexp_split_to_array(op.ident, '/') AS partial_ident FROM origin_packages op INNER JOIN origins o ON o.id = op.origin_id WHERE o.name = op_origin AND op.name = op_pkg) AS p GROUP BY version;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    migrator.migrate("originsrv-v3",
                 r#"CREATE OR REPLACE FUNCTION get_origin_package_latest_v2 (
                    op_ident text,
                    op_target text
                 ) RETURNS SETOF origin_packages AS $$
                    BEGIN
                        RETURN QUERY SELECT * FROM origin_packages WHERE ident LIKE (op_ident  || '%') AND target = op_target;
                        RETURN;
                    END
                    $$ LANGUAGE plpgsql STABLE"#)?;
    Ok(())
}
<|endoftext|>


<commit_before>//! A slow, but clear reference implementation of SeaHash.
//!
//! # Specification
//!
//! The input buffer is padded with null bytes until the length is divisible by 8.
//!
//! We start out with state
//!
//! ```notest
//! a = 0x16f11fe89b0d677c
//! b = 0xb480a793d8e6c86c
//! c = 0x6fe2e5aaf078ebc9
//! d = 0x14f994a4c5259381
//! ```
//!
//! From the stream, we read one 64-bit block (in little-endian) at a time.  This number, `n`,
//! determines the new the new state by:
//!
//! ```notest
//! a' = b
//! b' = c
//! c' = d
//! d  = g(a ⊕ n)
//! ```
//!
//! `g(x)` is defined as `g(x) = h(j(h(j(x)))))` with `h(x) = x ≫ 32` and `j(x) ≡ px (mod 2^64)`
//! with `p = 0x7ed0e9fa0d94a33`.
//!
//! Let the final state be `(x, y, z, w)`. Then the final result is given by `H = g(x ⊕ y ⊕ z ⊕ w ⊕
//! l)` where `l` is the number of bytes in the original buffer.

use diffuse;

/// Read an integer in little-endian.
fn read_int(int: &[u8]) -> u64 {
    debug_assert!(int.len() <= 8, "The buffer length of the integer must be less than or equal to \
                  the one of an u64.");

    // Start at 0.
    let mut x = 0;
    for &i in int.iter().rev() {
        // Shift up a byte.
        x <<= 8;
        // Set the lower byte.
        x |= i as u64;
    }

    x
}

/// A hash state.
struct State {
    /// The `a` substate.
    a: u64,
    /// The `b` substate.
    b: u64,
    /// The `c` substate.
    c: u64,
    /// The `d` substate.
    d: u64,
}

impl State {
    /// Write a 64-bit integer to the state.
    fn write_u64(&mut self, x: u64) {
        let mut a = self.a;

        // Mix `x` into `a`.
        a = diffuse(a ^ x);

        //  Rotate around.
        //  _______________________
        // |                       v
        // a <---- b <---- c <---- d
        self.a = self.b;
        self.b = self.c;
        self.c = self.d;
        self.d = a;
    }

    /// Calculate the final hash.
    fn finish(self, total: usize) -> u64 {
        // Even though XORing is commutative, it doesn't matter, because the state vector's initial
        // components are mutually distinct, and thus swapping even and odd chunks will affect the
        // result, because it is sensitive to the initial condition. To add discreteness, we
        // diffuse.
        diffuse(self.a
            ^ self.b
            ^ self.c
            ^ self.d
            // We XOR in the number of written bytes to make it zero-sensitive when excessive bytes
            // are written (0u32.0u8 ≠ 0u16.0u8).
            ^ total as u64
        )
    }

    fn with_seed(seed: u64) -> State {
        State {
            // These values are randomly generated.
            a: seed.wrapping_mul(0x16f11fe89b0d677c),
            b: seed.wrapping_mul(0xb480a793d8e6c86c),
            c: seed.wrapping_mul(0x6fe2e5aaf078ebc9),
            d: seed.wrapping_mul(0x14f994a4c5259381),
        }
    }
}

/// A reference implementation of SeaHash.
///
/// This is bloody slow when compared to the optimized version. This is because SeaHash was
/// specifically designed to take all sorts of hardware and software hacks into account to achieve
/// maximal performance, but this makes code significantly less readable. As such, this version has
/// only one goal: to make the algorithm readable and understandable.
#[inline]
pub fn hash(buf: &[u8]) -> u64 {
    hash_seeded(buf, 1)
}

/// The seeded version of the reference implementation.
#[inline]
pub fn hash_seeded(buf: &[u8], seed: u64) -> u64 {
    // Initialize the state.
    let mut state = State::with_seed(seed);

    // Partition the rounded down buffer to chunks of 8 bytes, and iterate over them. The last
    // block might not be 8 bytes long.
    for int in buf.chunks(8) {
        // Read the chunk into an integer and write into the state.
        state.write_u64(read_int(int));
    }

    // Finish the hash state and return the final value.
    state.finish(buf.len())
}
<commit_msg>Specify the semantics of seeded SeaHash.<commit_after>//! A slow, but clear reference implementation of SeaHash.
//!
//! # Specification
//!
//! The input buffer is padded with null bytes until the length is divisible by 8.
//!
//! We start out with state
//!
//! ```notest
//! a = 0x16f11fe89b0d677c
//! b = 0xb480a793d8e6c86c
//! c = 0x6fe2e5aaf078ebc9
//! d = 0x14f994a4c5259381
//! ```
//!
//! If a seed is given, each of the initial state component are modularly multiplied by the seed.
//!
//! From the stream, we read one 64-bit block (in little-endian) at a time.  This number, `n`,
//! determines the new the new state by:
//!
//! ```notest
//! a' = b
//! b' = c
//! c' = d
//! d  = g(a ⊕ n)
//! ```
//!
//! `g(x)` is defined as `g(x) = h(j(h(j(x)))))` with `h(x) = x ≫ 32` and `j(x) ≡ px (mod 2^64)`
//! with `p = 0x7ed0e9fa0d94a33`.
//!
//! Let the final state be `(x, y, z, w)`. Then the final result is given by `H = g(x ⊕ y ⊕ z ⊕ w ⊕
//! l)` where `l` is the number of bytes in the original buffer.

use diffuse;

/// Read an integer in little-endian.
fn read_int(int: &[u8]) -> u64 {
    debug_assert!(int.len() <= 8, "The buffer length of the integer must be less than or equal to \
                  the one of an u64.");

    // Start at 0.
    let mut x = 0;
    for &i in int.iter().rev() {
        // Shift up a byte.
        x <<= 8;
        // Set the lower byte.
        x |= i as u64;
    }

    x
}

/// A hash state.
struct State {
    /// The `a` substate.
    a: u64,
    /// The `b` substate.
    b: u64,
    /// The `c` substate.
    c: u64,
    /// The `d` substate.
    d: u64,
}

impl State {
    /// Write a 64-bit integer to the state.
    fn write_u64(&mut self, x: u64) {
        let mut a = self.a;

        // Mix `x` into `a`.
        a = diffuse(a ^ x);

        //  Rotate around.
        //  _______________________
        // |                       v
        // a <---- b <---- c <---- d
        self.a = self.b;
        self.b = self.c;
        self.c = self.d;
        self.d = a;
    }

    /// Calculate the final hash.
    fn finish(self, total: usize) -> u64 {
        // Even though XORing is commutative, it doesn't matter, because the state vector's initial
        // components are mutually distinct, and thus swapping even and odd chunks will affect the
        // result, because it is sensitive to the initial condition. To add discreteness, we
        // diffuse.
        diffuse(self.a
            ^ self.b
            ^ self.c
            ^ self.d
            // We XOR in the number of written bytes to make it zero-sensitive when excessive bytes
            // are written (0u32.0u8 ≠ 0u16.0u8).
            ^ total as u64
        )
    }

    fn with_seed(seed: u64) -> State {
        State {
            // These values are randomly generated.
            a: seed.wrapping_mul(0x16f11fe89b0d677c),
            b: seed.wrapping_mul(0xb480a793d8e6c86c),
            c: seed.wrapping_mul(0x6fe2e5aaf078ebc9),
            d: seed.wrapping_mul(0x14f994a4c5259381),
        }
    }
}

/// A reference implementation of SeaHash.
///
/// This is bloody slow when compared to the optimized version. This is because SeaHash was
/// specifically designed to take all sorts of hardware and software hacks into account to achieve
/// maximal performance, but this makes code significantly less readable. As such, this version has
/// only one goal: to make the algorithm readable and understandable.
#[inline]
pub fn hash(buf: &[u8]) -> u64 {
    hash_seeded(buf, 1)
}

/// The seeded version of the reference implementation.
#[inline]
pub fn hash_seeded(buf: &[u8], seed: u64) -> u64 {
    // Initialize the state.
    let mut state = State::with_seed(seed);

    // Partition the rounded down buffer to chunks of 8 bytes, and iterate over them. The last
    // block might not be 8 bytes long.
    for int in buf.chunks(8) {
        // Read the chunk into an integer and write into the state.
        state.write_u64(read_int(int));
    }

    // Finish the hash state and return the final value.
    state.finish(buf.len())
}
<|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::{Metadata, Package},
    cmd,
    util::ask_yes_no,
    GithubConfig, 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";

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

        if publish_only {
            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 tag to remote repository…");
        if cmd!("git ls-remote --tags {remote} {tag}").read()?.is_empty() {
            cmd!("git push {remote} {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 mut input = String::new();
        let stdin = stdin();

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

        let mut handle = stdin.lock();

        while let _ = handle.read_line(&mut input)? {
            match input.trim().to_ascii_lowercase().as_str() {
                "c" | "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()?;

        println!("Pushing commit…");
        cmd!("git push").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: Exit feedback loop on EOF<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::{Metadata, Package},
    cmd,
    util::ask_yes_no,
    GithubConfig, 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";

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

        if publish_only {
            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 tag to remote repository…");
        if cmd!("git ls-remote --tags {remote} {tag}").read()?.is_empty() {
            cmd!("git push {remote} {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" | "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()?;

        println!("Pushing commit…");
        cmd!("git push").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|>