<commit_before>//! Implements a command for managing releases.
use std::path::{Path, PathBuf};
use std::ffi::OsStr;
use std::collections::HashSet;

use clap::{App, AppSettings, Arg, ArgMatches};
use walkdir::WalkDir;

use prelude::*;
use api::{Api, NewRelease, FileContents};
use config::Config;
use utils::make_subcommand;
use sourcemaputils::SourceMapProcessor;

pub fn make_app<'a, 'b: 'a>(app: App<'a, 'b>) -> App<'a, 'b> {
    app.about("manage releases on Sentry")
        .setting(AppSettings::SubcommandRequiredElseHelp)
        .arg(Arg::with_name("org")
            .value_name("ORG")
            .long("org")
            .short("o")
            .help("The organization slug"))
        .arg(Arg::with_name("project")
            .value_name("PROJECT")
            .long("project")
            .short("p")
            .help("The project slug"))
        .subcommand(make_subcommand("new")
            .about("Create a new release")
            .arg(Arg::with_name("version")
                .value_name("VERSION")
                .required(true)
                .index(1)
                .help("The version identifier for this release"))
            .arg(Arg::with_name("ref")
                .long("ref")
                .value_name("REF")
                .help("Optional commit reference (commit hash)"))
            .arg(Arg::with_name("url")
                .long("url")
                .value_name("URL")
                .help("Optional URL to the release for information purposes")))
        .subcommand(make_subcommand("delete")
            .about("Delete a release")
            .arg(Arg::with_name("version")
                .value_name("VERSION")
                .required(true)
                .index(1)
                .help("The version to delete")))
        .subcommand(make_subcommand("list").about("list the most recent releases"))
        .subcommand(make_subcommand("files")
            .about("manage release artifact files")
            .setting(AppSettings::SubcommandRequiredElseHelp)
            .arg(Arg::with_name("version")
                .value_name("VERSION")
                .required(true)
                .index(1)
                .help("The release to manage the files of"))
            .subcommand(make_subcommand("list").about("List all release files"))
            .subcommand(make_subcommand("delete")
                .about("Delete a release file")
                .arg(Arg::with_name("all")
                    .short("A")
                    .long("all")
                    .help("deletes all files"))
                .arg(Arg::with_name("names")
                    .value_name("NAMES")
                    .index(1)
                    .multiple(true)
                    .help("a list of filenames to delete.")))
            .subcommand(make_subcommand("upload")
                .about("Uploads a file for a given release")
                .arg(Arg::with_name("headers")
                    .long("header")
                    .short("H")
                    .value_name("KEY VALUE")
                    .multiple(true)
                    .number_of_values(1)
                    .help("Stores a header with this file"))
                .arg(Arg::with_name("path")
                    .value_name("PATH")
                    .index(1)
                    .required(true)
                    .help("The file to upload"))
                .arg(Arg::with_name("name")
                    .index(2)
                    .value_name("NAME")
                    .help("The name of the file on the server.")))
            .subcommand(make_subcommand("upload-sourcemaps")
                .about("Uploads sourcemap information for a given release")
                .arg(Arg::with_name("paths")
                    .value_name("PATHS")
                    .index(1)
                    .required(true)
                    .multiple(true)
                    .help("The files to upload"))
                .arg(Arg::with_name("url_prefix")
                    .short("u")
                    .long("url-prefix")
                    .value_name("PREFIX")
                    .help("The URL prefix to prepend to all filenames"))
                .arg(Arg::with_name("validate")
                    .long("validate")
                    .help("Enable basic sourcemap validation"))
                .arg(Arg::with_name("no_sourcemap_reference")
                    .long("no-sourcemap-reference")
                    .help("Disables the emitting of automatic sourcemap references. \
                            By default the tool will store a 'Sourcemap' header with \
                            minified files so that sourcemaps are located automatically \
                            if the tool can detect a link. If this causes issues it can \
                            be disabled."))
                .arg(Arg::with_name("rewrite")
                    .long("rewrite")
                    .help("Enables rewriting of matching sourcemaps \
                            so that indexed maps are flattened and missing \
                            sources are inlined if possible.  This fundamentally \
                            changes the upload process to be based on sourcemaps \
                            and minified files exclusively and comes in handy for \
                            setups like react-native that generate sourcemaps that \
                            would otherwise not work for sentry."))
                .arg(Arg::with_name("strip_prefix")
                    .long("strip-prefix")
                    .value_name("PREFIX")
                    .multiple(true)
                    .help("When passed all sources that start with the given prefix \
                            will have that prefix stripped from the filename.  This \
                            requires --rewrite to be enabled."))
                .arg(Arg::with_name("strip_common_prefix")
                    .long("strip-common-prefix")
                    .help("Similar to --strip-prefix but strips the most common \
                            prefix on all sources."))
                .arg(Arg::with_name("verbose")
                    .long("verbose")
                    .short("verbose")
                    .help("Enable verbose mode"))
                .arg(Arg::with_name("extensions")
                    .long("ext")
                    .short("x")
                    .value_name("EXT")
                    .multiple(true)
                    .help("Add a file extension to the list of files to upload."))))
}

fn execute_new<'a>(matches: &ArgMatches<'a>,
                   config: &Config,
                   org: &str,
                   project: &str)
                   -> Result<()> {
    let info_rv = Api::new(config).new_release(org,
                     project,
                     &NewRelease {
                         version: matches.value_of("version").unwrap().to_owned(),
                         reference: matches.value_of("ref").map(|x| x.to_owned()),
                         url: matches.value_of("url").map(|x| x.to_owned()),
                     })?;
    println!("Created release {}.", info_rv.version);
    Ok(())
}

fn execute_delete<'a>(matches: &ArgMatches<'a>,
                      config: &Config,
                      org: &str,
                      project: &str)
                      -> Result<()> {
    let version = matches.value_of("version").unwrap();
    if Api::new(config).delete_release(org, project, version)? {
        println!("Deleted release {}!", version);
    } else {
        println!("Did nothing. Release with this version ({}) does not exist.",
                 version);
    }
    Ok(())
}

fn execute_list<'a>(_matches: &ArgMatches<'a>,
                    config: &Config,
                    org: &str,
                    project: &str)
                    -> Result<()> {
    for info in Api::new(config).list_releases(org, project)? {
        println!("[{}] {}: {} ({} new groups)",
                 info.date_released.unwrap_or("              unreleased".into()),
                 info.version,
                 info.reference.unwrap_or("-".into()),
                 info.new_groups);
    }
    Ok(())
}

fn execute_files_list<'a>(_matches: &ArgMatches<'a>,
                          config: &Config,
                          org: &str,
                          project: &str,
                          release: &str)
                          -> Result<()> {
    for artifact in Api::new(config).list_release_files(org, project, release)? {
        print!("{}  ({} bytes)", artifact.name, artifact.size);
        if let Some(sm_ref) = artifact.get_sourcemap_reference() {
            print!("  -> sourcemap: {}", sm_ref);
        }
        println!("");
    }
    Ok(())
}

fn execute_files_delete<'a>(matches: &ArgMatches<'a>,
                            config: &Config,
                            org: &str,
                            project: &str,
                            release: &str)
                            -> Result<()> {
    let files: HashSet<String> = match matches.values_of("names") {
        Some(paths) => paths.map(|x| x.into()).collect(),
        None => HashSet::new(),
    };
    let api = Api::new(config);
    for file in api.list_release_files(org, project, release)? {
        if !(matches.is_present("all") || files.contains(&file.name)) {
            continue;
        }
        if api.delete_release_file(org, project, release, &file.id)? {
            println!("D {}", file.name);
        }
    }
    Ok(())
}

fn execute_files_upload<'a>(matches: &ArgMatches<'a>,
                            config: &Config,
                            org: &str,
                            project: &str,
                            version: &str)
                            -> Result<()> {
    let path = Path::new(matches.value_of("path").unwrap());
    let name = match matches.value_of("name") {
        Some(name) => name,
        None => {
            Path::new(path).file_name()
                .and_then(|x| x.to_str())
                .ok_or("No filename provided.")?
        }
    };
    let mut headers = vec![];
    if let Some(header_list) = matches.values_of("header") {
        for header in header_list {
            if !header.contains(':') {
                fail!("Invalid header. Needs to be in key:value format");
            }
            let mut iter = header.splitn(2, ':');
            let key = iter.next().unwrap();
            let value = iter.next().unwrap();
            headers.push((key.trim().to_string(), value.trim().to_string()));
        }
    };
    if let Some(artifact) = Api::new(config).upload_release_file(org,
                             project,
                             &version,
                             FileContents::FromPath(&path),
                             &name,
                             Some(&headers[..]))? {
        println!("A {}  ({} bytes)", artifact.sha1, artifact.size);
    } else {
        fail!("File already present!");
    }
    Ok(())
}

fn execute_files_upload_sourcemaps<'a>(matches: &ArgMatches<'a>,
                                       config: &Config,
                                       org: &str,
                                       project: &str,
                                       version: &str)
                                       -> Result<()> {
    let api = Api::new(config);
    let release = api.get_release(org, project, version)?.ok_or("release not found")?;
    let url_prefix = matches.value_of("url_prefix").unwrap_or("~").trim_right_matches("/");
    let paths = matches.values_of("paths").unwrap();
    let extensions = match matches.values_of("extensions") {
        Some(matches) => matches.map(|ext| OsStr::new(ext.trim_left_matches("."))).collect(),
        None => {
            vec![OsStr::new("js"), OsStr::new("map"), OsStr::new("jsbundle"), OsStr::new("bundle")]
        }
    };

    let mut processor = SourceMapProcessor::new(matches.is_present("verbose"));

    for path in paths {
        // if we start walking over something that is an actual file then
        // the directory iterator yields that path and terminates.  We
        // handle that case here specifically to figure out what the path is
        // we should strip off.
        let walk_path = PathBuf::from(&path);
        let (base_path, skip_ext_test) = if walk_path.is_file() {
            (walk_path.parent().unwrap(), true)
        } else {
            (walk_path.as_path(), false)
        };

        for dent in WalkDir::new(&walk_path) {
            let dent = dent?;
            if !skip_ext_test {
                let extension = dent.path().extension();
                if !extensions.iter().any(|ext| Some(*ext) == extension) {
                    continue;
                }
            }
            debug!("found: {} ({} bytes)",
                   dent.path().display(),
                   dent.metadata().unwrap().len());
            let local_path = dent.path().strip_prefix(&base_path).unwrap();
            let url = format!("{}/{}", url_prefix, local_path.display());
            processor.add(&url, dent.path())?;
        }
    }

    if matches.is_present("validate") {
        println!("Running with sourcemap validation");
        processor.validate_all()?;
    }

    if matches.is_present("rewrite") {
        let mut prefixes: Vec<&str> = match matches.values_of("strip_prefix") {
            Some(paths) => paths.collect(),
            None => vec![],
        };
        if matches.is_present("strip_common_prefix") {
            prefixes.push("~");
        }
        processor.rewrite(&prefixes)?;
    }

    if !matches.is_present("no_sourcemap_reference") {
        processor.add_sourcemap_references()?;
    }

    println!("Uploading sourcemaps for release {}", release.version);
    processor.upload(&api, &org, &project, &release.version)?;

    Ok(())
}

fn execute_files<'a>(matches: &ArgMatches<'a>,
                     config: &Config,
                     org: &str,
                     project: &str)
                     -> Result<()> {
    let release = matches.value_of("version").unwrap();
    if let Some(sub_matches) = matches.subcommand_matches("list") {
        return execute_files_list(sub_matches, config, org, project, release);
    }
    if let Some(sub_matches) = matches.subcommand_matches("delete") {
        return execute_files_delete(sub_matches, config, org, project, release);
    }
    if let Some(sub_matches) = matches.subcommand_matches("upload") {
        return execute_files_upload(sub_matches, config, org, project, release);
    }
    if let Some(sub_matches) = matches.subcommand_matches("upload-sourcemaps") {
        return execute_files_upload_sourcemaps(sub_matches, config, org, project, release);
    }
    unreachable!();
}

pub fn execute<'a>(matches: &ArgMatches<'a>, config: &Config) -> Result<()> {
    if let Some(sub_matches) = matches.subcommand_matches("new") {
        let (org, project) = config.get_org_and_project(matches)?;
        return execute_new(sub_matches, config, &org, &project);
    }
    if let Some(sub_matches) = matches.subcommand_matches("delete") {
        let (org, project) = config.get_org_and_project(matches)?;
        return execute_delete(sub_matches, config, &org, &project);
    }
    if let Some(sub_matches) = matches.subcommand_matches("list") {
        let (org, project) = config.get_org_and_project(matches)?;
        return execute_list(sub_matches, config, &org, &project);
    }
    if let Some(sub_matches) = matches.subcommand_matches("files") {
        let (org, project) = config.get_org_and_project(matches)?;
        return execute_files(sub_matches, config, &org, &project);
    }
    unreachable!();
}
<commit_msg>Convert backslashes to slashes in URLs on windows<commit_after>//! Implements a command for managing releases.
use std::path::{Path, PathBuf};
use std::ffi::OsStr;
use std::collections::HashSet;

use clap::{App, AppSettings, Arg, ArgMatches};
use walkdir::WalkDir;

use prelude::*;
use api::{Api, NewRelease, FileContents};
use config::Config;
use utils::make_subcommand;
use sourcemaputils::SourceMapProcessor;

pub fn make_app<'a, 'b: 'a>(app: App<'a, 'b>) -> App<'a, 'b> {
    app.about("manage releases on Sentry")
        .setting(AppSettings::SubcommandRequiredElseHelp)
        .arg(Arg::with_name("org")
            .value_name("ORG")
            .long("org")
            .short("o")
            .help("The organization slug"))
        .arg(Arg::with_name("project")
            .value_name("PROJECT")
            .long("project")
            .short("p")
            .help("The project slug"))
        .subcommand(make_subcommand("new")
            .about("Create a new release")
            .arg(Arg::with_name("version")
                .value_name("VERSION")
                .required(true)
                .index(1)
                .help("The version identifier for this release"))
            .arg(Arg::with_name("ref")
                .long("ref")
                .value_name("REF")
                .help("Optional commit reference (commit hash)"))
            .arg(Arg::with_name("url")
                .long("url")
                .value_name("URL")
                .help("Optional URL to the release for information purposes")))
        .subcommand(make_subcommand("delete")
            .about("Delete a release")
            .arg(Arg::with_name("version")
                .value_name("VERSION")
                .required(true)
                .index(1)
                .help("The version to delete")))
        .subcommand(make_subcommand("list").about("list the most recent releases"))
        .subcommand(make_subcommand("files")
            .about("manage release artifact files")
            .setting(AppSettings::SubcommandRequiredElseHelp)
            .arg(Arg::with_name("version")
                .value_name("VERSION")
                .required(true)
                .index(1)
                .help("The release to manage the files of"))
            .subcommand(make_subcommand("list").about("List all release files"))
            .subcommand(make_subcommand("delete")
                .about("Delete a release file")
                .arg(Arg::with_name("all")
                    .short("A")
                    .long("all")
                    .help("deletes all files"))
                .arg(Arg::with_name("names")
                    .value_name("NAMES")
                    .index(1)
                    .multiple(true)
                    .help("a list of filenames to delete.")))
            .subcommand(make_subcommand("upload")
                .about("Uploads a file for a given release")
                .arg(Arg::with_name("headers")
                    .long("header")
                    .short("H")
                    .value_name("KEY VALUE")
                    .multiple(true)
                    .number_of_values(1)
                    .help("Stores a header with this file"))
                .arg(Arg::with_name("path")
                    .value_name("PATH")
                    .index(1)
                    .required(true)
                    .help("The file to upload"))
                .arg(Arg::with_name("name")
                    .index(2)
                    .value_name("NAME")
                    .help("The name of the file on the server.")))
            .subcommand(make_subcommand("upload-sourcemaps")
                .about("Uploads sourcemap information for a given release")
                .arg(Arg::with_name("paths")
                    .value_name("PATHS")
                    .index(1)
                    .required(true)
                    .multiple(true)
                    .help("The files to upload"))
                .arg(Arg::with_name("url_prefix")
                    .short("u")
                    .long("url-prefix")
                    .value_name("PREFIX")
                    .help("The URL prefix to prepend to all filenames"))
                .arg(Arg::with_name("validate")
                    .long("validate")
                    .help("Enable basic sourcemap validation"))
                .arg(Arg::with_name("no_sourcemap_reference")
                    .long("no-sourcemap-reference")
                    .help("Disables the emitting of automatic sourcemap references. \
                            By default the tool will store a 'Sourcemap' header with \
                            minified files so that sourcemaps are located automatically \
                            if the tool can detect a link. If this causes issues it can \
                            be disabled."))
                .arg(Arg::with_name("rewrite")
                    .long("rewrite")
                    .help("Enables rewriting of matching sourcemaps \
                            so that indexed maps are flattened and missing \
                            sources are inlined if possible.  This fundamentally \
                            changes the upload process to be based on sourcemaps \
                            and minified files exclusively and comes in handy for \
                            setups like react-native that generate sourcemaps that \
                            would otherwise not work for sentry."))
                .arg(Arg::with_name("strip_prefix")
                    .long("strip-prefix")
                    .value_name("PREFIX")
                    .multiple(true)
                    .help("When passed all sources that start with the given prefix \
                            will have that prefix stripped from the filename.  This \
                            requires --rewrite to be enabled."))
                .arg(Arg::with_name("strip_common_prefix")
                    .long("strip-common-prefix")
                    .help("Similar to --strip-prefix but strips the most common \
                            prefix on all sources."))
                .arg(Arg::with_name("verbose")
                    .long("verbose")
                    .short("verbose")
                    .help("Enable verbose mode"))
                .arg(Arg::with_name("extensions")
                    .long("ext")
                    .short("x")
                    .value_name("EXT")
                    .multiple(true)
                    .help("Add a file extension to the list of files to upload."))))
}

#[cfg(windows)]
fn path_as_url(path: &Path) -> String {
    path.display().to_string().replace('\\', '/')
}

#[cfg(not(windows))]
fn path_as_url(path: &Path) -> String {
    path.display().to_string()
}

fn execute_new<'a>(matches: &ArgMatches<'a>,
                   config: &Config,
                   org: &str,
                   project: &str)
                   -> Result<()> {
    let info_rv = Api::new(config).new_release(org,
                     project,
                     &NewRelease {
                         version: matches.value_of("version").unwrap().to_owned(),
                         reference: matches.value_of("ref").map(|x| x.to_owned()),
                         url: matches.value_of("url").map(|x| x.to_owned()),
                     })?;
    println!("Created release {}.", info_rv.version);
    Ok(())
}

fn execute_delete<'a>(matches: &ArgMatches<'a>,
                      config: &Config,
                      org: &str,
                      project: &str)
                      -> Result<()> {
    let version = matches.value_of("version").unwrap();
    if Api::new(config).delete_release(org, project, version)? {
        println!("Deleted release {}!", version);
    } else {
        println!("Did nothing. Release with this version ({}) does not exist.",
                 version);
    }
    Ok(())
}

fn execute_list<'a>(_matches: &ArgMatches<'a>,
                    config: &Config,
                    org: &str,
                    project: &str)
                    -> Result<()> {
    for info in Api::new(config).list_releases(org, project)? {
        println!("[{}] {}: {} ({} new groups)",
                 info.date_released.unwrap_or("              unreleased".into()),
                 info.version,
                 info.reference.unwrap_or("-".into()),
                 info.new_groups);
    }
    Ok(())
}

fn execute_files_list<'a>(_matches: &ArgMatches<'a>,
                          config: &Config,
                          org: &str,
                          project: &str,
                          release: &str)
                          -> Result<()> {
    for artifact in Api::new(config).list_release_files(org, project, release)? {
        print!("{}  ({} bytes)", artifact.name, artifact.size);
        if let Some(sm_ref) = artifact.get_sourcemap_reference() {
            print!("  -> sourcemap: {}", sm_ref);
        }
        println!("");
    }
    Ok(())
}

fn execute_files_delete<'a>(matches: &ArgMatches<'a>,
                            config: &Config,
                            org: &str,
                            project: &str,
                            release: &str)
                            -> Result<()> {
    let files: HashSet<String> = match matches.values_of("names") {
        Some(paths) => paths.map(|x| x.into()).collect(),
        None => HashSet::new(),
    };
    let api = Api::new(config);
    for file in api.list_release_files(org, project, release)? {
        if !(matches.is_present("all") || files.contains(&file.name)) {
            continue;
        }
        if api.delete_release_file(org, project, release, &file.id)? {
            println!("D {}", file.name);
        }
    }
    Ok(())
}

fn execute_files_upload<'a>(matches: &ArgMatches<'a>,
                            config: &Config,
                            org: &str,
                            project: &str,
                            version: &str)
                            -> Result<()> {
    let path = Path::new(matches.value_of("path").unwrap());
    let name = match matches.value_of("name") {
        Some(name) => name,
        None => {
            Path::new(path).file_name()
                .and_then(|x| x.to_str())
                .ok_or("No filename provided.")?
        }
    };
    let mut headers = vec![];
    if let Some(header_list) = matches.values_of("header") {
        for header in header_list {
            if !header.contains(':') {
                fail!("Invalid header. Needs to be in key:value format");
            }
            let mut iter = header.splitn(2, ':');
            let key = iter.next().unwrap();
            let value = iter.next().unwrap();
            headers.push((key.trim().to_string(), value.trim().to_string()));
        }
    };
    if let Some(artifact) = Api::new(config).upload_release_file(org,
                             project,
                             &version,
                             FileContents::FromPath(&path),
                             &name,
                             Some(&headers[..]))? {
        println!("A {}  ({} bytes)", artifact.sha1, artifact.size);
    } else {
        fail!("File already present!");
    }
    Ok(())
}

fn execute_files_upload_sourcemaps<'a>(matches: &ArgMatches<'a>,
                                       config: &Config,
                                       org: &str,
                                       project: &str,
                                       version: &str)
                                       -> Result<()> {
    let api = Api::new(config);
    let release = api.get_release(org, project, version)?.ok_or("release not found")?;
    let url_prefix = matches.value_of("url_prefix").unwrap_or("~").trim_right_matches("/");
    let paths = matches.values_of("paths").unwrap();
    let extensions = match matches.values_of("extensions") {
        Some(matches) => matches.map(|ext| OsStr::new(ext.trim_left_matches("."))).collect(),
        None => {
            vec![OsStr::new("js"), OsStr::new("map"), OsStr::new("jsbundle"), OsStr::new("bundle")]
        }
    };

    let mut processor = SourceMapProcessor::new(matches.is_present("verbose"));

    for path in paths {
        // if we start walking over something that is an actual file then
        // the directory iterator yields that path and terminates.  We
        // handle that case here specifically to figure out what the path is
        // we should strip off.
        let walk_path = PathBuf::from(&path);
        let (base_path, skip_ext_test) = if walk_path.is_file() {
            (walk_path.parent().unwrap(), true)
        } else {
            (walk_path.as_path(), false)
        };

        for dent in WalkDir::new(&walk_path) {
            let dent = dent?;
            if !skip_ext_test {
                let extension = dent.path().extension();
                if !extensions.iter().any(|ext| Some(*ext) == extension) {
                    continue;
                }
            }
            debug!("found: {} ({} bytes)",
                   dent.path().display(),
                   dent.metadata().unwrap().len());
            let local_path = dent.path().strip_prefix(&base_path).unwrap();
            let url = format!("{}/{}", url_prefix, path_as_url(local_path));
            processor.add(&url, dent.path())?;
        }
    }

    if matches.is_present("validate") {
        println!("Running with sourcemap validation");
        processor.validate_all()?;
    }

    if matches.is_present("rewrite") {
        let mut prefixes: Vec<&str> = match matches.values_of("strip_prefix") {
            Some(paths) => paths.collect(),
            None => vec![],
        };
        if matches.is_present("strip_common_prefix") {
            prefixes.push("~");
        }
        processor.rewrite(&prefixes)?;
    }

    if !matches.is_present("no_sourcemap_reference") {
        processor.add_sourcemap_references()?;
    }

    println!("Uploading sourcemaps for release {}", release.version);
    processor.upload(&api, &org, &project, &release.version)?;

    Ok(())
}

fn execute_files<'a>(matches: &ArgMatches<'a>,
                     config: &Config,
                     org: &str,
                     project: &str)
                     -> Result<()> {
    let release = matches.value_of("version").unwrap();
    if let Some(sub_matches) = matches.subcommand_matches("list") {
        return execute_files_list(sub_matches, config, org, project, release);
    }
    if let Some(sub_matches) = matches.subcommand_matches("delete") {
        return execute_files_delete(sub_matches, config, org, project, release);
    }
    if let Some(sub_matches) = matches.subcommand_matches("upload") {
        return execute_files_upload(sub_matches, config, org, project, release);
    }
    if let Some(sub_matches) = matches.subcommand_matches("upload-sourcemaps") {
        return execute_files_upload_sourcemaps(sub_matches, config, org, project, release);
    }
    unreachable!();
}

pub fn execute<'a>(matches: &ArgMatches<'a>, config: &Config) -> Result<()> {
    if let Some(sub_matches) = matches.subcommand_matches("new") {
        let (org, project) = config.get_org_and_project(matches)?;
        return execute_new(sub_matches, config, &org, &project);
    }
    if let Some(sub_matches) = matches.subcommand_matches("delete") {
        let (org, project) = config.get_org_and_project(matches)?;
        return execute_delete(sub_matches, config, &org, &project);
    }
    if let Some(sub_matches) = matches.subcommand_matches("list") {
        let (org, project) = config.get_org_and_project(matches)?;
        return execute_list(sub_matches, config, &org, &project);
    }
    if let Some(sub_matches) = matches.subcommand_matches("files") {
        let (org, project) = config.get_org_and_project(matches)?;
        return execute_files(sub_matches, config, &org, &project);
    }
    unreachable!();
}
<|endoftext|>


<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/. */

<%namespace name="helpers" file="/helpers.mako.rs" />

<% data.new_style_struct("Counters", inherited=False, gecko_name="Content") %>

<%helpers:longhand name="content" boxed="True" animation_value_type="none"
                   spec="https://drafts.csswg.org/css-content/#propdef-content">
    use cssparser::Token;
    use std::ascii::AsciiExt;
    use values::computed::ComputedValueAsSpecified;
    use values::specified::url::SpecifiedUrl;
    use values::HasViewportPercentage;

    use super::list_style_type;

    pub use self::computed_value::T as SpecifiedValue;
    pub use self::computed_value::ContentItem;

    impl ComputedValueAsSpecified for SpecifiedValue {}
    no_viewport_percentage!(SpecifiedValue);

    pub mod computed_value {
        use super::super::list_style_type;

        use cssparser;
        use std::fmt;
        use style_traits::ToCss;
        use values::specified::url::SpecifiedUrl;

        #[derive(Debug, PartialEq, Eq, Clone)]
        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
        pub enum ContentItem {
            /// Literal string content.
            String(String),
            /// `counter(name, style)`.
            Counter(String, list_style_type::computed_value::T),
            /// `counters(name, separator, style)`.
            Counters(String, String, list_style_type::computed_value::T),
            /// `open-quote`.
            OpenQuote,
            /// `close-quote`.
            CloseQuote,
            /// `no-open-quote`.
            NoOpenQuote,
            /// `no-close-quote`.
            NoCloseQuote,

            % if product == "gecko":
                /// `-moz-alt-content`
                MozAltContent,
                /// `attr([namespace? `|`]? ident)`
                Attr(Option<String>, String),
                /// `url(url)`
                Url(SpecifiedUrl),
            % endif
        }

        impl ToCss for ContentItem {
            fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
                match *self {
                    ContentItem::String(ref s) => {
                        cssparser::serialize_string(&**s, dest)
                    }
                    ContentItem::Counter(ref s, ref list_style_type) => {
                        try!(dest.write_str("counter("));
                        try!(cssparser::serialize_identifier(&**s, dest));
                        try!(dest.write_str(", "));
                        try!(list_style_type.to_css(dest));
                        dest.write_str(")")
                    }
                    ContentItem::Counters(ref s, ref separator, ref list_style_type) => {
                        try!(dest.write_str("counter("));
                        try!(cssparser::serialize_identifier(&**s, dest));
                        try!(dest.write_str(", "));
                        try!(cssparser::serialize_string(&**separator, dest));
                        try!(dest.write_str(", "));
                        try!(list_style_type.to_css(dest));
                        dest.write_str(")")
                    }
                    ContentItem::OpenQuote => dest.write_str("open-quote"),
                    ContentItem::CloseQuote => dest.write_str("close-quote"),
                    ContentItem::NoOpenQuote => dest.write_str("no-open-quote"),
                    ContentItem::NoCloseQuote => dest.write_str("no-close-quote"),

                    % if product == "gecko":
                        ContentItem::MozAltContent => dest.write_str("-moz-alt-content"),
                        ContentItem::Attr(ref ns, ref attr) => {
                            dest.write_str("attr(")?;
                            if let Some(ref ns) = *ns {
                                cssparser::Token::Ident((&**ns).into()).to_css(dest)?;
                                dest.write_str("|")?;
                            }
                            cssparser::Token::Ident((&**attr).into()).to_css(dest)?;
                            dest.write_str(")")
                        }
                        ContentItem::Url(ref url) => url.to_css(dest),
                    % endif
                }
            }
        }

        #[allow(non_camel_case_types)]
        #[derive(Debug, PartialEq, Eq, Clone)]
        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
        pub enum T {
            normal,
            none,
            Content(Vec<ContentItem>),
        }

        impl ToCss for T {
            fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
                match *self {
                    T::normal => dest.write_str("normal"),
                    T::none => dest.write_str("none"),
                    T::Content(ref content) => {
                        let mut iter = content.iter();
                        try!(iter.next().unwrap().to_css(dest));
                        for c in iter {
                            try!(dest.write_str(" "));
                            try!(c.to_css(dest));
                        }
                        Ok(())
                    }
                }
            }
        }
    }
    #[inline]
    pub fn get_initial_value() -> computed_value::T  {
        computed_value::T::normal
    }

    // normal | none | [ <string> | <counter> | open-quote | close-quote | no-open-quote |
    // no-close-quote ]+
    // TODO: <uri>, attr(<identifier>)
    pub fn parse(context: &ParserContext, input: &mut Parser)
                 -> Result<SpecifiedValue, ()> {
        if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
            return Ok(SpecifiedValue::normal)
        }
        if input.try(|input| input.expect_ident_matching("none")).is_ok() {
            return Ok(SpecifiedValue::none)
        }
        let mut content = vec![];
        loop {
            % if product == "gecko":
                if let Ok(url) = input.try(|i| SpecifiedUrl::parse(context, i)) {
                    content.push(ContentItem::Url(url));
                    continue;
                }
            % endif
            match input.next() {
                Ok(Token::QuotedString(value)) => {
                    content.push(ContentItem::String(value.into_owned()))
                }
                Ok(Token::Function(name)) => {
                    content.push(try!(match_ignore_ascii_case! { &name,
                        "counter" => input.parse_nested_block(|input| {
                            let name = try!(input.expect_ident()).into_owned();
                            let style = input.try(|input| {
                                try!(input.expect_comma());
                                list_style_type::parse(context, input)
                            }).unwrap_or(list_style_type::computed_value::T::decimal);
                            Ok(ContentItem::Counter(name, style))
                        }),
                        "counters" => input.parse_nested_block(|input| {
                            let name = try!(input.expect_ident()).into_owned();
                            try!(input.expect_comma());
                            let separator = try!(input.expect_string()).into_owned();
                            let style = input.try(|input| {
                                try!(input.expect_comma());
                                list_style_type::parse(context, input)
                            }).unwrap_or(list_style_type::computed_value::T::decimal);
                            Ok(ContentItem::Counters(name, separator, style))
                        }),
                        % if product == "gecko":
                            "attr" => input.parse_nested_block(|input| {
                                // Syntax is `[namespace? `|`]? ident`
                                // no spaces allowed
                                // FIXME (bug 1346693) we should be checking that
                                // this is a valid namespace and encoding it as a namespace
                                // number from the map
                                let first = input.try(|i| i.expect_ident()).ok().map(|i| i.into_owned());
                                if let Ok(token) = input.try(|i| i.next_including_whitespace()) {
                                    match token {
                                        Token::Delim('|') => {
                                            // must be followed by an ident
                                            let tok2 = input.next_including_whitespace()?;
                                            if let Token::Ident(second) = tok2 {
                                                return Ok(ContentItem::Attr(first, second.into_owned()))
                                            } else {
                                                return Err(())
                                            }
                                        }
                                        _ => return Err(())
                                    }
                                }
                                if let Some(first) = first {
                                    Ok(ContentItem::Attr(None, first))
                                } else {
                                    Err(())
                                }
                            }),
                        % endif
                        _ => return Err(())
                    }));
                }
                Ok(Token::Ident(ident)) => {
                    match_ignore_ascii_case! { &ident,
                        "open-quote" => content.push(ContentItem::OpenQuote),
                        "close-quote" => content.push(ContentItem::CloseQuote),
                        "no-open-quote" => content.push(ContentItem::NoOpenQuote),
                        "no-close-quote" => content.push(ContentItem::NoCloseQuote),

                        % if product == "gecko":
                            "-moz-alt-content" => content.push(ContentItem::MozAltContent),
                        % endif

                        _ => return Err(())
                    }
                }
                Err(_) => break,
                _ => return Err(())
            }
        }
        if !content.is_empty() {
            Ok(SpecifiedValue::Content(content))
        } else {
            Err(())
        }
    }
</%helpers:longhand>

<%helpers:longhand name="counter-increment" animation_value_type="none"
                   spec="https://drafts.csswg.org/css-lists/#propdef-counter-increment">
    use std::fmt;
    use style_traits::ToCss;
    use super::content;
    use values::{HasViewportPercentage, CustomIdent};

    use cssparser::{Token, serialize_identifier};
    use std::borrow::{Cow, ToOwned};

    #[derive(Debug, Clone, PartialEq)]
    pub struct SpecifiedValue(pub Vec<(CustomIdent, specified::Integer)>);

    pub mod computed_value {
        use std::fmt;
        use style_traits::ToCss;
        use values::CustomIdent;

        #[derive(Debug, Clone, PartialEq)]
        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
        pub struct T(pub Vec<(CustomIdent, i32)>);

        impl ToCss for T {
            fn to_css<W>(&self, dest: &mut W) -> fmt::Result
                where W: fmt::Write,
            {
                use cssparser::serialize_identifier;
                if self.0.is_empty() {
                    return dest.write_str("none")
                }

                let mut first = true;
                for &(ref name, value) in &self.0 {
                    if !first {
                        dest.write_str(" ")?;
                    }
                    first = false;
                    name.to_css(dest)?;
                    dest.write_str(" ")?;
                    value.to_css(dest)?;
                }
                Ok(())
            }
        }
    }

    impl ToComputedValue for SpecifiedValue {
        type ComputedValue = computed_value::T;

        fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
            computed_value::T(self.0.iter().map(|&(ref name, ref value)| {
                (name.clone(), value.to_computed_value(context))
            }).collect::<Vec<_>>())
        }

        fn from_computed_value(computed: &Self::ComputedValue) -> Self {
            SpecifiedValue(computed.0.iter().map(|&(ref name, ref value)| {
                (name.clone(), specified::Integer::from_computed_value(&value))
            }).collect::<Vec<_>>())
        }
    }

    #[inline]
    pub fn get_initial_value() -> computed_value::T {
        computed_value::T(Vec::new())
    }

    no_viewport_percentage!(SpecifiedValue);

    impl ToCss for SpecifiedValue {
        fn to_css<W>(&self, dest: &mut W) -> fmt::Result
            where W: fmt::Write,
        {
            if self.0.is_empty() {
                return dest.write_str("none");
            }
            let mut first = true;
            for &(ref name, ref value) in &self.0 {
                if !first {
                    dest.write_str(" ")?;
                }
                first = false;
                name.to_css(dest)?;
                dest.write_str(" ")?;
                value.to_css(dest)?;
            }

            Ok(())
        }
    }

    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
        parse_common(context, 1, input)
    }

    pub fn parse_common(context: &ParserContext, default_value: i32, input: &mut Parser) -> Result<SpecifiedValue, ()> {
        use std::ascii::AsciiExt;

        if input.try(|input| input.expect_ident_matching("none")).is_ok() {
            return Ok(SpecifiedValue(Vec::new()))
        }

        let mut counters = Vec::new();
        loop {
            let counter_name = match input.next() {
                Ok(Token::Ident(ident)) => CustomIdent::from_ident(ident, &["none"])?,
                Ok(_) => return Err(()),
                Err(_) => break,
            };
            let counter_delta = input.try(|input| specified::parse_integer(context, input))
                                     .unwrap_or(specified::Integer::new(default_value));
            counters.push((counter_name, counter_delta))
        }

        if !counters.is_empty() {
            Ok(SpecifiedValue(counters))
        } else {
            Err(())
        }
    }
</%helpers:longhand>

<%helpers:longhand name="counter-reset" animation_value_type="none"
                   spec="https://drafts.csswg.org/css-lists-3/#propdef-counter-reset">
    pub use super::counter_increment::{SpecifiedValue, computed_value, get_initial_value};
    use super::counter_increment::parse_common;

    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
        parse_common(context, 0, input)
    }
</%helpers:longhand>
<commit_msg>Auto merge of #16791 - upsuper:counters-fix, r=heycam<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/. */

<%namespace name="helpers" file="/helpers.mako.rs" />

<% data.new_style_struct("Counters", inherited=False, gecko_name="Content") %>

<%helpers:longhand name="content" boxed="True" animation_value_type="none"
                   spec="https://drafts.csswg.org/css-content/#propdef-content">
    use cssparser::Token;
    use std::ascii::AsciiExt;
    use values::computed::ComputedValueAsSpecified;
    use values::specified::url::SpecifiedUrl;
    use values::HasViewportPercentage;

    use super::list_style_type;

    pub use self::computed_value::T as SpecifiedValue;
    pub use self::computed_value::ContentItem;

    impl ComputedValueAsSpecified for SpecifiedValue {}
    no_viewport_percentage!(SpecifiedValue);

    pub mod computed_value {
        use super::super::list_style_type;

        use cssparser;
        use std::fmt;
        use style_traits::ToCss;
        use values::specified::url::SpecifiedUrl;

        #[derive(Debug, PartialEq, Eq, Clone)]
        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
        pub enum ContentItem {
            /// Literal string content.
            String(String),
            /// `counter(name, style)`.
            Counter(String, list_style_type::computed_value::T),
            /// `counters(name, separator, style)`.
            Counters(String, String, list_style_type::computed_value::T),
            /// `open-quote`.
            OpenQuote,
            /// `close-quote`.
            CloseQuote,
            /// `no-open-quote`.
            NoOpenQuote,
            /// `no-close-quote`.
            NoCloseQuote,

            % if product == "gecko":
                /// `-moz-alt-content`
                MozAltContent,
                /// `attr([namespace? `|`]? ident)`
                Attr(Option<String>, String),
                /// `url(url)`
                Url(SpecifiedUrl),
            % endif
        }

        impl ToCss for ContentItem {
            fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
                match *self {
                    ContentItem::String(ref s) => {
                        cssparser::serialize_string(&**s, dest)
                    }
                    ContentItem::Counter(ref s, ref list_style_type) => {
                        try!(dest.write_str("counter("));
                        try!(cssparser::serialize_identifier(&**s, dest));
                        try!(dest.write_str(", "));
                        try!(list_style_type.to_css(dest));
                        dest.write_str(")")
                    }
                    ContentItem::Counters(ref s, ref separator, ref list_style_type) => {
                        try!(dest.write_str("counters("));
                        try!(cssparser::serialize_identifier(&**s, dest));
                        try!(dest.write_str(", "));
                        try!(cssparser::serialize_string(&**separator, dest));
                        try!(dest.write_str(", "));
                        try!(list_style_type.to_css(dest));
                        dest.write_str(")")
                    }
                    ContentItem::OpenQuote => dest.write_str("open-quote"),
                    ContentItem::CloseQuote => dest.write_str("close-quote"),
                    ContentItem::NoOpenQuote => dest.write_str("no-open-quote"),
                    ContentItem::NoCloseQuote => dest.write_str("no-close-quote"),

                    % if product == "gecko":
                        ContentItem::MozAltContent => dest.write_str("-moz-alt-content"),
                        ContentItem::Attr(ref ns, ref attr) => {
                            dest.write_str("attr(")?;
                            if let Some(ref ns) = *ns {
                                cssparser::Token::Ident((&**ns).into()).to_css(dest)?;
                                dest.write_str("|")?;
                            }
                            cssparser::Token::Ident((&**attr).into()).to_css(dest)?;
                            dest.write_str(")")
                        }
                        ContentItem::Url(ref url) => url.to_css(dest),
                    % endif
                }
            }
        }

        #[allow(non_camel_case_types)]
        #[derive(Debug, PartialEq, Eq, Clone)]
        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
        pub enum T {
            normal,
            none,
            Content(Vec<ContentItem>),
        }

        impl ToCss for T {
            fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
                match *self {
                    T::normal => dest.write_str("normal"),
                    T::none => dest.write_str("none"),
                    T::Content(ref content) => {
                        let mut iter = content.iter();
                        try!(iter.next().unwrap().to_css(dest));
                        for c in iter {
                            try!(dest.write_str(" "));
                            try!(c.to_css(dest));
                        }
                        Ok(())
                    }
                }
            }
        }
    }
    #[inline]
    pub fn get_initial_value() -> computed_value::T  {
        computed_value::T::normal
    }

    // normal | none | [ <string> | <counter> | open-quote | close-quote | no-open-quote |
    // no-close-quote ]+
    // TODO: <uri>, attr(<identifier>)
    pub fn parse(context: &ParserContext, input: &mut Parser)
                 -> Result<SpecifiedValue, ()> {
        if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
            return Ok(SpecifiedValue::normal)
        }
        if input.try(|input| input.expect_ident_matching("none")).is_ok() {
            return Ok(SpecifiedValue::none)
        }
        let mut content = vec![];
        loop {
            % if product == "gecko":
                if let Ok(url) = input.try(|i| SpecifiedUrl::parse(context, i)) {
                    content.push(ContentItem::Url(url));
                    continue;
                }
            % endif
            match input.next() {
                Ok(Token::QuotedString(value)) => {
                    content.push(ContentItem::String(value.into_owned()))
                }
                Ok(Token::Function(name)) => {
                    content.push(try!(match_ignore_ascii_case! { &name,
                        "counter" => input.parse_nested_block(|input| {
                            let name = try!(input.expect_ident()).into_owned();
                            let style = input.try(|input| {
                                try!(input.expect_comma());
                                list_style_type::parse(context, input)
                            }).unwrap_or(list_style_type::computed_value::T::decimal);
                            Ok(ContentItem::Counter(name, style))
                        }),
                        "counters" => input.parse_nested_block(|input| {
                            let name = try!(input.expect_ident()).into_owned();
                            try!(input.expect_comma());
                            let separator = try!(input.expect_string()).into_owned();
                            let style = input.try(|input| {
                                try!(input.expect_comma());
                                list_style_type::parse(context, input)
                            }).unwrap_or(list_style_type::computed_value::T::decimal);
                            Ok(ContentItem::Counters(name, separator, style))
                        }),
                        % if product == "gecko":
                            "attr" => input.parse_nested_block(|input| {
                                // Syntax is `[namespace? `|`]? ident`
                                // no spaces allowed
                                // FIXME (bug 1346693) we should be checking that
                                // this is a valid namespace and encoding it as a namespace
                                // number from the map
                                let first = input.try(|i| i.expect_ident()).ok().map(|i| i.into_owned());
                                if let Ok(token) = input.try(|i| i.next_including_whitespace()) {
                                    match token {
                                        Token::Delim('|') => {
                                            // must be followed by an ident
                                            let tok2 = input.next_including_whitespace()?;
                                            if let Token::Ident(second) = tok2 {
                                                return Ok(ContentItem::Attr(first, second.into_owned()))
                                            } else {
                                                return Err(())
                                            }
                                        }
                                        _ => return Err(())
                                    }
                                }
                                if let Some(first) = first {
                                    Ok(ContentItem::Attr(None, first))
                                } else {
                                    Err(())
                                }
                            }),
                        % endif
                        _ => return Err(())
                    }));
                }
                Ok(Token::Ident(ident)) => {
                    match_ignore_ascii_case! { &ident,
                        "open-quote" => content.push(ContentItem::OpenQuote),
                        "close-quote" => content.push(ContentItem::CloseQuote),
                        "no-open-quote" => content.push(ContentItem::NoOpenQuote),
                        "no-close-quote" => content.push(ContentItem::NoCloseQuote),

                        % if product == "gecko":
                            "-moz-alt-content" => content.push(ContentItem::MozAltContent),
                        % endif

                        _ => return Err(())
                    }
                }
                Err(_) => break,
                _ => return Err(())
            }
        }
        if !content.is_empty() {
            Ok(SpecifiedValue::Content(content))
        } else {
            Err(())
        }
    }
</%helpers:longhand>

<%helpers:longhand name="counter-increment" animation_value_type="none"
                   spec="https://drafts.csswg.org/css-lists/#propdef-counter-increment">
    use std::fmt;
    use style_traits::ToCss;
    use super::content;
    use values::{HasViewportPercentage, CustomIdent};

    use cssparser::{Token, serialize_identifier};
    use std::borrow::{Cow, ToOwned};

    #[derive(Debug, Clone, PartialEq)]
    pub struct SpecifiedValue(pub Vec<(CustomIdent, specified::Integer)>);

    pub mod computed_value {
        use std::fmt;
        use style_traits::ToCss;
        use values::CustomIdent;

        #[derive(Debug, Clone, PartialEq)]
        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
        pub struct T(pub Vec<(CustomIdent, i32)>);

        impl ToCss for T {
            fn to_css<W>(&self, dest: &mut W) -> fmt::Result
                where W: fmt::Write,
            {
                use cssparser::serialize_identifier;
                if self.0.is_empty() {
                    return dest.write_str("none")
                }

                let mut first = true;
                for &(ref name, value) in &self.0 {
                    if !first {
                        dest.write_str(" ")?;
                    }
                    first = false;
                    name.to_css(dest)?;
                    dest.write_str(" ")?;
                    value.to_css(dest)?;
                }
                Ok(())
            }
        }
    }

    impl ToComputedValue for SpecifiedValue {
        type ComputedValue = computed_value::T;

        fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
            computed_value::T(self.0.iter().map(|&(ref name, ref value)| {
                (name.clone(), value.to_computed_value(context))
            }).collect::<Vec<_>>())
        }

        fn from_computed_value(computed: &Self::ComputedValue) -> Self {
            SpecifiedValue(computed.0.iter().map(|&(ref name, ref value)| {
                (name.clone(), specified::Integer::from_computed_value(&value))
            }).collect::<Vec<_>>())
        }
    }

    #[inline]
    pub fn get_initial_value() -> computed_value::T {
        computed_value::T(Vec::new())
    }

    no_viewport_percentage!(SpecifiedValue);

    impl ToCss for SpecifiedValue {
        fn to_css<W>(&self, dest: &mut W) -> fmt::Result
            where W: fmt::Write,
        {
            if self.0.is_empty() {
                return dest.write_str("none");
            }
            let mut first = true;
            for &(ref name, ref value) in &self.0 {
                if !first {
                    dest.write_str(" ")?;
                }
                first = false;
                name.to_css(dest)?;
                dest.write_str(" ")?;
                value.to_css(dest)?;
            }

            Ok(())
        }
    }

    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
        parse_common(context, 1, input)
    }

    pub fn parse_common(context: &ParserContext, default_value: i32, input: &mut Parser) -> Result<SpecifiedValue, ()> {
        use std::ascii::AsciiExt;

        if input.try(|input| input.expect_ident_matching("none")).is_ok() {
            return Ok(SpecifiedValue(Vec::new()))
        }

        let mut counters = Vec::new();
        loop {
            let counter_name = match input.next() {
                Ok(Token::Ident(ident)) => CustomIdent::from_ident(ident, &["none"])?,
                Ok(_) => return Err(()),
                Err(_) => break,
            };
            let counter_delta = input.try(|input| specified::parse_integer(context, input))
                                     .unwrap_or(specified::Integer::new(default_value));
            counters.push((counter_name, counter_delta))
        }

        if !counters.is_empty() {
            Ok(SpecifiedValue(counters))
        } else {
            Err(())
        }
    }
</%helpers:longhand>

<%helpers:longhand name="counter-reset" animation_value_type="none"
                   spec="https://drafts.csswg.org/css-lists-3/#propdef-counter-reset">
    pub use super::counter_increment::{SpecifiedValue, computed_value, get_initial_value};
    use super::counter_increment::parse_common;

    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
        parse_common(context, 0, input)
    }
</%helpers:longhand>
<|endoftext|>


<commit_before>
use std::io;
use std::clone::Clone;
use std::slice::Iter;

use linked_hash_map::LinkedHashMap;

use parser::ast::*;
use parser::util::*;
use processing::structs::*;
use output::client_ops_stream_writer::*;
use output::client_misc::*;
use output::scope::*;

#[derive(Debug)]
pub struct BlockDefinition {
    pub block_id: String,
    pub ops: Vec<ElementOp>
}
pub type BlockMap = LinkedHashMap<String, BlockDefinition>;

pub struct ElementOpsWriter<'input: 'scope, 'scope> {
    pub doc: &'input DocumentState<'input>,
    pub stream_writer: &'scope mut ElementOpsStreamWriter,
    pub scopes: LinkedHashMap<String, ElementOpScope>,
    pub blocks: BlockMap,
    pub cur_block_id: Option<String>
}

impl<'input: 'scope, 'scope> ElementOpsWriter<'input, 'scope> {

    pub fn with_doc(doc: &'input DocumentState<'input>, stream_writer: &'scope mut ElementOpsStreamWriter) -> Self {
        ElementOpsWriter {
            doc: doc,
            stream_writer: stream_writer,
            scopes: Default::default(),
            blocks: Default::default(),
            cur_block_id: None
        }
    }

    #[inline]
    fn write_loop_item(&mut self, w: &mut io::Write, doc: &'input DocumentState, item_expr: &ExprValue, scope: &ElementOpScope, ele: Option<&str>, element_ty: Option<&VarType>, block_id: &str, output_component_contents: bool) -> Result {
        let mut scope = scope.clone();

        if let Some(ele_key) = ele {
            scope.add_loop_var_with_value(ele_key, item_expr);

            // loop_scope.1.symbol_map.insert(ele_key.to_owned(), (Some(SymbolReferenceType::LoopVarReference(ele_key.to_owned())), None));
            // loop_scope = loop_scope
            //     .with_var(ele_key,
            //         SymbolReferenceType::LoopVarReference(ele_key.to_owned()),
            //         element_ty.as_ref().map(Clone::clone),
            //         Some(SymbolValueType::ConstantValue(item_expr.clone()))
            //     );
        };

        let block_ops = self.blocks.get(block_id)
            .map(|block| block.ops.clone());

        if let Some(ref block_ops) = block_ops {
            // Push scope
            let scope_id = format!("{}_{}", scope.0.key_prefix(block_id), 1);
            self.scopes.insert(scope_id, scope.clone());

            // Output ops
            self.write_ops_content(w, block_ops.iter(), doc, &scope, output_component_contents)?;

            // Pop scope
            self.scopes.pop_back();
        };
        Ok(())
    }

    #[inline]
    fn invoke_component_with_props(&mut self, w: &mut io::Write, doc: &'input DocumentState, scope: &ElementOpScope, comp: &Component, props: Option<Iter<Prop>>, output_component_contents: bool) -> Result {
        let mut scope = scope.clone();

        if let Some(props) = props {
            for &(ref key, ref expr) in props {
                if let Some(ref expr) = expr.as_ref().and_then(|expr| reduce_expr(&expr, doc, &scope)) {
                    scope.add_prop_with_value(key, &expr);
                };
            }
        };

        // for (key, param) in comp.props.iter() {
            // if let Some(_) = scope.1.props.get(key) {
            //     let sym = Symbol::prop(key);
            //     let sym_ref = ExprValue::SymbolReference(sym);
            //     scope.add_prop_with_value(key, &sym_ref);
            //     continue;
            // };

            // if let Some(ref expr) = doc.resolve_symbol_value(param) {
            //     prop_scope.add_prop_with_value(key, &expr);
            // };

            // if let &Symbol(SymbolReferenceType::PropReference(ref var_name), _, _) = param {
            //     if let Some(&Symbol(SymbolReferenceType::ReducerKeyReference(ref reducer_key), _, Some(box ref expr))) = scope.1.props.get(var_name) {
            //         if let Some(ref expr) = reduce_expr(&expr, doc, scope) {
            //             //let value = ExprValue::SymbolReference(sym.clone());
            //             prop_scope.add_prop_with_value(key, &expr);
            //         };
            //     };
            // };
        // }

        let scope_key = allocate_element_key();
        let scope_id = scope.0.key_prefix(&scope_key);
        self.scopes.insert(scope_id, scope.clone());

        if let Some(ref ops) = comp.ops {
            self.write_ops_content(w, ops.iter(), doc, &scope, output_component_contents)?;
        };
        Ok(())
    }

    #[inline]
    #[allow(unused_variables)]
    pub fn write_ops_content<'op>(&mut self, w: &mut io::Write, ops: Iter<'op, ElementOp>, doc: &'input DocumentState, scope: &ElementOpScope, output_component_contents: bool) -> Result {
        for op in ops {
            if output_component_contents {
                if let &ElementOp::EndBlock(..) = op {
                    self.cur_block_id = None;
                    continue;
                };

                if let Some(ref cur_block_id) = self.cur_block_id {
                    let block = self.blocks.entry(cur_block_id.to_owned())
                        .or_insert_with(|| BlockDefinition { block_id: cur_block_id.clone(), ops: Default::default() });

                    block.ops.push(op.clone());
                    continue;
                };
            };

            let is_void = if let &ElementOp::ElementVoid(..) = op { true } else { false };

            match op {
                &ElementOp::ElementOpen(ref element_tag, ref element_key, ref attrs, ref events) |
                &ElementOp::ElementVoid(ref element_tag, ref element_key, ref attrs, ref events) => {
                    let scope = self.scopes.back().map_or(scope.clone(), |s| s.1.clone());

                    let element_key = element_key.as_ref().map_or("null", |s| s);

                    let attrs = attrs.as_ref().map(|attrs| attrs.iter());
                    let events = events.as_ref().map(|events| events.iter());

                    let element_key = format!("{}", scope.0.key_prefix(element_key));
                    self.stream_writer.write_op_element(w, op, doc, &scope, &element_key, element_tag, is_void, attrs, events)?;
                }
                &ElementOp::ElementClose(ref element_tag) => {
                    let scope = self.scopes.back().map_or(scope.clone(), |s| s.1.clone());

                    self.stream_writer.write_op_element_close(w, op, doc, &scope, element_tag)?;
                }
                &ElementOp::WriteValue(ref expr, ref value_key) => {
                    let scope = self.scopes.back().map_or(scope.clone(), |s| s.1.clone());

                    let value_key = value_key.as_ref().map_or("null", |s| s);
                    self.stream_writer.write_op_element_value(w, op, doc, &scope, expr, value_key)?;
                }
                &ElementOp::InstanceComponent(ref component_ty,
                                            ref component_key,
                                            ref props,
                                            ref lens) => {
                    let scope = self.scopes.back().map_or(scope.clone(), |s| s.1.clone());

                    let comp = doc.comp_map.get(component_ty.as_str());
                    if let Some(ref comp) = comp {
                        let props = props.as_ref().map(|s| s.clone());
                        let lens = lens.as_ref().map(|s| s.clone());

                        let component_key = component_key.as_ref().map_or("null", |s| s);
                        let component_id = format!("{}_1", component_key);

                        let props_iter = props.as_ref().map(|s| s.iter());

                        // OpenS
                        self.stream_writer.write_op_element_instance_component_open(w, op, doc, &scope, &comp, component_key, component_id.as_str(), props_iter.as_ref().map(Clone::clone), lens.as_ref())?;

                        if output_component_contents {
                            self.invoke_component_with_props(w, doc, &scope, comp, props_iter.as_ref().map(Clone::clone), true)?;
                            // if let Some(ref ops) = comp.ops {
                            //     self.write_ops_content(w, ops.iter(), doc, &scope, output_component_contents)?;
                            // };
                        };

                        // Close
                        self.stream_writer.write_op_element_instance_component_close(w, op, doc, &scope, &comp, component_key, component_id.as_str())?;
                    }
                }

                &ElementOp::StartBlock(ref block_id) => {
                    if output_component_contents {
                        // Collect blocks to render
                        self.cur_block_id = Some(block_id.to_owned());
                    } else {
                        // Write function header
                        let mut scope = self.scopes.back().map_or(scope.clone(), |s| s.1.clone());

                        let loopidx_ref = Symbol::loop_idx("foridx", block_id);
                        scope.0 = with_key_expr_prefix(&scope.0, ExprValue::SymbolReference(loopidx_ref));
                        self.stream_writer.write_op_element_start_block(w, op, doc, &scope, block_id)?;

                        let scope_id = scope.0.key_prefix(block_id);
                        self.scopes.insert(scope_id, scope.clone());
                    };
                }

                &ElementOp::EndBlock(ref block_id) => {
                    if output_component_contents {
                        // Finish current block
                        self.cur_block_id = None;
                    } else {
                        self.scopes.pop_back();
                        let scope = self.scopes.back().map_or(scope.clone(), |s| s.1.clone());

                        self.stream_writer.write_op_element_end_block(w, op, doc, &scope, block_id)?;
                    };
                }

                &ElementOp::MapCollection(ref block_id, ref ele, ref coll_expr) => {
                    if output_component_contents {
                        let scope = self.scopes.back().unwrap().1.clone();
                        // let mut scope = self.scopes.back().map_or(scope.clone(), |s| s.1.clone());

                        let has_block = self.blocks.contains_key(block_id);
                        if has_block {
                            let ele = ele.as_ref().map(|e| e.as_str());

                            let coll_expr = reduce_expr(coll_expr, doc, &scope);

                            if let Some(ExprValue::LiteralArray(Some(ref items))) = coll_expr {
                                for item_expr in items {
                                    self.write_loop_item(w, doc, item_expr, &scope, ele, None, block_id, output_component_contents)?;
                                };
                            };

                            // if let Some(ExprValue::SymbolReference(ref sym)) = coll_expr {
                            //     let sym_ty = sym.ty();

                            //     if let Some(ExprValue::LiteralArray(Some(ref items))) = doc.resolve_symbol_value(sym) {
                            //         for item_expr in items {
                            //             self.write_loop_item(w, doc, item_expr, &scope, ele, Some(&element_ty), block_id, output_component_contents)?;
                            //         };
                            //     };

                            //     // if let Some(&VarType::ArrayVar(Some(box ref element_ty))) = sym.ty() {
                            //     //     if let Some(ExprValue::LiteralArray(Some(ref items))) = doc.resolve_symbol_value(sym) {
                            //     //         for item_expr in items {
                            //     //             self.write_loop_item(w, doc, item_expr, &scope, ele, Some(&element_ty), block_id, output_component_contents)?;
                            //     //         };
                            //     //     };
                            //     // };
                            // };

                            // if let &ExprValue::SymbolReference(Symbol(SymbolReferenceType::ReducerKeyReference(ref reducer_key), _, _)) = coll_expr {
                            //     if let Some(ref reducer_data) = doc.reducer_key_data.get(reducer_key) {
                            //         if let Some(VarType::ArrayVar(Some(box ref element_ty))) = reducer_data.ty {
                            //             if let Some(ExprValue::LiteralArray(Some(ref items))) = reducer_data.default_expr {
                            //                 for item_expr in items {
                            //                     self.write_loop_item(w, doc, item_expr, scope, ele, Some(element_ty), block_id, output_component_contents)?;
                            //                 };
                            //                 continue;
                            //             };
                            //         };
                            //     };
                            // };
                        };
                    } else {
                        let forvar_default = &format!("__forvar_{}", block_id);
                        let scope_id = format!("{}_map", block_id);

                        // Map to block
                        self.stream_writer.write_op_element_map_collection_to_block(w, op, doc, &scope, coll_expr, block_id)?;
                    };
                }
            }
        }

        Ok(())
    }
}<commit_msg>Cleanup dead code<commit_after>
use std::io;
use std::clone::Clone;
use std::slice::Iter;

use linked_hash_map::LinkedHashMap;

use parser::ast::*;
use parser::util::*;
use processing::structs::*;
use output::client_ops_stream_writer::*;
use output::client_misc::*;
use output::scope::*;

#[derive(Debug)]
pub struct BlockDefinition {
    pub block_id: String,
    pub ops: Vec<ElementOp>
}
pub type BlockMap = LinkedHashMap<String, BlockDefinition>;

pub struct ElementOpsWriter<'input: 'scope, 'scope> {
    pub doc: &'input DocumentState<'input>,
    pub stream_writer: &'scope mut ElementOpsStreamWriter,
    pub scopes: LinkedHashMap<String, ElementOpScope>,
    pub blocks: BlockMap,
    pub cur_block_id: Option<String>
}

impl<'input: 'scope, 'scope> ElementOpsWriter<'input, 'scope> {

    pub fn with_doc(doc: &'input DocumentState<'input>, stream_writer: &'scope mut ElementOpsStreamWriter) -> Self {
        ElementOpsWriter {
            doc: doc,
            stream_writer: stream_writer,
            scopes: Default::default(),
            blocks: Default::default(),
            cur_block_id: None
        }
    }

    #[inline]
    fn write_loop_item(&mut self, w: &mut io::Write, doc: &'input DocumentState, item_expr: &ExprValue, scope: &ElementOpScope, ele: Option<&str>, element_ty: Option<&VarType>, block_id: &str, output_component_contents: bool) -> Result {
        let mut scope = scope.clone();

        if let Some(ele_key) = ele {
            scope.add_loop_var_with_value(ele_key, item_expr);

            // loop_scope.1.symbol_map.insert(ele_key.to_owned(), (Some(SymbolReferenceType::LoopVarReference(ele_key.to_owned())), None));
            // loop_scope = loop_scope
            //     .with_var(ele_key,
            //         SymbolReferenceType::LoopVarReference(ele_key.to_owned()),
            //         element_ty.as_ref().map(Clone::clone),
            //         Some(SymbolValueType::ConstantValue(item_expr.clone()))
            //     );
        };

        let block_ops = self.blocks.get(block_id)
            .map(|block| block.ops.clone());

        if let Some(ref block_ops) = block_ops {
            // Push scope
            let scope_id = format!("{}_{}", scope.0.key_prefix(block_id), 1);
            self.scopes.insert(scope_id, scope.clone());

            // Output ops
            self.write_ops_content(w, block_ops.iter(), doc, &scope, output_component_contents)?;

            // Pop scope
            self.scopes.pop_back();
        };
        Ok(())
    }

    #[inline]
    fn invoke_component_with_props(&mut self, w: &mut io::Write, doc: &'input DocumentState, scope: &ElementOpScope, comp: &Component, props: Option<Iter<Prop>>, output_component_contents: bool) -> Result {
        let mut scope = scope.clone();

        if let Some(props) = props {
            for &(ref key, ref expr) in props {
                if let Some(ref expr) = expr.as_ref().and_then(|expr| reduce_expr(&expr, doc, &scope)) {
                    scope.add_prop_with_value(key, &expr);
                };
            }
        };

        // for (key, param) in comp.props.iter() {
            // if let Some(_) = scope.1.props.get(key) {
            //     let sym = Symbol::prop(key);
            //     let sym_ref = ExprValue::SymbolReference(sym);
            //     scope.add_prop_with_value(key, &sym_ref);
            //     continue;
            // };

            // if let Some(ref expr) = doc.resolve_symbol_value(param) {
            //     prop_scope.add_prop_with_value(key, &expr);
            // };

            // if let &Symbol(SymbolReferenceType::PropReference(ref var_name), _, _) = param {
            //     if let Some(&Symbol(SymbolReferenceType::ReducerKeyReference(ref reducer_key), _, Some(box ref expr))) = scope.1.props.get(var_name) {
            //         if let Some(ref expr) = reduce_expr(&expr, doc, scope) {
            //             //let value = ExprValue::SymbolReference(sym.clone());
            //             prop_scope.add_prop_with_value(key, &expr);
            //         };
            //     };
            // };
        // }

        let scope_key = allocate_element_key();
        let scope_id = scope.0.key_prefix(&scope_key);
        self.scopes.insert(scope_id, scope.clone());

        if let Some(ref ops) = comp.ops {
            self.write_ops_content(w, ops.iter(), doc, &scope, output_component_contents)?;
        };
        Ok(())
    }

    #[inline]
    #[allow(unused_variables)]
    pub fn write_ops_content<'op>(&mut self, w: &mut io::Write, ops: Iter<'op, ElementOp>, doc: &'input DocumentState, scope: &ElementOpScope, output_component_contents: bool) -> Result {
        for op in ops {
            if output_component_contents {
                if let &ElementOp::EndBlock(..) = op {
                    self.cur_block_id = None;
                    continue;
                };

                if let Some(ref cur_block_id) = self.cur_block_id {
                    let block = self.blocks.entry(cur_block_id.to_owned())
                        .or_insert_with(|| BlockDefinition { block_id: cur_block_id.clone(), ops: Default::default() });

                    block.ops.push(op.clone());
                    continue;
                };
            };

            let is_void = if let &ElementOp::ElementVoid(..) = op { true } else { false };

            match op {
                &ElementOp::ElementOpen(ref element_tag, ref element_key, ref attrs, ref events) |
                &ElementOp::ElementVoid(ref element_tag, ref element_key, ref attrs, ref events) => {
                    let scope = self.scopes.back().map_or(scope.clone(), |s| s.1.clone());

                    let element_key = element_key.as_ref().map_or("null", |s| s);

                    let attrs = attrs.as_ref().map(|attrs| attrs.iter());
                    let events = events.as_ref().map(|events| events.iter());

                    let element_key = format!("{}", scope.0.key_prefix(element_key));
                    self.stream_writer.write_op_element(w, op, doc, &scope, &element_key, element_tag, is_void, attrs, events)?;
                }
                &ElementOp::ElementClose(ref element_tag) => {
                    let scope = self.scopes.back().map_or(scope.clone(), |s| s.1.clone());

                    self.stream_writer.write_op_element_close(w, op, doc, &scope, element_tag)?;
                }
                &ElementOp::WriteValue(ref expr, ref value_key) => {
                    let scope = self.scopes.back().map_or(scope.clone(), |s| s.1.clone());

                    let value_key = value_key.as_ref().map_or("null", |s| s);
                    self.stream_writer.write_op_element_value(w, op, doc, &scope, expr, value_key)?;
                }
                &ElementOp::InstanceComponent(ref component_ty,
                                            ref component_key,
                                            ref props,
                                            ref lens) => {
                    let scope = self.scopes.back().map_or(scope.clone(), |s| s.1.clone());

                    let comp = doc.comp_map.get(component_ty.as_str());
                    if let Some(ref comp) = comp {
                        let props = props.as_ref().map(|s| s.clone());
                        let lens = lens.as_ref().map(|s| s.clone());

                        let component_key = component_key.as_ref().map_or("null", |s| s);
                        let component_id = format!("{}_1", component_key);

                        let props_iter = props.as_ref().map(|s| s.iter());

                        // OpenS
                        self.stream_writer.write_op_element_instance_component_open(w, op, doc, &scope, &comp, component_key, component_id.as_str(), props_iter.as_ref().map(Clone::clone), lens.as_ref())?;

                        if output_component_contents {
                            self.invoke_component_with_props(w, doc, &scope, comp, props_iter.as_ref().map(Clone::clone), true)?;
                            // if let Some(ref ops) = comp.ops {
                            //     self.write_ops_content(w, ops.iter(), doc, &scope, output_component_contents)?;
                            // };
                        };

                        // Close
                        self.stream_writer.write_op_element_instance_component_close(w, op, doc, &scope, &comp, component_key, component_id.as_str())?;
                    }
                }

                &ElementOp::StartBlock(ref block_id) => {
                    if output_component_contents {
                        // Collect blocks to render
                        self.cur_block_id = Some(block_id.to_owned());
                    } else {
                        // Write function header
                        let mut scope = self.scopes.back().map_or(scope.clone(), |s| s.1.clone());

                        let loopidx_ref = Symbol::loop_idx("foridx", block_id);
                        scope.0 = with_key_expr_prefix(&scope.0, ExprValue::SymbolReference(loopidx_ref));
                        self.stream_writer.write_op_element_start_block(w, op, doc, &scope, block_id)?;

                        let scope_id = scope.0.key_prefix(block_id);
                        self.scopes.insert(scope_id, scope.clone());
                    };
                }

                &ElementOp::EndBlock(ref block_id) => {
                    if output_component_contents {
                        // Finish current block
                        self.cur_block_id = None;
                    } else {
                        self.scopes.pop_back();
                        let scope = self.scopes.back().map_or(scope.clone(), |s| s.1.clone());

                        self.stream_writer.write_op_element_end_block(w, op, doc, &scope, block_id)?;
                    };
                }

                &ElementOp::MapCollection(ref block_id, ref ele, ref coll_expr) => {
                    if output_component_contents {
                        let scope = self.scopes.back().unwrap().1.clone();
                        // let mut scope = self.scopes.back().map_or(scope.clone(), |s| s.1.clone());

                        let has_block = self.blocks.contains_key(block_id);
                        if has_block {
                            let ele = ele.as_ref().map(|e| e.as_str());

                            let coll_expr = reduce_expr(coll_expr, doc, &scope);

                            if let Some(ExprValue::LiteralArray(Some(ref items))) = coll_expr {
                                for item_expr in items {
                                    self.write_loop_item(w, doc, item_expr, &scope, ele, None, block_id, output_component_contents)?;
                                };
                            };
                        };
                    } else {
                        let forvar_default = &format!("__forvar_{}", block_id);
                        let scope_id = format!("{}_map", block_id);

                        // Map to block
                        self.stream_writer.write_op_element_map_collection_to_block(w, op, doc, &scope, coll_expr, block_id)?;
                    };
                }
            }
        }

        Ok(())
    }
}<|endoftext|>