<commit_before>//
// Copyright (c) Pirmin Kalberer. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
//

use datasource::postgis::PostgisInput;
use core::grid::Grid;
use mvt::tile::Tile;
use mvt::vector_tile;
use service::mvt::{MvtService,Tileset};
use core::{Config,read_config,parse_config};
use toml;
use cache::{Tilecache,Nocache,Filecache};

use nickel::{Nickel, Options, HttpRouter, MediaType, Request, Responder, Response, MiddlewareResult, Halt, StaticFilesHandler};
use mustache;
use rustc_serialize::Encodable;
use hyper::header::{CacheControl, CacheDirective, AccessControlAllowOrigin, AccessControlAllowMethods, ContentEncoding, Encoding};
use hyper::method::Method;
use hyper::header;
use std::collections::HashMap;
use std::str::FromStr;
use clap::ArgMatches;
use std::str;
use std::process;

fn log_request<'mw>(req: &mut Request<MvtService>, res: Response<'mw,MvtService>) -> MiddlewareResult<'mw,MvtService> {
    info!("{} {}", req.origin.method, req.origin.uri);
    res.next_middleware()
}

#[allow(dead_code)]
fn enable_cors<'mw>(_req: &mut Request, mut res: Response<'mw>) -> MiddlewareResult<'mw> {
  // access-control-allow-methods: GET
  // access-control-allow-origin: *
  res.set(AccessControlAllowMethods(vec![Method::Get]));
  res.set(AccessControlAllowOrigin::Any);

  res.next_middleware()
}

header! { (ContentType, "Content-Type") => [String] }

impl<D> Responder<D> for vector_tile::Tile {
    fn respond<'a>(self, mut res: Response<'a, D>) -> MiddlewareResult<'a, D> {
        res.set_header_fallback(|| ContentType("application/x-protobuf".to_owned()));
        res.set_header_fallback(|| CacheControl(vec![CacheDirective::MaxAge(43200u32)])); //TODO: from cache settings

        let vec = Tile::binary_tile(&self);
        res.send(vec)
    }
}

#[derive(RustcEncodable)]
struct TilesetInfo {
    name: String,
    layerinfos: String,
    hasviewer: bool,
}

impl TilesetInfo {
    fn from_tileset(set: &Tileset) -> TilesetInfo {
        let mut hasviewer = true;
        let layerinfos: Vec<String> = set.layers.iter().map(|l| {
                let geom_type = l.geometry_type.clone().unwrap_or("UNKNOWN".to_string());
                hasviewer = hasviewer && ["POINT","LINESTRING","POLYGON"].contains(&(&geom_type as &str));
                format!("{} [{}]", &l.name, &geom_type)
            }).collect();
        TilesetInfo {
            name: set.name.clone(),
            layerinfos: format!("{}", layerinfos.join(", ")),
            hasviewer: hasviewer
        }
    }
}

struct StaticFiles {
    files: HashMap<&'static str, &'static str>,
}

impl StaticFiles {
    fn new() -> StaticFiles {
        let mut static_files = HashMap::new();
        static_files.insert("tile-inspector", str::from_utf8(include_bytes!("static/tile-inspector.html")).unwrap());
        static_files.insert("vector", str::from_utf8(include_bytes!("static/vector.js")).unwrap());
        static_files.insert("xray", str::from_utf8(include_bytes!("static/xray.html")).unwrap());
        StaticFiles { files: static_files }
    }
}

struct InlineTemplate {
    template: mustache::Template,
}

impl InlineTemplate {
    fn new(template: &str) -> InlineTemplate {
        let tpl = mustache::compile_str(template);
        InlineTemplate { template: tpl }
    }
    // extracted from Nickel::Response#render
    fn render<'a, D, T>(&self, res: Response<'a, D>, data: &T)
            -> MiddlewareResult<'a, D> where T: Encodable {
        let mut stream = try!(res.start());
        match self.template.render(&mut stream, data) {
            Ok(()) => Ok(Halt(stream)),
            Err(e) => stream.bail(format!("Problem rendering template: {:?}", e))
        }
    }
}

const DEFAULT_CONFIG: &'static str = r#"
[service.mvt]
viewer = true

[webserver]
bind = "127.0.0.1"
port = 6767
threads = 4
"#;

pub fn service_from_args(args: &ArgMatches) -> (MvtService, toml::Value) {
    if let Some(cfgpath) = args.value_of("config") {
        info!("Reading configuration from '{}'", cfgpath);
        let config = read_config(cfgpath).unwrap_or_else(|err| {
                println!("Error reading configuration - {} ", err);
                process::exit(1)
            });
        let mut svc = MvtService::from_config(&config)
            .unwrap_or_else(|err| {
                println!("Error reading configuration - {} ", err);
                process::exit(1)
            });
        svc.connect();
        (svc, config)
    } else {
        let config = parse_config(DEFAULT_CONFIG.to_string(), "").unwrap();
        let cache = match args.value_of("cache") {
            None => Tilecache::Nocache(Nocache),
            Some(dir) => Tilecache::Filecache(Filecache { basepath: dir.to_string() })
        };
        let simplify = bool::from_str(args.value_of("simplify").unwrap_or("true")).unwrap_or(false);
        let clip = bool::from_str(args.value_of("clip").unwrap_or("true")).unwrap_or(false);
        if let Some(dbconn) = args.value_of("dbconn") {
            let pg = PostgisInput::new(dbconn).connected();
            let grid = Grid::web_mercator();
            let detect_geometry_types = true; //TODO: add option (maybe slow for many geometries)
            let mut layers = pg.detect_layers(detect_geometry_types);
            let mut tilesets = Vec::new();
            while let Some(mut l) = layers.pop() {
                l.simplify = Some(simplify);
                l.buffer_size = match l.geometry_type {
                    Some(ref geom) => {
                        let types = vec!["LINESTRING", "MULTILINESTRING", "POLYGON", "MULTIPOLYGON"];
                        if clip && types.contains(&(geom as &str)) { Some(1) } else { None }
                    }
                    None => None,
                };
                let tileset = Tileset{name: l.name.clone(), layers: vec![l]};
                tilesets.push(tileset);
            }
            let svc = MvtService {input: pg, grid: grid,
                tilesets: tilesets, cache: cache};
            (svc, config)
        } else {
            println!("Either 'config' or 'dbconn' is required");
            process::exit(1)
        }
    }
}

pub fn webserver(args: &ArgMatches) {
    let (mut service, config) = service_from_args(args);

    let mvt_config = config.lookup("service.mvt")
        .ok_or("Missing configuration entry [service.mvt]".to_string())
        .unwrap_or_else(|err| {
            println!("Error reading configuration - {} ", err);
            process::exit(1)
        });
    let mvt_viewer = mvt_config.lookup("viewer")
        .map_or(true, |val| val.as_bool().unwrap_or(true));
    let http_config = config.lookup("webserver")
        .ok_or("Missing configuration entry [webserver]".to_string())
        .unwrap_or_else(|err| {
            println!("Error reading configuration - {} ", err);
            process::exit(1)
        });
    let bind = http_config.lookup("bind")
        .map_or("127.0.0.1", |val| val.as_str().unwrap_or("127.0.0.1"));
    let port = http_config.lookup("port")
        .map_or(6767, |val| val.as_integer().unwrap_or(6767)) as u16;
    let threads = http_config.lookup("threads")
        .map_or(4, |val| val.as_integer().unwrap_or(4)) as usize;

    service.prepare_feature_queries();
    service.init_cache();

    let mut tileset_infos: Vec<TilesetInfo> = service.tilesets.iter().map(|set| {
        TilesetInfo::from_tileset(&set)
    }).collect();
    tileset_infos.sort_by_key(|ti| ti.name.clone());

    let mut server = Nickel::with_data(service);
    server.options = Options::default()
                     .thread_count(Some(threads));
    server.utilize(log_request);

    server.get("/index.json", middleware! { |_req, mut res|
        let service: &MvtService = res.server_data();
        res.set(MediaType::Json);
        service.get_mvt_metadata()
    });

    server.get("/:tileset.json", middleware! { |req, mut res|
        let service: &MvtService = res.server_data();
        let tileset = req.param("tileset").unwrap();
        res.set(MediaType::Json);
        let host = req.origin.headers.get::<header::Host>().unwrap();
        let baseurl = format!("http://{}:{}", host.hostname, host.port.unwrap_or(80));
        service.get_tilejson(&baseurl, &tileset)
    });

    server.get("/:tileset/metadata.json", middleware! { |req, mut res|
        let service: &MvtService = res.server_data();
        let tileset = req.param("tileset").unwrap();
        res.set(MediaType::Json);
        service.get_mbtiles_metadata(&tileset)
    });

    server.get("/:tileset/:z/:x/:y.pbf", middleware! { |req, mut res|
        let service: &MvtService = res.server_data();

        let tileset = req.param("tileset").unwrap();
        let z = req.param("z").unwrap().parse::<u8>().unwrap();
        let x = req.param("x").unwrap().parse::<u16>().unwrap();
        let y = req.param("y").unwrap().parse::<u16>().unwrap();

        let gzip = true; // TODO: From AcceptEncoding
        let tile = service.tile_cached(tileset, x, y, z, gzip);
        if gzip {
            res.set_header_fallback(|| ContentEncoding(vec![Encoding::Gzip]));
        }
        res.set_header_fallback(|| ContentType("application/x-protobuf".to_owned()));
        res.set_header_fallback(|| CacheControl(vec![CacheDirective::MaxAge(43200u32)])); //TODO: from cache settings
        //res.set_header_fallback(|| ContentLength(tile.len() as u64));
        res.set(AccessControlAllowMethods(vec![Method::Get]));
        res.set(AccessControlAllowOrigin::Any);

        tile
    });

    if mvt_viewer {
        let tpl_olviewer = InlineTemplate::new(str::from_utf8(include_bytes!("templates/olviewer.tpl")).unwrap());
        server.get("/:tileset/", middleware! { |req, res|
            let tileset = req.param("tileset").unwrap();
            let host = req.origin.headers.get::<header::Host>().unwrap();
            let baseurl = format!("http://{}:{}", host.hostname, host.port.unwrap_or(80));
            let mut data = HashMap::new();
            data.insert("baseurl", baseurl);
            data.insert("tileset", tileset.to_string());
            return tpl_olviewer.render(res, &data);
        });

        let static_files = StaticFiles::new();
        server.get("/:static", middleware! { |req, res|
            let name = req.param("static").unwrap();
            if let Some(content) = static_files.files.get(name) {
                return res.send(*content)
            }
        });

        let tpl_index = InlineTemplate::new(str::from_utf8(include_bytes!("templates/index.tpl")).unwrap());
        server.get("/", middleware! { |_req, res|
            let mut data = HashMap::new();
            data.insert("tileset", &tileset_infos);
            return tpl_index.render(res, &data);
        });
    }

    server.get("/**", StaticFilesHandler::new("public/"));

    server.listen((bind, port));
}

pub fn gen_config(args: &ArgMatches) -> String {
        let toml = r#"
[webserver]
# Bind address. Use 0.0.0.0 to listen on all adresses.
bind = "127.0.0.1"
port = 6767
threads = 4
"#;
    let mut config;
    if let Some(_dbconn) = args.value_of("dbconn") {
        let (service, _) = service_from_args(args);
        config = service.gen_runtime_config();
    } else {
        config = MvtService::gen_config();
    }
    config.push_str(toml);
    config
}

#[test]
fn test_gen_config() {
    use core::parse_config;

    let args = ArgMatches::new();
    let toml = gen_config(&args);
    println!("{}", toml);
    assert_eq!(Some("# t-rex configuration"), toml.lines().next());

    let config = parse_config(toml, "").unwrap();
    let service = MvtService::from_config(&config).unwrap();
    assert_eq!(service.input.connection_url, "postgresql://user:pass@host/database");
}

#[test]
fn test_runtime_config() {
    use std::io::{self,Write};
    use std::env;
    use clap::App;
    use core::parse_config;

    if env::var("DBCONN").is_err() {
        write!(&mut io::stdout(), "skipped ").unwrap();
        return;
    }
    let args = App::new("test")
                .args_from_usage("--dbconn=[SPEC] 'PostGIS connection postgresql://USER@HOST/DBNAME'")
                .get_matches_from(vec!["", "--dbconn", &env::var("DBCONN").unwrap()]);
    let toml = gen_config(&args);
    println!("{}", toml);
    assert_eq!(Some("# t-rex configuration"), toml.lines().next());

    let config = parse_config(toml, "").unwrap();
    let service = MvtService::from_config(&config).unwrap();
    assert_eq!(service.input.connection_url, env::var("DBCONN").unwrap());
}
<commit_msg>Return also CORS headers for JSON requests<commit_after>//
// Copyright (c) Pirmin Kalberer. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
//

use datasource::postgis::PostgisInput;
use core::grid::Grid;
use mvt::tile::Tile;
use mvt::vector_tile;
use service::mvt::{MvtService,Tileset};
use core::{Config,read_config,parse_config};
use toml;
use cache::{Tilecache,Nocache,Filecache};

use nickel::{Nickel, Options, HttpRouter, MediaType, Request, Responder, Response, MiddlewareResult, Halt, StaticFilesHandler};
use mustache;
use rustc_serialize::Encodable;
use hyper::header::{CacheControl, CacheDirective, AccessControlAllowOrigin, AccessControlAllowMethods, ContentEncoding, Encoding};
use hyper::method::Method;
use hyper::header;
use std::collections::HashMap;
use std::str::FromStr;
use clap::ArgMatches;
use std::str;
use std::process;

fn log_request<'mw>(req: &mut Request<MvtService>, res: Response<'mw,MvtService>) -> MiddlewareResult<'mw,MvtService> {
    info!("{} {}", req.origin.method, req.origin.uri);
    res.next_middleware()
}

#[allow(dead_code)]
fn enable_cors<'mw>(_req: &mut Request<MvtService>, mut res: Response<'mw,MvtService>) -> MiddlewareResult<'mw,MvtService> {
  // access-control-allow-methods: GET
  // access-control-allow-origin: *
  // see also https://github.com/nickel-org/nickel.rs/blob/master/examples/enable_cors.rs
  res.set(AccessControlAllowMethods(vec![Method::Get]));
  res.set(AccessControlAllowOrigin::Any);

  res.next_middleware()
}

header! { (ContentType, "Content-Type") => [String] }

impl<D> Responder<D> for vector_tile::Tile {
    fn respond<'a>(self, mut res: Response<'a, D>) -> MiddlewareResult<'a, D> {
        res.set_header_fallback(|| ContentType("application/x-protobuf".to_owned()));
        res.set_header_fallback(|| CacheControl(vec![CacheDirective::MaxAge(43200u32)])); //TODO: from cache settings

        let vec = Tile::binary_tile(&self);
        res.send(vec)
    }
}

#[derive(RustcEncodable)]
struct TilesetInfo {
    name: String,
    layerinfos: String,
    hasviewer: bool,
}

impl TilesetInfo {
    fn from_tileset(set: &Tileset) -> TilesetInfo {
        let mut hasviewer = true;
        let layerinfos: Vec<String> = set.layers.iter().map(|l| {
                let geom_type = l.geometry_type.clone().unwrap_or("UNKNOWN".to_string());
                hasviewer = hasviewer && ["POINT","LINESTRING","POLYGON"].contains(&(&geom_type as &str));
                format!("{} [{}]", &l.name, &geom_type)
            }).collect();
        TilesetInfo {
            name: set.name.clone(),
            layerinfos: format!("{}", layerinfos.join(", ")),
            hasviewer: hasviewer
        }
    }
}

struct StaticFiles {
    files: HashMap<&'static str, &'static str>,
}

impl StaticFiles {
    fn new() -> StaticFiles {
        let mut static_files = HashMap::new();
        static_files.insert("tile-inspector", str::from_utf8(include_bytes!("static/tile-inspector.html")).unwrap());
        static_files.insert("vector", str::from_utf8(include_bytes!("static/vector.js")).unwrap());
        static_files.insert("xray", str::from_utf8(include_bytes!("static/xray.html")).unwrap());
        StaticFiles { files: static_files }
    }
}

struct InlineTemplate {
    template: mustache::Template,
}

impl InlineTemplate {
    fn new(template: &str) -> InlineTemplate {
        let tpl = mustache::compile_str(template);
        InlineTemplate { template: tpl }
    }
    // extracted from Nickel::Response#render
    fn render<'a, D, T>(&self, res: Response<'a, D>, data: &T)
            -> MiddlewareResult<'a, D> where T: Encodable {
        let mut stream = try!(res.start());
        match self.template.render(&mut stream, data) {
            Ok(()) => Ok(Halt(stream)),
            Err(e) => stream.bail(format!("Problem rendering template: {:?}", e))
        }
    }
}

const DEFAULT_CONFIG: &'static str = r#"
[service.mvt]
viewer = true

[webserver]
bind = "127.0.0.1"
port = 6767
threads = 4
"#;

pub fn service_from_args(args: &ArgMatches) -> (MvtService, toml::Value) {
    if let Some(cfgpath) = args.value_of("config") {
        info!("Reading configuration from '{}'", cfgpath);
        let config = read_config(cfgpath).unwrap_or_else(|err| {
                println!("Error reading configuration - {} ", err);
                process::exit(1)
            });
        let mut svc = MvtService::from_config(&config)
            .unwrap_or_else(|err| {
                println!("Error reading configuration - {} ", err);
                process::exit(1)
            });
        svc.connect();
        (svc, config)
    } else {
        let config = parse_config(DEFAULT_CONFIG.to_string(), "").unwrap();
        let cache = match args.value_of("cache") {
            None => Tilecache::Nocache(Nocache),
            Some(dir) => Tilecache::Filecache(Filecache { basepath: dir.to_string() })
        };
        let simplify = bool::from_str(args.value_of("simplify").unwrap_or("true")).unwrap_or(false);
        let clip = bool::from_str(args.value_of("clip").unwrap_or("true")).unwrap_or(false);
        if let Some(dbconn) = args.value_of("dbconn") {
            let pg = PostgisInput::new(dbconn).connected();
            let grid = Grid::web_mercator();
            let detect_geometry_types = true; //TODO: add option (maybe slow for many geometries)
            let mut layers = pg.detect_layers(detect_geometry_types);
            let mut tilesets = Vec::new();
            while let Some(mut l) = layers.pop() {
                l.simplify = Some(simplify);
                l.buffer_size = match l.geometry_type {
                    Some(ref geom) => {
                        let types = vec!["LINESTRING", "MULTILINESTRING", "POLYGON", "MULTIPOLYGON"];
                        if clip && types.contains(&(geom as &str)) { Some(1) } else { None }
                    }
                    None => None,
                };
                let tileset = Tileset{name: l.name.clone(), layers: vec![l]};
                tilesets.push(tileset);
            }
            let svc = MvtService {input: pg, grid: grid,
                tilesets: tilesets, cache: cache};
            (svc, config)
        } else {
            println!("Either 'config' or 'dbconn' is required");
            process::exit(1)
        }
    }
}

pub fn webserver(args: &ArgMatches) {
    let (mut service, config) = service_from_args(args);

    let mvt_config = config.lookup("service.mvt")
        .ok_or("Missing configuration entry [service.mvt]".to_string())
        .unwrap_or_else(|err| {
            println!("Error reading configuration - {} ", err);
            process::exit(1)
        });
    let mvt_viewer = mvt_config.lookup("viewer")
        .map_or(true, |val| val.as_bool().unwrap_or(true));
    let http_config = config.lookup("webserver")
        .ok_or("Missing configuration entry [webserver]".to_string())
        .unwrap_or_else(|err| {
            println!("Error reading configuration - {} ", err);
            process::exit(1)
        });
    let bind = http_config.lookup("bind")
        .map_or("127.0.0.1", |val| val.as_str().unwrap_or("127.0.0.1"));
    let port = http_config.lookup("port")
        .map_or(6767, |val| val.as_integer().unwrap_or(6767)) as u16;
    let threads = http_config.lookup("threads")
        .map_or(4, |val| val.as_integer().unwrap_or(4)) as usize;

    service.prepare_feature_queries();
    service.init_cache();

    let mut tileset_infos: Vec<TilesetInfo> = service.tilesets.iter().map(|set| {
        TilesetInfo::from_tileset(&set)
    }).collect();
    tileset_infos.sort_by_key(|ti| ti.name.clone());

    let mut server = Nickel::with_data(service);
    server.options = Options::default()
                     .thread_count(Some(threads));
    server.utilize(log_request);

    server.get("/index.json", middleware! { |_req, mut res|
        let service: &MvtService = res.server_data();
        res.set(MediaType::Json);
        res.set(AccessControlAllowMethods(vec![Method::Get]));
        res.set(AccessControlAllowOrigin::Any);
        service.get_mvt_metadata()
    });

    server.get("/:tileset.json", middleware! { |req, mut res|
        let service: &MvtService = res.server_data();
        let tileset = req.param("tileset").unwrap();
        res.set(MediaType::Json);
        res.set(AccessControlAllowMethods(vec![Method::Get]));
        res.set(AccessControlAllowOrigin::Any);
        let host = req.origin.headers.get::<header::Host>().unwrap();
        let baseurl = format!("http://{}:{}", host.hostname, host.port.unwrap_or(80));
        service.get_tilejson(&baseurl, &tileset)
    });

    server.get("/:tileset/metadata.json", middleware! { |req, mut res|
        let service: &MvtService = res.server_data();
        let tileset = req.param("tileset").unwrap();
        res.set(MediaType::Json);
        service.get_mbtiles_metadata(&tileset)
    });

    server.get("/:tileset/:z/:x/:y.pbf", middleware! { |req, mut res|
        let service: &MvtService = res.server_data();

        let tileset = req.param("tileset").unwrap();
        let z = req.param("z").unwrap().parse::<u8>().unwrap();
        let x = req.param("x").unwrap().parse::<u16>().unwrap();
        let y = req.param("y").unwrap().parse::<u16>().unwrap();

        let gzip = true; // TODO: From AcceptEncoding
        let tile = service.tile_cached(tileset, x, y, z, gzip);
        if gzip {
            res.set_header_fallback(|| ContentEncoding(vec![Encoding::Gzip]));
        }
        res.set_header_fallback(|| ContentType("application/x-protobuf".to_owned()));
        res.set_header_fallback(|| CacheControl(vec![CacheDirective::MaxAge(43200u32)])); //TODO: from cache settings
        //res.set_header_fallback(|| ContentLength(tile.len() as u64));
        res.set(AccessControlAllowMethods(vec![Method::Get]));
        res.set(AccessControlAllowOrigin::Any);

        tile
    });

    if mvt_viewer {
        let tpl_olviewer = InlineTemplate::new(str::from_utf8(include_bytes!("templates/olviewer.tpl")).unwrap());
        server.get("/:tileset/", middleware! { |req, res|
            let tileset = req.param("tileset").unwrap();
            let host = req.origin.headers.get::<header::Host>().unwrap();
            let baseurl = format!("http://{}:{}", host.hostname, host.port.unwrap_or(80));
            let mut data = HashMap::new();
            data.insert("baseurl", baseurl);
            data.insert("tileset", tileset.to_string());
            return tpl_olviewer.render(res, &data);
        });

        let static_files = StaticFiles::new();
        server.get("/:static", middleware! { |req, res|
            let name = req.param("static").unwrap();
            if let Some(content) = static_files.files.get(name) {
                return res.send(*content)
            }
        });

        let tpl_index = InlineTemplate::new(str::from_utf8(include_bytes!("templates/index.tpl")).unwrap());
        server.get("/", middleware! { |_req, res|
            let mut data = HashMap::new();
            data.insert("tileset", &tileset_infos);
            return tpl_index.render(res, &data);
        });
    }

    server.get("/**", StaticFilesHandler::new("public/"));

    server.listen((bind, port));
}

pub fn gen_config(args: &ArgMatches) -> String {
        let toml = r#"
[webserver]
# Bind address. Use 0.0.0.0 to listen on all adresses.
bind = "127.0.0.1"
port = 6767
threads = 4
"#;
    let mut config;
    if let Some(_dbconn) = args.value_of("dbconn") {
        let (service, _) = service_from_args(args);
        config = service.gen_runtime_config();
    } else {
        config = MvtService::gen_config();
    }
    config.push_str(toml);
    config
}

#[test]
fn test_gen_config() {
    use core::parse_config;

    let args = ArgMatches::new();
    let toml = gen_config(&args);
    println!("{}", toml);
    assert_eq!(Some("# t-rex configuration"), toml.lines().next());

    let config = parse_config(toml, "").unwrap();
    let service = MvtService::from_config(&config).unwrap();
    assert_eq!(service.input.connection_url, "postgresql://user:pass@host/database");
}

#[test]
fn test_runtime_config() {
    use std::io::{self,Write};
    use std::env;
    use clap::App;
    use core::parse_config;

    if env::var("DBCONN").is_err() {
        write!(&mut io::stdout(), "skipped ").unwrap();
        return;
    }
    let args = App::new("test")
                .args_from_usage("--dbconn=[SPEC] 'PostGIS connection postgresql://USER@HOST/DBNAME'")
                .get_matches_from(vec!["", "--dbconn", &env::var("DBCONN").unwrap()]);
    let toml = gen_config(&args);
    println!("{}", toml);
    assert_eq!(Some("# t-rex configuration"), toml.lines().next());

    let config = parse_config(toml, "").unwrap();
    let service = MvtService::from_config(&config).unwrap();
    assert_eq!(service.input.connection_url, env::var("DBCONN").unwrap());
}
<|endoftext|>


<commit_before>use neb::ram::cell;
use neb::ram::cell::*;
use neb::ram::schema::*;
use neb::ram::chunk::Chunks;
use neb::ram::types;
use neb::ram::types::*;
use neb::ram::io::writer;
use neb::ram::cleaner::Cleaner;
use neb::ram::schema::Field;
use neb::server::ServerMeta;
use neb::ram::cleaner::*;
use neb::ram::tombstone::Tombstone;
use neb::ram::entry::{EntryType, EntryContent};
use env_logger;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Arc;
use std;
use std::rc::Rc;
use super::*;
use neb::ram::chunk::Chunk;

pub const DATA_SIZE: usize = 1000 * 1024; // nearly 1MB
const MAX_SEGMENT_SIZE: usize = 8 * 1024 * 1024;

fn default_cell(id: &Id) -> Cell {
    let data: Vec<_> =
        std::iter::repeat(Value::U8(id.lower as u8))
            .take(DATA_SIZE)
            .collect();
    Cell {
        header: CellHeader::new(0, 0, id, 0),
        data: data_map_value!(id: id.lower as i32, data: data)
    }
}

fn default_fields () -> Field {
    Field::new ("*", 0, false, false, Some(
        vec![
            Field::new("id", type_id_of(Type::I32), false, false, None),
            Field::new("data", type_id_of(Type::U8), false, true, None)
        ]
    ))
}

fn seg_positions(chunk: &Chunk) -> Vec<(u64, usize)> {
    chunk.addrs_seg
        .read()
        .iter()
        .map(|(pos, hash)| (*hash, *pos))
        .collect()
}

#[test]
pub fn full_clean_cycle() {
    env_logger::init();
    let schema = Schema::new(
        "cleaner_test",
        None,
        default_fields(),
        false);
    let schemas = LocalSchemasCache::new("", None).unwrap();
    schemas.new_schema(schema);
    let chunks = Chunks::new(
        1, // single chunk
        MAX_SEGMENT_SIZE * 2, // chunk two segments
        Arc::new(ServerMeta { schemas }),
        None);
    let chunk = &chunks.list[0];

    assert_eq!(chunk.segments().len(), 1);

    // put 16 cells to fill up all of those segments allocated
    for i in 0..16 {
        let mut cell = default_cell(&Id::new(0, i));
        chunks.write_cell(&mut cell).unwrap();

    }

    assert_eq!(chunk.segments().len(), 2);
    assert_eq!(chunk.index.len(), 16);

    println!("trying to delete cells");

    let all_seg_positions = seg_positions(chunk);
    let all_cell_addresses = chunk.cell_addresses();
    assert_eq!(all_seg_positions.len(), 2);
    assert_eq!(all_cell_addresses.len(), 16);

    for i in 0..8 {
        chunks.remove_cell(&Id::new(0, i * 2)).unwrap();
    }

    assert_eq!(chunk.segments().len(), 2);

    // try to scan first segment expect no panic
    println!("Scanning first segment...");
    chunk.live_entries(&chunk.segs.get(&0).unwrap());

    println!("Scanning second segment for tombstones...");
    let live_entries = chunk.live_entries(&chunk.segs.get(&1).unwrap());
    let tombstones: Vec<_> = live_entries
        .iter()
        .filter(|e| e.meta.entry_header.entry_type == EntryType::Tombstone)
        .collect();
    assert_eq!(tombstones.len(), 8);
    for i in 0..tombstones.len() {
        let hash = (i * 2) as u64;
        let e = &tombstones[i];
        assert_eq!(e.meta.entry_header.entry_type, EntryType::Tombstone);
        if let EntryContent::Tombstone(ref t) = e.content {
            assert_eq!(t.hash, hash);
            assert_eq!(t.partition, 0);
        } else { panic!(); }
    }

    assert_eq!(chunk.cell_addresses().len(), 8);

    chunk.apply_dead_entry();

    // Compact all segments order by id
    let mut segments = chunk.segments();
    segments.sort_by_key(|seg| seg.id);
    segments.into_iter()
        .for_each(|seg|
            compact::CompactCleaner::clean_segment(chunk, &seg));

    let compacted_seg_positions = seg_positions(chunk);
    let compacted_cell_addresses = chunk.cell_addresses();
    assert_eq!(compacted_seg_positions.len(), 2);
    assert_eq!(compacted_cell_addresses.len(), 8);

    // scan segments to check entries
    let seg0 = &chunk.segs.get(&0).unwrap();
    let seg1 = &chunk.segs.get(&1).unwrap();
    let compacted_segment_0_entries = chunk.live_entries(seg0);
    let compacted_segment_1_entries = chunk.live_entries(seg1);
    let compacted_segment_0_ids = (0..4).map(|num| num as u64 * 2 + 1);
    let compacted_segment_1_ids =
        (4..8).map(|num| num as i64 * 2 + 1)
        .chain((0..8).map(|i| -1 * i * 2));
    assert_eq!(seg0.id, 0);
    assert_eq!(seg1.id, 1);
    // check for cells continuity
    compacted_segment_0_entries
        .iter()
        .zip(compacted_segment_0_ids)
        .for_each(|(entry, hash)| {
            assert_eq!(entry.meta.entry_header.entry_type, EntryType::Cell);
            if let EntryContent::Cell(header) = entry.content {
                assert_eq!(header.hash, hash)
            } else { panic!(); }
        });

    // check for cells and 4 tombstones
    compacted_segment_1_entries
        .iter()
        .zip(compacted_segment_1_ids)
        .for_each(|(entry, hash)|{
            if hash > 1 {
                assert_eq!(entry.meta.entry_header.entry_type, EntryType::Cell);
                if let EntryContent::Cell(header) = entry.content {
                    assert_eq!(header.hash, hash as u64)
                } else { panic!(); }
            } else {
                assert_eq!(entry.meta.entry_header.entry_type, EntryType::Tombstone);
                let tombstone = if let EntryContent::Tombstone(ref tombstone) = entry.content {
                    tombstone
                } else { panic!() };
                assert_eq!((hash * -1) as u64, tombstone.hash);
            }
        })
}<commit_msg>check number of entries in segments<commit_after>use neb::ram::cell;
use neb::ram::cell::*;
use neb::ram::schema::*;
use neb::ram::chunk::Chunks;
use neb::ram::types;
use neb::ram::types::*;
use neb::ram::io::writer;
use neb::ram::cleaner::Cleaner;
use neb::ram::schema::Field;
use neb::server::ServerMeta;
use neb::ram::cleaner::*;
use neb::ram::tombstone::Tombstone;
use neb::ram::entry::{EntryType, EntryContent};
use env_logger;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Arc;
use std;
use std::rc::Rc;
use super::*;
use neb::ram::chunk::Chunk;

pub const DATA_SIZE: usize = 1000 * 1024; // nearly 1MB
const MAX_SEGMENT_SIZE: usize = 8 * 1024 * 1024;

fn default_cell(id: &Id) -> Cell {
    let data: Vec<_> =
        std::iter::repeat(Value::U8(id.lower as u8))
            .take(DATA_SIZE)
            .collect();
    Cell {
        header: CellHeader::new(0, 0, id, 0),
        data: data_map_value!(id: id.lower as i32, data: data)
    }
}

fn default_fields () -> Field {
    Field::new ("*", 0, false, false, Some(
        vec![
            Field::new("id", type_id_of(Type::I32), false, false, None),
            Field::new("data", type_id_of(Type::U8), false, true, None)
        ]
    ))
}

fn seg_positions(chunk: &Chunk) -> Vec<(u64, usize)> {
    chunk.addrs_seg
        .read()
        .iter()
        .map(|(pos, hash)| (*hash, *pos))
        .collect()
}

#[test]
pub fn full_clean_cycle() {
    env_logger::init();
    let schema = Schema::new(
        "cleaner_test",
        None,
        default_fields(),
        false);
    let schemas = LocalSchemasCache::new("", None).unwrap();
    schemas.new_schema(schema);
    let chunks = Chunks::new(
        1, // single chunk
        MAX_SEGMENT_SIZE * 2, // chunk two segments
        Arc::new(ServerMeta { schemas }),
        None);
    let chunk = &chunks.list[0];

    assert_eq!(chunk.segments().len(), 1);

    // put 16 cells to fill up all of those segments allocated
    for i in 0..16 {
        let mut cell = default_cell(&Id::new(0, i));
        chunks.write_cell(&mut cell).unwrap();

    }

    assert_eq!(chunk.segments().len(), 2);
    assert_eq!(chunk.index.len(), 16);

    println!("trying to delete cells");

    let all_seg_positions = seg_positions(chunk);
    let all_cell_addresses = chunk.cell_addresses();
    assert_eq!(all_seg_positions.len(), 2);
    assert_eq!(all_cell_addresses.len(), 16);

    for i in 0..8 {
        chunks.remove_cell(&Id::new(0, i * 2)).unwrap();
    }

    assert_eq!(chunk.segments().len(), 2);

    // try to scan first segment expect no panic
    println!("Scanning first segment...");
    chunk.live_entries(&chunk.segs.get(&0).unwrap());

    println!("Scanning second segment for tombstones...");
    let live_entries = chunk.live_entries(&chunk.segs.get(&1).unwrap());
    let tombstones: Vec<_> = live_entries
        .iter()
        .filter(|e| e.meta.entry_header.entry_type == EntryType::Tombstone)
        .collect();
    assert_eq!(tombstones.len(), 8);
    for i in 0..tombstones.len() {
        let hash = (i * 2) as u64;
        let e = &tombstones[i];
        assert_eq!(e.meta.entry_header.entry_type, EntryType::Tombstone);
        if let EntryContent::Tombstone(ref t) = e.content {
            assert_eq!(t.hash, hash);
            assert_eq!(t.partition, 0);
        } else { panic!(); }
    }

    assert_eq!(chunk.cell_addresses().len(), 8);

    chunk.apply_dead_entry();

    // Compact all segments order by id
    let mut segments = chunk.segments();
    segments.sort_by_key(|seg| seg.id);
    segments.into_iter()
        .for_each(|seg|
            compact::CompactCleaner::clean_segment(chunk, &seg));

    let compacted_seg_positions = seg_positions(chunk);
    let compacted_cell_addresses = chunk.cell_addresses();
    assert_eq!(compacted_seg_positions.len(), 2);
    assert_eq!(compacted_cell_addresses.len(), 8);

    // scan segments to check entries
    let seg0 = &chunk.segs.get(&0).unwrap();
    let seg1 = &chunk.segs.get(&1).unwrap();
    let compacted_segment_0_entries = chunk.live_entries(seg0);
    let compacted_segment_1_entries = chunk.live_entries(seg1);
    let compacted_segment_0_ids = (0..4).map(|num| num as u64 * 2 + 1);
    let compacted_segment_1_ids =
        (4..8).map(|num| num as i64 * 2 + 1)
        .chain((0..8).map(|i| -1 * i * 2));
    assert_eq!(seg0.id, 0);
    assert_eq!(seg1.id, 1);
    // check for cells continuity
    compacted_segment_0_entries
        .iter()
        .zip(compacted_segment_0_ids)
        .for_each(|(entry, hash)| {
            assert_eq!(entry.meta.entry_header.entry_type, EntryType::Cell);
            if let EntryContent::Cell(header) = entry.content {
                assert_eq!(header.hash, hash)
            } else { panic!(); }
        });
    assert_eq!(compacted_segment_0_entries.len(), 4);

    // check for cells and 4 tombstones
    compacted_segment_1_entries
        .iter()
        .zip(compacted_segment_1_ids)
        .for_each(|(entry, hash)|{
            if hash > 1 {
                assert_eq!(entry.meta.entry_header.entry_type, EntryType::Cell);
                if let EntryContent::Cell(header) = entry.content {
                    assert_eq!(header.hash, hash as u64)
                } else { panic!(); }
            } else {
                assert_eq!(entry.meta.entry_header.entry_type, EntryType::Tombstone);
                let tombstone = if let EntryContent::Tombstone(ref tombstone) = entry.content {
                    tombstone
                } else { panic!() };
                assert_eq!((hash * -1) as u64, tombstone.hash);
            }
        });
    assert_eq!(compacted_segment_1_entries.len(), 12);
}<|endoftext|>


<commit_before>// Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#[test]
fn dogfood() {
    if option_env!("RUSTC_TEST_SUITE").is_some() || cfg!(windows) {
        return;
    }
    let root_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let clippy_cmd = std::path::Path::new(&root_dir).join("target/debug/cargo-clippy");

    println!("{:?}", clippy_cmd);
    let output = std::process::Command::new(clippy_cmd)
        .arg("clippy")
        .arg("--all-targets")
        .arg("--all-features")
        .arg("--")
        .args(&["-D", "clippy::all"])
        .args(&["-D", "clippy::internal"])
        .args(&["-D", "clippy::pedantic"])
        .output()
        .unwrap();
    println!("status: {}", output.status);
    println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
    println!("stderr: {}", String::from_utf8_lossy(&output.stderr));

    assert!(output.status.success());
}

#[test]
fn dogfood_tests() {
    if option_env!("RUSTC_TEST_SUITE").is_some() || cfg!(windows) {
        return;
    }
    let root_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));

    for d in &[
        "clippy_workspace_tests",
        "clippy_workspace_tests/src",
        "clippy_workspace_tests/subcrate",
        "clippy_workspace_tests/subcrate/src",
        "clippy_dev",
        "rustc_tools_util",
    ] {
        let clippy_cmd = std::path::Path::new(&root_dir)
            .join("target/debug/cargo-clippy");
        std::env::set_current_dir(root_dir.join(d)).unwrap();
        let output = std::process::Command::new(clippy_cmd)
            .arg("clippy")
            .arg("--")
            .args(&["-D", "clippy::all"])
            .args(&["-D", "clippy::pedantic"])
            .output()
            .unwrap();
        println!("status: {}", output.status);
        println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
        println!("stderr: {}", String::from_utf8_lossy(&output.stderr));

        assert!(output.status.success());
    }
    std::env::set_current_dir(root_dir).unwrap();
}
<commit_msg>Use dogfood_runner for deterministic test ordering<commit_after>// Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#[test]
fn dogfood_runner() {
    dogfood();
    dogfood_tests();
}

fn dogfood() {
    if option_env!("RUSTC_TEST_SUITE").is_some() || cfg!(windows) {
        return;
    }
    let root_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let clippy_cmd = std::path::Path::new(&root_dir).join("target/debug/cargo-clippy");

    std::env::set_current_dir(root_dir).unwrap();
    let output = std::process::Command::new(clippy_cmd)
        .arg("clippy")
        .arg("--all-targets")
        .arg("--all-features")
        .arg("--")
        .args(&["-D", "clippy::all"])
        .args(&["-D", "clippy::internal"])
        .args(&["-D", "clippy::pedantic"])
        .output()
        .unwrap();
    println!("status: {}", output.status);
    println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
    println!("stderr: {}", String::from_utf8_lossy(&output.stderr));

    assert!(output.status.success());
}

fn dogfood_tests() {
    if option_env!("RUSTC_TEST_SUITE").is_some() || cfg!(windows) {
        return;
    }
    let root_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));

    for d in &[
        "clippy_workspace_tests",
        "clippy_workspace_tests/src",
        "clippy_workspace_tests/subcrate",
        "clippy_workspace_tests/subcrate/src",
        "clippy_dev",
        "rustc_tools_util",
    ] {
        let clippy_cmd = std::path::Path::new(&root_dir)
            .join("target/debug/cargo-clippy");
        std::env::set_current_dir(root_dir.join(d)).unwrap();
        let output = std::process::Command::new(clippy_cmd)
            .arg("clippy")
            .arg("--")
            .args(&["-D", "clippy::all"])
            .args(&["-D", "clippy::pedantic"])
            .output()
            .unwrap();
        println!("status: {}", output.status);
        println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
        println!("stderr: {}", String::from_utf8_lossy(&output.stderr));

        assert!(output.status.success());
    }
}
<|endoftext|>


<commit_before>extern crate ultra;

use ultra::Enigma;

#[test]
fn expected_ciphertext() {
    let mut enigma = Enigma::new("123", "BAT", "HTU", 'B', "");
    assert_eq!(enigma.encrypt("THEQUICKBROWNFOX"), "USSXBXPNRLBSTKQR");
}

#[test]
fn turnover_points() {
	let mut enigma = Enigma::new("123", "AAA", "ADU", 'B', "");
	assert_eq!(enigma.encrypt("THEQUICKBROWNFOX"), "ACGXKHKYCBVQZMJM");
}
<commit_msg>Convert indentation to spaces<commit_after>extern crate ultra;

use ultra::Enigma;

#[test]
fn expected_ciphertext() {
    let mut enigma = Enigma::new("123", "BAT", "HTU", 'B', "");
    assert_eq!(enigma.encrypt("THEQUICKBROWNFOX"), "USSXBXPNRLBSTKQR");
}

#[test]
fn turnover_points() {
    let mut enigma = Enigma::new("123", "AAA", "ADU", 'B', "");
    assert_eq!(enigma.encrypt("THEQUICKBROWNFOX"), "ACGXKHKYCBVQZMJM");
}
<|endoftext|>


<commit_before>use imageflow_helpers::preludes::from_std::*;
use imageflow_types as s;

pub mod parsing;
mod layout;

use crate::sizing;
use crate::sizing::prelude::*;
use crate::ir4::parsing::*;
use crate::ir4::layout::*;

pub use layout::ConstraintResults;

pub fn process_constraint(source_w: i32, source_h: i32, constraint: &imageflow_types::Constraint) -> sizing::Result<ConstraintResults>{
    layout::Ir4Layout::process_constraint(source_w,source_h, constraint)
}

pub enum Ir4Command{
    Instructions(Box<Instructions>),
    Url(String),
    QueryString(String)
}

impl Ir4Command{
    pub fn parse(&self) -> sizing::Result<Ir4Result> {
        let (i, warn) = match *self {
            Ir4Command::Url(ref url) => parsing::parse_url(&::url::Url::from_str(url).expect("ImageResizer4 Url cannot be parsed into instructions: invalid URI")),
            Ir4Command::Instructions(ref i) => (**i, vec![]),
            Ir4Command::QueryString(ref s) => {
                let url = ::url::Url::from_str(&format!("https://fakeurl/img.jpg?{}", s)).expect("Must be a valid querystring, excluding ?");
                parsing::parse_url(&url)
            }
        };
        Ok(Ir4Result{
            parse_warnings: warn,
            parsed: i,
            steps: None,
            canvas: None
        })
    }

}

/// Minimal translation into framewise (delay as much as possible)
pub struct Ir4Translate{
    pub i: Ir4Command,
    pub decode_id: Option<i32>,
    pub encode_id: Option<i32>,
}

// If using trim.threshold, delayed expansion is required.

pub struct Ir4Result{
    pub parse_warnings: Vec<parsing::ParseWarning>,
    pub parsed: Instructions,
    pub steps: Option<Vec<s::Node>>,
    pub canvas: Option<AspectRatio>
}

impl Ir4Translate{

    pub fn get_decode_node(&self) -> Option<s::Node>{
        if let Some(id) = self.decode_id {
            Some(s::Node::Decode { io_id: id, commands: None })
        }else{
            None
        }
    }

    pub fn translate(&self) -> sizing::Result<Ir4Result> {
        let mut r = self.i.parse()?;
        let mut b = crate::ir4::layout::FramewiseBuilder::new();
        //Expand decoder early if trimming
        let delayed_id = if r.parsed.trim_whitespace_threshold.is_some() {
            if let Some(n) = self.get_decode_node() {
                b.add(n);
            }
            None
        } else {
            self.decode_id
        };
        // Add CropWhitespace
        if r.parsed.trim_whitespace_threshold.is_some(){
            b.add(s::Node::CropWhitespace {
                threshold: cmp::max(0,r.parsed.trim_whitespace_threshold.unwrap()) as u32,
                percent_padding: r.parsed.trim_whitespace_padding_percent.unwrap_or(0f64) as f32
            });
        }

        //delete whitespace from instructions
        let mut without_trimming: Instructions = r.parsed;
        without_trimming.trim_whitespace_padding_percent = None;
        without_trimming.trim_whitespace_threshold = None;

        b.add(s::Node::CommandString {
            kind: s::CommandStringKind::ImageResizer4,
            value: without_trimming.to_string(),
            decode: delayed_id,
            encode: self.encode_id
        });

        r.steps = Some(b.into_steps());
        Ok(r)
    }
}

pub struct Ir4SourceFrameInfo{
    pub w: i32,
    pub h: i32,
    pub fmt: s::PixelFormat,
    pub original_mime: Option<String>,
}

impl Ir4SourceFrameInfo{

    fn get_format_from_mime(&self) -> Option<OutputFormat>{
        self.original_mime.as_ref().and_then(|f|
                    match f.as_str(){
                        "image/jpeg" => Some(OutputFormat::Jpeg),
                        "image/png" => Some(OutputFormat::Png),
                        "image/gif" => Some(OutputFormat::Gif),
                        "image/webp" => Some(OutputFormat::Webp),
                        _ => None
                    })
    }
    fn get_format_from_frame(&self) -> OutputFormat{
        match self.fmt{
            s::PixelFormat::Bgr24 | s::PixelFormat::Bgr32 => OutputFormat::Jpeg,
            _ => OutputFormat::Png
        }
    }
}

/// Cannot expand decoder. use `Ir4Translate` for that.
pub struct Ir4Expand{
    pub i: Ir4Command,
    pub source: Ir4SourceFrameInfo,
    pub encode_id: Option<i32>,

}

impl Ir4Expand{

    pub fn get_decode_commands(&self) -> sizing::Result<Option<Vec<s::DecoderCommand>>> { //TODO: consider smallvec or generalizing decoder hints
        let i = self.i.parse()?.parsed;

        // Default to gamma correct
        let gamma_correct = i.down_colorspace != Some(ScalingColorspace::Srgb);

        let layout = self.get_layout(&i)?;
        let (from, to): (AspectRatio, AspectRatio) = layout.get_downscaling()?;

        let downscale_ratio = (f64::from(from.w) / f64::from(to.w)).min(f64::from(from.h) / f64::from(to.w));

        let preshrink_ratio = i.min_precise_scaling_ratio.unwrap_or(2.1f64) / downscale_ratio;

        let scaled_width = (f64::from(self.source.w) * preshrink_ratio).floor() as i64;
        let scaled_height = (f64::from(self.source.h) * preshrink_ratio).floor() as i64;
        if preshrink_ratio < 1f64 {
            let mut vec = Vec::with_capacity(2);
            vec.push(s::DecoderCommand::JpegDownscaleHints(s::JpegIDCTDownscaleHints {
                scale_luma_spatially: Some(gamma_correct),
                gamma_correct_for_srgb_during_spatial_luma_scaling: Some(gamma_correct),
                width: scaled_width,
                height: scaled_height
            }));
            if !gamma_correct{
                vec.push(s::DecoderCommand::WebPDecoderHints(s::WebPDecoderHints{
                    width: scaled_width as i32,
                    height: scaled_height as i32,
                }));
            }
            Ok(Some(vec))
        } else {
            Ok(None)
        }
    }

    pub fn get_canvas_size(&self) -> sizing::Result<AspectRatio>{
        let i = self.i.parse()?.parsed;
        let (_, layout) = self.get_layout(&i).unwrap().get_crop_and_layout().unwrap();
        Ok(layout.get_box(BoxTarget::CurrentCanvas))
    }

    pub fn get_layout(&self, i: &Instructions) -> sizing::Result<Ir4Layout> {
        if i.trim_whitespace_threshold.is_some() {
            return Err(sizing::LayoutError::ContentDependent);
        }
        Ok(layout::Ir4Layout::new(*i, self.source.w, self.source.h))
    }

    pub fn expand_steps(&self) -> sizing::Result<Ir4Result> {
        let mut r = self.i.parse()?;

        let layout = self.get_layout(&r.parsed)?;

        let mut b = FramewiseBuilder::new();
        r.canvas = Some(layout.add_steps(&mut b)?.canvas);

        if let Some(n) = self.get_encoder_node(&r.parsed) {
            b.add(n);
        }
        r.steps = Some(b.into_steps());
        Ok(r)

    }

    pub fn get_encoder_node(&self, i: &Instructions) -> Option<s::Node>{

        if let Some(id) = self.encode_id {

            let format = i.format.or_else(|| self.source.get_format_from_mime())
                .unwrap_or_else(|| self.source.get_format_from_frame());

            let encoder = match format {
                OutputFormat::Gif => s::EncoderPreset::Gif,
                OutputFormat::Jpeg => s::EncoderPreset::LibjpegTurbo {
                    quality: Some(i.quality.unwrap_or(90)),
                    optimize_huffman_coding: i.jpeg_progressive,
                    progressive: i.jpeg_progressive
                    //TODO: support self.i.jpeg_subsampling
                },
                // TODO: introduce support for 24-bit png and self.i.bgcolor_srgb (matte)
                OutputFormat::Png  => s::EncoderPreset::Libpng {
                    depth: Some(if i.bgcolor_srgb.is_some() { s::PngBitDepth::Png24 } else { s::PngBitDepth::Png32 }),
                    zlib_compression: None,
                    matte: i.bgcolor_srgb.map(|sr| s::Color::Srgb(s::ColorSrgb::Hex(sr.to_rrggbbaa_string())))
                },
                OutputFormat::Webp if i.webp_lossless == Some(true) => s::EncoderPreset::WebPLossless,
                OutputFormat::Webp => s::EncoderPreset::WebPLossy {
                    quality: i.webp_quality.unwrap_or(90f64) as f32
                },
            };
            Some(s::Node::Encode { io_id: id, preset: encoder })
        }else{
            None
        }
    }
}

<commit_msg>Switch to the Mozjpeg encoder by default for RIAPI (means optimized huffman coding and smart chroma subsampling<commit_after>use imageflow_helpers::preludes::from_std::*;
use imageflow_types as s;

pub mod parsing;
mod layout;

use crate::sizing;
use crate::sizing::prelude::*;
use crate::ir4::parsing::*;
use crate::ir4::layout::*;

pub use layout::ConstraintResults;

pub fn process_constraint(source_w: i32, source_h: i32, constraint: &imageflow_types::Constraint) -> sizing::Result<ConstraintResults>{
    layout::Ir4Layout::process_constraint(source_w,source_h, constraint)
}

pub enum Ir4Command{
    Instructions(Box<Instructions>),
    Url(String),
    QueryString(String)
}

impl Ir4Command{
    pub fn parse(&self) -> sizing::Result<Ir4Result> {
        let (i, warn) = match *self {
            Ir4Command::Url(ref url) => parsing::parse_url(&::url::Url::from_str(url).expect("ImageResizer4 Url cannot be parsed into instructions: invalid URI")),
            Ir4Command::Instructions(ref i) => (**i, vec![]),
            Ir4Command::QueryString(ref s) => {
                let url = ::url::Url::from_str(&format!("https://fakeurl/img.jpg?{}", s)).expect("Must be a valid querystring, excluding ?");
                parsing::parse_url(&url)
            }
        };
        Ok(Ir4Result{
            parse_warnings: warn,
            parsed: i,
            steps: None,
            canvas: None
        })
    }

}

/// Minimal translation into framewise (delay as much as possible)
pub struct Ir4Translate{
    pub i: Ir4Command,
    pub decode_id: Option<i32>,
    pub encode_id: Option<i32>,
}

// If using trim.threshold, delayed expansion is required.

pub struct Ir4Result{
    pub parse_warnings: Vec<parsing::ParseWarning>,
    pub parsed: Instructions,
    pub steps: Option<Vec<s::Node>>,
    pub canvas: Option<AspectRatio>
}

impl Ir4Translate{

    pub fn get_decode_node(&self) -> Option<s::Node>{
        if let Some(id) = self.decode_id {
            Some(s::Node::Decode { io_id: id, commands: None })
        }else{
            None
        }
    }

    pub fn translate(&self) -> sizing::Result<Ir4Result> {
        let mut r = self.i.parse()?;
        let mut b = crate::ir4::layout::FramewiseBuilder::new();
        //Expand decoder early if trimming
        let delayed_id = if r.parsed.trim_whitespace_threshold.is_some() {
            if let Some(n) = self.get_decode_node() {
                b.add(n);
            }
            None
        } else {
            self.decode_id
        };
        // Add CropWhitespace
        if r.parsed.trim_whitespace_threshold.is_some(){
            b.add(s::Node::CropWhitespace {
                threshold: cmp::max(0,r.parsed.trim_whitespace_threshold.unwrap()) as u32,
                percent_padding: r.parsed.trim_whitespace_padding_percent.unwrap_or(0f64) as f32
            });
        }

        //delete whitespace from instructions
        let mut without_trimming: Instructions = r.parsed;
        without_trimming.trim_whitespace_padding_percent = None;
        without_trimming.trim_whitespace_threshold = None;

        b.add(s::Node::CommandString {
            kind: s::CommandStringKind::ImageResizer4,
            value: without_trimming.to_string(),
            decode: delayed_id,
            encode: self.encode_id
        });

        r.steps = Some(b.into_steps());
        Ok(r)
    }
}

pub struct Ir4SourceFrameInfo{
    pub w: i32,
    pub h: i32,
    pub fmt: s::PixelFormat,
    pub original_mime: Option<String>,
}

impl Ir4SourceFrameInfo{

    fn get_format_from_mime(&self) -> Option<OutputFormat>{
        self.original_mime.as_ref().and_then(|f|
                    match f.as_str(){
                        "image/jpeg" => Some(OutputFormat::Jpeg),
                        "image/png" => Some(OutputFormat::Png),
                        "image/gif" => Some(OutputFormat::Gif),
                        "image/webp" => Some(OutputFormat::Webp),
                        _ => None
                    })
    }
    fn get_format_from_frame(&self) -> OutputFormat{
        match self.fmt{
            s::PixelFormat::Bgr24 | s::PixelFormat::Bgr32 => OutputFormat::Jpeg,
            _ => OutputFormat::Png
        }
    }
}

/// Cannot expand decoder. use `Ir4Translate` for that.
pub struct Ir4Expand{
    pub i: Ir4Command,
    pub source: Ir4SourceFrameInfo,
    pub encode_id: Option<i32>,

}

impl Ir4Expand{

    pub fn get_decode_commands(&self) -> sizing::Result<Option<Vec<s::DecoderCommand>>> { //TODO: consider smallvec or generalizing decoder hints
        let i = self.i.parse()?.parsed;

        // Default to gamma correct
        let gamma_correct = i.down_colorspace != Some(ScalingColorspace::Srgb);

        let layout = self.get_layout(&i)?;
        let (from, to): (AspectRatio, AspectRatio) = layout.get_downscaling()?;

        let downscale_ratio = (f64::from(from.w) / f64::from(to.w)).min(f64::from(from.h) / f64::from(to.w));

        let preshrink_ratio = i.min_precise_scaling_ratio.unwrap_or(2.1f64) / downscale_ratio;

        let scaled_width = (f64::from(self.source.w) * preshrink_ratio).floor() as i64;
        let scaled_height = (f64::from(self.source.h) * preshrink_ratio).floor() as i64;
        if preshrink_ratio < 1f64 {
            let mut vec = Vec::with_capacity(2);
            vec.push(s::DecoderCommand::JpegDownscaleHints(s::JpegIDCTDownscaleHints {
                scale_luma_spatially: Some(gamma_correct),
                gamma_correct_for_srgb_during_spatial_luma_scaling: Some(gamma_correct),
                width: scaled_width,
                height: scaled_height
            }));
            if !gamma_correct{
                vec.push(s::DecoderCommand::WebPDecoderHints(s::WebPDecoderHints{
                    width: scaled_width as i32,
                    height: scaled_height as i32,
                }));
            }
            Ok(Some(vec))
        } else {
            Ok(None)
        }
    }

    pub fn get_canvas_size(&self) -> sizing::Result<AspectRatio>{
        let i = self.i.parse()?.parsed;
        let (_, layout) = self.get_layout(&i).unwrap().get_crop_and_layout().unwrap();
        Ok(layout.get_box(BoxTarget::CurrentCanvas))
    }

    pub fn get_layout(&self, i: &Instructions) -> sizing::Result<Ir4Layout> {
        if i.trim_whitespace_threshold.is_some() {
            return Err(sizing::LayoutError::ContentDependent);
        }
        Ok(layout::Ir4Layout::new(*i, self.source.w, self.source.h))
    }

    pub fn expand_steps(&self) -> sizing::Result<Ir4Result> {
        let mut r = self.i.parse()?;

        let layout = self.get_layout(&r.parsed)?;

        let mut b = FramewiseBuilder::new();
        r.canvas = Some(layout.add_steps(&mut b)?.canvas);

        if let Some(n) = self.get_encoder_node(&r.parsed) {
            b.add(n);
        }
        r.steps = Some(b.into_steps());
        Ok(r)

    }

    pub fn get_encoder_node(&self, i: &Instructions) -> Option<s::Node>{

        if let Some(id) = self.encode_id {

            let format = i.format.or_else(|| self.source.get_format_from_mime())
                .unwrap_or_else(|| self.source.get_format_from_frame());

            let encoder = match format {
                OutputFormat::Gif => s::EncoderPreset::Gif,
                OutputFormat::Jpeg => s::EncoderPreset::Mozjpeg {
                    quality: Some(i.quality.unwrap_or(90) as u8),
                    progressive: i.jpeg_progressive
                },
                // TODO: introduce support for 24-bit png and self.i.bgcolor_srgb (matte)
                OutputFormat::Png  => s::EncoderPreset::Libpng {
                    depth: Some(if i.bgcolor_srgb.is_some() { s::PngBitDepth::Png24 } else { s::PngBitDepth::Png32 }),
                    zlib_compression: None,
                    matte: i.bgcolor_srgb.map(|sr| s::Color::Srgb(s::ColorSrgb::Hex(sr.to_rrggbbaa_string())))
                },
                OutputFormat::Webp if i.webp_lossless == Some(true) => s::EncoderPreset::WebPLossless,
                OutputFormat::Webp => s::EncoderPreset::WebPLossy {
                    quality: i.webp_quality.unwrap_or(90f64) as f32
                },
            };
            Some(s::Node::Encode { io_id: id, preset: encoder })
        }else{
            None
        }
    }
}

<|endoftext|>


<commit_before>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// Test sized-ness checking in substitution.

// Unbounded.
fn f1<Sized? X>(x: &X) {
    f2::<X>(x); //~ ERROR instantiating a type parameter with an incompatible type `X`, which does n
}
fn f2<X>(x: &X) {
}

// Bounded.
trait T for Sized? {}
fn f3<Sized? X: T>(x: &X) {
    f4::<X>(x); //~ ERROR instantiating a type parameter with an incompatible type `X`, which does n
}
fn f4<X: T>(x: &X) {
}

// Test with unsized enum.
enum E<Sized? X> {
    V(X),
}

fn f5<Y>(x: &Y) {}
fn f6<Sized? X>(x: &X) {}
fn f7<Sized? X>(x1: &E<X>, x2: &E<X>) {
    f5(x1); //~ERROR instantiating a type parameter with an incompatible type `E<X>`, which does not
    f6(x2); // ok
}

// Test with unsized struct.
struct S<Sized? X> {
    x: X,
}

fn f8<Sized? X>(x1: &S<X>, x2: &S<X>) {
    f5(x1); //~ERROR instantiating a type parameter with an incompatible type `S<X>`, which does not
    f6(x2); // ok
}

// Test some tuples.
fn f9<Sized? X>(x1: Box<S<X>>, x2: Box<E<X>>) {
    f5(&(*x1, 34i)); //~ERROR instantiating a type parameter with an incompatible type `(S<X>,int)`,
    f5(&(32i, *x2)); //~ERROR instantiating a type parameter with an incompatible type `(int,E<X>)`,
}

// I would like these to fail eventually.
/*
// impl - bounded
trait T1<Z: T> {
}
struct S3<Sized? Y>;
impl<Sized? X: T> T1<X> for S3<X> { //ERROR instantiating a type parameter with an incompatible type
}

// impl - unbounded
trait T2<Z> {
}
impl<Sized? X> T2<X> for S3<X> { //ERROR instantiating a type parameter with an incompatible type `X

// impl - struct
trait T3<Sized? Z> {
}
struct S4<Y>;
impl<Sized? X> T3<X> for S4<X> { //ERROR instantiating a type parameter with an incompatible type `X
}
*/

pub fn main() {
}
<commit_msg>Enable a test for correct treatment of Sized?<commit_after>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// Test sized-ness checking in substitution.

// Unbounded.
fn f1<Sized? X>(x: &X) {
    f2::<X>(x); //~ ERROR instantiating a type parameter with an incompatible type `X`, which does n
}
fn f2<X>(x: &X) {
}

// Bounded.
trait T for Sized? {}
fn f3<Sized? X: T>(x: &X) {
    f4::<X>(x); //~ ERROR instantiating a type parameter with an incompatible type `X`, which does n
}
fn f4<X: T>(x: &X) {
}

// Test with unsized enum.
enum E<Sized? X> {
    V(X),
}

fn f5<Y>(x: &Y) {}
fn f6<Sized? X>(x: &X) {}
fn f7<Sized? X>(x1: &E<X>, x2: &E<X>) {
    f5(x1); //~ERROR instantiating a type parameter with an incompatible type `E<X>`, which does not
    f6(x2); // ok
}

// Test with unsized struct.
struct S<Sized? X> {
    x: X,
}

fn f8<Sized? X>(x1: &S<X>, x2: &S<X>) {
    f5(x1); //~ERROR instantiating a type parameter with an incompatible type `S<X>`, which does not
    f6(x2); // ok
}

// Test some tuples.
fn f9<Sized? X>(x1: Box<S<X>>, x2: Box<E<X>>) {
    f5(&(*x1, 34i)); //~ERROR instantiating a type parameter with an incompatible type `(S<X>,int)`,
    f5(&(32i, *x2)); //~ERROR instantiating a type parameter with an incompatible type `(int,E<X>)`,
}

// I would like these to fail eventually.
/*
// impl - bounded
trait T1<Z: T> {
}
struct S3<Sized? Y>;
impl<Sized? X: T> T1<X> for S3<X> { //ERROR instantiating a type parameter with an incompatible type
}

// impl - unbounded
trait T2<Z> {
}
impl<Sized? X> T2<X> for S3<X> { //ERROR instantiating a type parameter with an incompatible type `X
*/

// impl - struct
trait T3<Sized? Z> {
}
struct S4<Y>;
impl<Sized? X> T3<X> for S4<X> { //~ ERROR instantiating a type parameter with an incompatible type
}

pub fn main() {
}
<|endoftext|>


<commit_before>use amq_protocol_types::*;
use format::field::*;
use format::frame::*;
use connection::*;
use generated::*;
use error::*;

#[derive(Clone,Debug,PartialEq,Eq)]
pub enum ChannelState {
  Initial,
  Connected,
  Closed,
  Error,
  SendingContent(usize),
  ReceivingContent(usize),
  {{#each specs.classes as |class| ~}}
  {{#unless class.is_connection}}
  {{#each class.methods as |method| ~}}{{#if method.synchronous }}Awaiting{{camel class.name}}{{camel method.name}}Ok,
  {{/if}}{{/each ~}}
  {{/unless}}
  {{/each ~}}
}

impl Connection {
  pub fn receive_method(&mut self, channel_id: u16, method: Class) -> Result<(), Error> {
    match method {
      {{#each specs.classes as |class| ~}}
      {{#unless class.is_connection}}
      {{#each class.methods as |method| ~}}
      Class::{{camel class.name}}({{snake class.name}}::Methods::{{camel method.name}}(m)) => {
        self.receive_{{snake class.name}}_{{snake method.name}}(channel_id, m)
      },
      {{/each ~}}
      {{/unless}}
      {{/each ~}}
    }
  }

  {{#each specs.classes as |class| ~}}
  {{#unless class.is_connection}}
  {{#each class.methods as |method| ~}}
  pub fn {{snake class.name}}_{{snake method.name}}(&mut self,
    _channel_id: u16{{#each method.arguments as |argument| ~}},
    {{snake argument.name}}: {{argument.type}}{{/each ~}}) -> Result<(), Error> {

      if !self.channels.contains_key(&_channel_id) {
        return Err(Error::InvalidChannel);
      }

      if ! self.channels.get_mut(&_channel_id).map(|c| c.state == ChannelState::Connected).unwrap_or(false) {
        return Err(Error::InvalidState);
      }

      let method = Class::{{camel class.name}}({{snake class.name}}::Methods::{{camel method.name}} (
        {{snake class.name}}::{{camel method.name}} {
          {{#each method.arguments as |argument| ~}}
          {{snake argument.name}}: {{snake argument.name}},
          {{/each ~}}
        }
      ));

      {{#if method.synchronous }}
      self.send_method_frame(_channel_id, &method).map(|_| {
        self.channels.get_mut(&_channel_id).map(|c| {
          c.state = ChannelState::Awaiting{{camel class.name}}{{camel method.name}}Ok;
          println!("channel {} state is now {:?}", _channel_id, c.state);
        });
      })
      {{else}}
      self.send_method_frame(_channel_id, &method)
      {{/if}}
  }

  pub fn receive_{{snake class.name}}_{{snake method.name}}(&mut self,
    _channel_id: u16, method: {{snake class.name}}::{{camel method.name}}) -> Result<(), Error> {

      if !self.channels.contains_key(&_channel_id) {
        println!("key {} not in channels {:?}", _channel_id, self.channels);
        return Err(Error::InvalidChannel);
      }

      match self.channels.get_mut(&_channel_id).map(|c| c.state.clone()).unwrap() {
        ChannelState::Initial | ChannelState::Connected => {},
        ChannelState::Error | ChannelState::Closed | ChannelState::SendingContent(_) | ChannelState::ReceivingContent(_) => {return Err(Error::InvalidState);},
        ChannelState::Awaiting{{camel class.name}}{{camel method.name}} => {
          self.channels.get_mut(&_channel_id).map(|c| c.state = ChannelState::Connected);
        }
        _ => {
          self.channels.get_mut(&_channel_id).map(|c| c.state = ChannelState::Error);
          return Err(Error::InvalidState);
        }
      }

      println!("unimplemented method {{camel class.name}}.{{camel method.name}}, ignoring packet");

      Ok(())
  }
  {{/each ~}}
  {{/unless}}
{{/each ~}}
}

<commit_msg>clean up api template<commit_after>use amq_protocol_types::*;
use format::field::*;
use format::frame::*;
use connection::*;
use generated::*;
use error::*;

#[derive(Clone,Debug,PartialEq,Eq)]
pub enum ChannelState {
  Initial,
  Connected,
  Closed,
  Error,
  SendingContent(usize),
  ReceivingContent(usize),
  {{#each specs.classes as |class| ~}}
  {{#unless class.is_connection}}
  {{#each class.methods as |method| ~}}{{#if method.synchronous }}Awaiting{{camel class.name}}{{camel method.name}}Ok,
  {{/if}}{{/each ~}}
  {{/unless}}
  {{/each ~}}
}

impl Connection {
  pub fn receive_method(&mut self, channel_id: u16, method: Class) -> Result<(), Error> {
    match method {
      {{#each specs.classes as |class| ~}}
      {{#unless class.is_connection}}
      {{#each class.methods as |method| ~}}
      Class::{{camel class.name}}({{snake class.name}}::Methods::{{camel method.name}}(m)) => {
        self.receive_{{snake class.name}}_{{snake method.name}}(channel_id, m)
      },
      {{/each ~}}
      {{/unless}}
      {{/each ~}}
    }
  }

  {{#each specs.classes as |class| ~}}
  {{#unless class.is_connection}}
  {{#each class.methods as |method| ~}}
  pub fn {{snake class.name}}_{{snake method.name}}(&mut self,
    _channel_id: u16{{#each method.arguments as |argument| ~}},
    {{snake argument.name}}: {{argument.type}}{{/each ~}}) -> Result<(), Error> {

      if !self.channels.contains_key(&_channel_id) {
        return Err(Error::InvalidChannel);
      }

      if ! self.channels.get_mut(&_channel_id).map(|c| c.state == ChannelState::Connected).unwrap_or(false) {
        return Err(Error::InvalidState);
      }

      let method = Class::{{camel class.name}}({{snake class.name}}::Methods::{{camel method.name}} (
        {{snake class.name}}::{{camel method.name}} {
          {{#each method.arguments as |argument| ~}}
          {{snake argument.name}}: {{snake argument.name}},
          {{/each ~}}
        }
      ));
      {{#if method.synchronous }}
      self.send_method_frame(_channel_id, &method).map(|_| {
        self.channels.get_mut(&_channel_id).map(|c| {
          c.state = ChannelState::Awaiting{{camel class.name}}{{camel method.name}}Ok;
          println!("channel {} state is now {:?}", _channel_id, c.state);
        });
      }){{else}}self.send_method_frame(_channel_id, &method){{/if}}
  }

  pub fn receive_{{snake class.name}}_{{snake method.name}}(&mut self,
    _channel_id: u16, method: {{snake class.name}}::{{camel method.name}}) -> Result<(), Error> {

      if !self.channels.contains_key(&_channel_id) {
        println!("key {} not in channels {:?}", _channel_id, self.channels);
        return Err(Error::InvalidChannel);
      }

      match self.channels.get_mut(&_channel_id).map(|c| c.state.clone()).unwrap() {
        ChannelState::Initial | ChannelState::Connected => {},
        ChannelState::Error | ChannelState::Closed | ChannelState::SendingContent(_) | ChannelState::ReceivingContent(_) => {return Err(Error::InvalidState);},
        ChannelState::Awaiting{{camel class.name}}{{camel method.name}} => {
          self.channels.get_mut(&_channel_id).map(|c| c.state = ChannelState::Connected);
        }
        _ => {
          self.channels.get_mut(&_channel_id).map(|c| c.state = ChannelState::Error);
          return Err(Error::InvalidState);
        }
      }

      println!("unimplemented method {{camel class.name}}.{{camel method.name}}, ignoring packet");

      Ok(())
  }

  {{/each ~}}
  {{/unless}}
{{/each ~}}
}

<|endoftext|>


<commit_before>use std::string::String;
use ropey::Rope;
use std::fs::File;
use std::cmp;
use std::io::{Read, Write, Result, Error, ErrorKind};
use std::path::Path;
use super::{Movement, Editable, Named, Saveable, CharIter};

#[derive(Debug)]
pub struct Text {
    pub pos: usize,
    pub text: Rope,
    pub name: String,
}

impl Text {
    pub fn empty() -> Text {
        Text {
            pos: 0,
            text: Rope::from_str("\n"),
            name: String::new(),
        }
    }
    pub fn open_file(filename: String) -> Result<Text> {
        if Path::new(&filename).exists() {
            let mut file = try!(File::open(&filename));

            let mut buf = String::new();
            try!(file.read_to_string(&mut buf));
            let text = Rope::from_string(buf);

            Ok(Text {
                pos: 0,
                text: text,
                name: filename,
            })
        } else {
            Ok(Text {
                pos: 0,
                text: Rope::new(),
                name: filename,
            })
        }
    }
}

impl Saveable for Text {
    fn save(&self) -> Result<()> {
        if self.name.is_empty() {
            return Err(Error::new(ErrorKind::InvalidInput, "Can't write file with no name"));
        }
        let mut file = try!(File::create(&self.name));
        for c in self.text.char_iter() {
            try!(write!(file, "{}", c));
        }
        try!(file.sync_all());
        Ok(())
    }
}

impl Named for Text {
    fn name(&self) -> &String {
        &self.name
    }
    fn set_name(&mut self, name: String) {
        self.name = name;
    }
}

impl Editable for Text {
    fn step(&mut self, mov: Movement) {
        match mov {
            Movement::Up => {
                if self.line() > 0 {
                    let prev_line = self.text.line_index_to_char_index(self.line() - 1);
                    let prev_line_size =
                        self.text.line_iter().nth(self.line() - 1).unwrap().char_count();
                    self.pos = prev_line + cmp::min(self.col(), prev_line_size - 1);
                }
            }
            Movement::Down => {
                if self.line() < self.line_count() - 1 {
                    let next_line = self.text.line_index_to_char_index(self.line() + 1);
                    let next_line_size =
                        self.text.line_iter().nth(self.line() + 1).unwrap().char_count();
                    self.pos = next_line + cmp::min(self.col(), next_line_size - 1);
                }
            }
            Movement::PageUp(up) => {
                let target_line = if self.line() < up {
                    0
                } else {
                    self.line() - up
                };
                self.pos = self.text.line_index_to_char_index(target_line);
            }
            Movement::PageDown(down) => {
                let target_line = if self.line_count() - self.line() < down {
                    self.line_count() - 1
                } else {
                    self.line() + down
                };
                self.pos = self.text.line_index_to_char_index(target_line);
            }
            Movement::Left => {
                if self.pos > 0 {
                    self.pos -= 1;
                }
            }
            Movement::Right => {
                if self.pos < self.text.char_count() - 1 {
                    self.pos += 1;
                }
            }
            Movement::LineStart => {
                let curr_line = self.text.line_index_to_char_index(self.line());

                self.pos = curr_line;
            }
            Movement::LineEnd => {
                let curr_line = self.text.line_index_to_char_index(self.line());
                let curr_line_size = self.text.line_iter().nth(self.line()).unwrap().char_count();
                self.pos = curr_line + curr_line_size - 1;
            }
        }
    }

    fn insert(&mut self, c: char) {
        self.text.insert_text_at_char_index(&format!("{}", c), self.pos);
        self.pos += 1;
    }

    fn delete(&mut self) -> Option<char> {
        if self.pos == 0 {
            None
        } else {
            self.pos -= 1;
            let ch = self.text.char_at_index(self.pos);
            self.text.remove_text_between_char_indices(self.pos, self.pos + 1);
            Some(ch)
        }
    }

    fn move_to(&mut self, pos: usize) {
        assert!(pos < self.text.char_count());
        self.pos = pos;
    }

    fn move_at(&mut self, line: usize, col: usize) {
        let line = cmp::min(line, self.line_count() - 1);
        let col = cmp::min(col,
                           self.text.line_iter().nth(line).unwrap().char_count() - 1);
        self.pos = self.text.line_index_to_char_index(line) + col;
    }

    fn pos(&self) -> usize {
        self.pos
    }

    fn line(&self) -> usize {
        self.text.char_index_to_line_index(self.pos)
    }

    fn col(&self) -> usize {
        self.pos - self.text.line_index_to_char_index(self.line())
    }

    fn line_count(&self) -> usize {
        self.text.line_ending_count()
    }

    fn len(&self) -> usize {
        self.text.char_count()
    }

    fn iter(&self) -> CharIter {
        self.text.char_iter()
    }
}
<commit_msg>fixed bug where opening a nonexistent file would crash on startup<commit_after>use std::string::String;
use ropey::Rope;
use std::fs::File;
use std::cmp;
use std::io::{Read, Write, Result, Error, ErrorKind};
use std::path::Path;
use super::{Movement, Editable, Named, Saveable, CharIter};

#[derive(Debug)]
pub struct Text {
    pub pos: usize,
    pub text: Rope,
    pub name: String,
}

impl Text {
    pub fn empty() -> Text {
        Text {
            pos: 0,
            text: Rope::from_str("\n"),
            name: String::new(),
        }
    }
    pub fn open_file(filename: String) -> Result<Text> {
        if Path::new(&filename).exists() {
            let mut file = try!(File::open(&filename));

            let mut buf = String::new();
            try!(file.read_to_string(&mut buf));
            if buf.is_empty() {
                buf = "\n".to_owned();
            }
            let text = Rope::from_string(buf);

            Ok(Text {
                pos: 0,
                text: text,
                name: filename,
            })
        } else {
            let mut text = Text::empty();
            text.set_name(filename);
            Ok(text)
        }
    }
}

impl Saveable for Text {
    fn save(&self) -> Result<()> {
        if self.name.is_empty() {
            return Err(Error::new(ErrorKind::InvalidInput, "Can't write file with no name"));
        }
        let mut file = try!(File::create(&self.name));
        for c in self.text.char_iter() {
            try!(write!(file, "{}", c));
        }
        try!(file.sync_all());
        Ok(())
    }
}

impl Named for Text {
    fn name(&self) -> &String {
        &self.name
    }
    fn set_name(&mut self, name: String) {
        self.name = name;
    }
}

impl Editable for Text {
    fn step(&mut self, mov: Movement) {
        match mov {
            Movement::Up => {
                if self.line() > 0 {
                    let prev_line = self.text.line_index_to_char_index(self.line() - 1);
                    let prev_line_size =
                        self.text.line_iter().nth(self.line() - 1).unwrap().char_count();
                    self.pos = prev_line + cmp::min(self.col(), prev_line_size - 1);
                }
            }
            Movement::Down => {
                if self.line() < self.line_count() - 1 {
                    let next_line = self.text.line_index_to_char_index(self.line() + 1);
                    let next_line_size =
                        self.text.line_iter().nth(self.line() + 1).unwrap().char_count();
                    self.pos = next_line + cmp::min(self.col(), next_line_size - 1);
                }
            }
            Movement::PageUp(up) => {
                let target_line = if self.line() < up {
                    0
                } else {
                    self.line() - up
                };
                self.pos = self.text.line_index_to_char_index(target_line);
            }
            Movement::PageDown(down) => {
                let target_line = if self.line_count() - self.line() < down {
                    self.line_count() - 1
                } else {
                    self.line() + down
                };
                self.pos = self.text.line_index_to_char_index(target_line);
            }
            Movement::Left => {
                if self.pos > 0 {
                    self.pos -= 1;
                }
            }
            Movement::Right => {
                if self.pos < self.text.char_count() - 1 {
                    self.pos += 1;
                }
            }
            Movement::LineStart => {
                let curr_line = self.text.line_index_to_char_index(self.line());

                self.pos = curr_line;
            }
            Movement::LineEnd => {
                let curr_line = self.text.line_index_to_char_index(self.line());
                let curr_line_size = self.text.line_iter().nth(self.line()).unwrap().char_count();
                self.pos = curr_line + curr_line_size - 1;
            }
        }
    }

    fn insert(&mut self, c: char) {
        self.text.insert_text_at_char_index(&format!("{}", c), self.pos);
        self.pos += 1;
    }

    fn delete(&mut self) -> Option<char> {
        if self.pos == 0 {
            None
        } else {
            self.pos -= 1;
            let ch = self.text.char_at_index(self.pos);
            self.text.remove_text_between_char_indices(self.pos, self.pos + 1);
            Some(ch)
        }
    }

    fn move_to(&mut self, pos: usize) {
        assert!(pos < self.text.char_count());
        self.pos = pos;
    }

    fn move_at(&mut self, line: usize, col: usize) {
        let line = cmp::min(line, self.line_count() - 1);
        let col = cmp::min(col,
                           self.text.line_iter().nth(line).unwrap().char_count() - 1);
        self.pos = self.text.line_index_to_char_index(line) + col;
    }

    fn pos(&self) -> usize {
        self.pos
    }

    fn line(&self) -> usize {
        self.text.char_index_to_line_index(self.pos)
    }

    fn col(&self) -> usize {
        self.pos - self.text.line_index_to_char_index(self.line())
    }

    fn line_count(&self) -> usize {
        self.text.line_ending_count()
    }

    fn len(&self) -> usize {
        self.text.char_count()
    }

    fn iter(&self) -> CharIter {
        self.text.char_iter()
    }
}
<|endoftext|>


<commit_before>//! Setup and run the all the tasks that compose the program
//!
//! The program runs over four main tasks
//!
//! * `data_input::task`: Handles input lines from `STDIN`
//! * `person_input::task`: Handles the person's interactions with the program
//! * `engine::task`: The search engine, it performs the actual fuzzy search
//! * `screen::task`: How to print the program's interface
//!
//! All tasks are futures that communicate between them sending events through channels
//! as you can see in the following diagram:
//!
//! ```text
//! +--------------+                    +--------+
//! | person_input +---------+--------->+ screen |
//! +------+-------+         ^          +--------+
//!        |                 |
//!        v             +---+----+
//!        +------------>+ engine |
//!        ^             +--------+
//!        |
//! +------+-----+
//! | data_input |
//! +------------+
//! ```
//!
//! The input from the person using the program is delivered to both the engine and the screen in
//! two different channels. There are some interactions (like moving through the list) that are
//! only relevant to the screen. Others, like new queries, are relevant for both. Using these two
//! channels also makes the screen more responsive to interactions since it doesn't have to wait
//! for the engine to finish searching in order to update the prompt, for example.

use crate::common::{Result, Text};
use crate::config::Config;
use crate::data_input;
use crate::engine;
use crate::events::Event;
use crate::person_input;
use crate::screen;
use async_std::io;
use async_std::sync::{self, Receiver, Sender};
use async_std::task;

const CHANNEL_SIZE: usize = 1024;

/// Run the program's tasks.
pub async fn run<R, I, W>(config: Config, stdin: R, inbox: I, outbox: W) -> Result<Option<Text>>
where
    R: io::Read + Send + Unpin + 'static,
    I: io::Read + Send + Unpin + 'static,
    W: io::Write + Send + Unpin + 'static,
{
    // channels
    let (input_sender, input_recv) = channel();
    let (output_sender, output_recv) = channel();

    let data_task = task::spawn(data_input::task(stdin, input_sender.clone()));
    let person_task = task::spawn(person_input::task(
        config.clone(),
        inbox,
        input_sender,
        output_sender.clone(),
    ));
    let engine_task = task::spawn(engine::task(input_recv, output_sender));
    let screen_task = task::spawn(screen::task(config, outbox, output_recv));

    let selection = screen_task.await;

    // Stop all remaining tasks
    drop(data_task);
    drop(person_task);
    drop(engine_task);

    selection
}

fn channel() -> (Sender<Event>, Receiver<Event>) {
    sync::channel::<Event>(CHANNEL_SIZE)
}
<commit_msg>Rearange order of initialization of tasks<commit_after>//! Setup and run the all the tasks that compose the program
//!
//! The program runs over four main tasks
//!
//! * `data_input::task`: Handles input lines from `STDIN`
//! * `person_input::task`: Handles the person's interactions with the program
//! * `engine::task`: The search engine, it performs the actual fuzzy search
//! * `screen::task`: How to print the program's interface
//!
//! All tasks are futures that communicate between them sending events through channels
//! as you can see in the following diagram:
//!
//! ```text
//! +--------------+                    +--------+
//! | person_input +---------+--------->+ screen |
//! +------+-------+         ^          +--------+
//!        |                 |
//!        v             +---+----+
//!        +------------>+ engine |
//!        ^             +--------+
//!        |
//! +------+-----+
//! | data_input |
//! +------------+
//! ```
//!
//! The input from the person using the program is delivered to both the engine and the screen in
//! two different channels. There are some interactions (like moving through the list) that are
//! only relevant to the screen. Others, like new queries, are relevant for both. Using these two
//! channels also makes the screen more responsive to interactions since it doesn't have to wait
//! for the engine to finish searching in order to update the prompt, for example.

use crate::common::{Result, Text};
use crate::config::Config;
use crate::data_input;
use crate::engine;
use crate::events::Event;
use crate::person_input;
use crate::screen;
use async_std::io;
use async_std::sync::{self, Receiver, Sender};
use async_std::task;

const CHANNEL_SIZE: usize = 1024;

/// Run the program's tasks.
pub async fn run<R, I, W>(config: Config, stdin: R, inbox: I, outbox: W) -> Result<Option<Text>>
where
    R: io::Read + Send + Unpin + 'static,
    I: io::Read + Send + Unpin + 'static,
    W: io::Write + Send + Unpin + 'static,
{
    // channels
    let (input_sender, input_recv) = channel();
    let (output_sender, output_recv) = channel();

    let screen_task = task::spawn(screen::task(config.clone(), outbox, output_recv));
    let person_task = task::spawn(person_input::task(
        config,
        inbox,
        input_sender.clone(),
        output_sender.clone(),
    ));
    let engine_task = task::spawn(engine::task(input_recv, output_sender));
    let data_task = task::spawn(data_input::task(stdin, input_sender));

    let selection = screen_task.await;

    // Stop all remaining tasks
    drop(data_task);
    drop(person_task);
    drop(engine_task);

    selection
}

fn channel() -> (Sender<Event>, Receiver<Event>) {
    sync::channel::<Event>(CHANNEL_SIZE)
}
<|endoftext|>