<commit_before>use super::*;
use crate::ram::cell::*;
use crate::ram::chunk::Chunks;
use crate::ram::entry::{EntryContent, EntryType};
use crate::ram::schema::Field;
use crate::ram::schema::*;
use crate::ram::types::*;
use crate::server::ServerMeta;
use env_logger;
use lightning::map::Map;
use std;
use std::collections::HashSet;
use std::sync::Arc;

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

fn default_cell(id: &Id) -> OwnedCell {
    let data: Vec<_> = std::iter::repeat(id.lower as u8).take(DATA_SIZE).collect();
    OwnedCell {
        header: CellHeader::new(0, 0, id),
        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, vec![]),
            Field::new("data", type_id_of(Type::U8), false, true, None, vec![]),
        ]),
        vec![],
    )
}

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

    // provision test data
    {
        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.cell_index.len(), 16);

        println!("trying to delete cells");

        assert_eq!(chunk.seg_count(), 2);
        assert_eq!(chunk.cell_count(), 16);

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

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

        //count entries, including dead ones
        assert_eq!(chunk.segs.get(&0).unwrap().entry_iter().count(), 8); // all 8 cells
        assert_eq!(chunk.segs.get(&1).unwrap().entry_iter().count(), 16); // 8 cells and 8 tombstones

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

        println!("Scanning second segment for tombstones...");
        let seg = &chunk.segs.get(&1).unwrap();
        let live_entries = chunk.live_entries(seg);
        let tombstones: Vec<_> = live_entries
            .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_count(), 8);
    }

    // check integrity
    let _ = chunk
        .live_entries(&chunk.segs.get(&0).unwrap())
        .collect::<Vec<_>>();
    let _ = chunk
        .live_entries(&chunk.segs.get(&1).unwrap())
        .collect::<Vec<_>>();

    // compact
    {
        // Compact all segments order by id
        chunk.segments().into_iter().for_each(|seg| {
            compact::CompactCleaner::clean_segment(chunk, &seg);
        });

        assert_eq!(chunk.seg_count(), 2);
        assert_eq!(chunk.cell_count(), 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).collect::<Vec<_>>();
        let compacted_segment_1_entries = chunk.live_entries(seg1).collect::<Vec<_>>();
        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);
        assert_eq!(seg0.entry_iter().count(), 4);

        // check for cells and 4 tombstones
        compacted_segment_1_entries
            .iter()
            .zip(compacted_segment_1_ids)
            .for_each(|(entry, hash)| {
                if hash > 1 {
                    // cell
                    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 {
                    // tombstone
                    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);
                }
            });
        // 4 remaining cells and 8 deleted cell tombstones
        assert_eq!(compacted_segment_1_entries.len(), 12);
        assert_eq!(seg1.entry_iter().count(), 12);
    }

    // combine
    {
        combine::CombinedCleaner::combine_segments(chunk, &chunk.segments());
        let survival_cells: HashSet<_> = chunk
            .live_entries(&chunk.segments()[0])
            .map(|entry| {
                assert_eq!(entry.meta.entry_header.entry_type, EntryType::CELL);
                if let EntryContent::Cell(ref header) = entry.content {
                    return header.hash;
                } else {
                    panic!()
                }
            })
            .collect();
        (0..8)
            .map(|n| n as u64 * 2 + 1)
            .for_each(|hash| assert!(survival_cells.contains(&hash)));
        assert_eq!(chunk.segments().len(), 1);
        assert_eq!(survival_cells.len(), 8);
        assert_eq!(chunk.segments()[0].entry_iter().count(), 8);
    }

    // validate cells
    (0..8).map(|n| n * 2 + 1).for_each(|id| {
        let id = Id::new(0, id);
        let cell = chunks.read_cell(&id).unwrap();
        assert_eq!(cell.to_owned().data, default_cell(&id).data);
    });
}
<commit_msg>fixing test<commit_after>use super::*;
use crate::ram::cell::*;
use crate::ram::chunk::Chunks;
use crate::ram::entry::{EntryContent, EntryType};
use crate::ram::schema::Field;
use crate::ram::schema::*;
use crate::ram::types::*;
use crate::server::ServerMeta;
use env_logger;
use lightning::map::Map;
use std;
use std::collections::HashSet;
use std::sync::Arc;

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

fn default_cell(id: &Id) -> OwnedCell {
    let data: Vec<_> = std::iter::repeat(id.lower as u8).take(DATA_SIZE).collect();
    OwnedCell {
        header: CellHeader::new(0, 0, id),
        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, vec![]),
            Field::new("data", type_id_of(Type::U8), false, true, None, vec![]),
        ]),
        vec![],
    )
}

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

    // provision test data
    {
        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.cell_index.len(), 16);

        println!("trying to delete cells");

        assert_eq!(chunk.seg_count(), 2);
        assert_eq!(chunk.cell_count(), 16);

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

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

        //count entries, including dead ones
        assert_eq!(chunk.segs.get(&0).unwrap().entry_iter().count(), 8); // all 8 cells
        assert_eq!(chunk.segs.get(&1).unwrap().entry_iter().count(), 16); // 8 cells and 8 tombstones

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

        println!("Scanning second segment for tombstones...");
        let seg = &chunk.segs.get(&1).unwrap();
        let live_entries = chunk.live_entries(seg);
        let tombstones: Vec<_> = live_entries
            .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_count(), 8);
    }

    // check integrity
    let _ = chunk
        .live_entries(&chunk.segs.get(&0).unwrap())
        .collect::<Vec<_>>();
    let _ = chunk
        .live_entries(&chunk.segs.get(&1).unwrap())
        .collect::<Vec<_>>();

    // compact
    {
        // Compact all segments order by id
        chunk.segments().into_iter().for_each(|seg| {
            compact::CompactCleaner::clean_segment(chunk, &seg);
        });

        assert_eq!(chunk.seg_count(), 1);
        assert_eq!(chunk.cell_count(), 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).collect::<Vec<_>>();
        let compacted_segment_1_entries = chunk.live_entries(seg1).collect::<Vec<_>>();
        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);
        assert_eq!(seg0.entry_iter().count(), 4);

        // check for cells and 4 tombstones
        compacted_segment_1_entries
            .iter()
            .zip(compacted_segment_1_ids)
            .for_each(|(entry, hash)| {
                if hash > 1 {
                    // cell
                    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 {
                    // tombstone
                    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);
                }
            });
        // 4 remaining cells and 8 deleted cell tombstones
        assert_eq!(compacted_segment_1_entries.len(), 12);
        assert_eq!(seg1.entry_iter().count(), 12);
    }

    // combine
    {
        combine::CombinedCleaner::combine_segments(chunk, &chunk.segments());
        let survival_cells: HashSet<_> = chunk
            .live_entries(&chunk.segments()[0])
            .map(|entry| {
                assert_eq!(entry.meta.entry_header.entry_type, EntryType::CELL);
                if let EntryContent::Cell(ref header) = entry.content {
                    return header.hash;
                } else {
                    panic!()
                }
            })
            .collect();
        (0..8)
            .map(|n| n as u64 * 2 + 1)
            .for_each(|hash| assert!(survival_cells.contains(&hash)));
        assert_eq!(chunk.segments().len(), 1);
        assert_eq!(survival_cells.len(), 8);
        assert_eq!(chunk.segments()[0].entry_iter().count(), 8);
    }

    // validate cells
    (0..8).map(|n| n * 2 + 1).for_each(|id| {
        let id = Id::new(0, id);
        let cell = chunks.read_cell(&id).unwrap();
        assert_eq!(cell.to_owned().data, default_cell(&id).data);
    });
}
<|endoftext|>


<commit_before>extern crate rustty;

use rustty::{
    Terminal,
    Event,
};

const BLOCK: char = '\u{25AA}';

fn main() {
    let mut term = Terminal::new().unwrap();
    let mut radius = 10isize;
    'main: loop {
        while let Some(Event::Key(ch)) = term.get_event(0).unwrap() {
            match ch {
                'q' => break 'main,
                '+' => radius += 1,
                '-' => radius -= 1,
                _ => {},
            }
        }

        let (cols, rows) = term.size();
        let (cols, rows) = (cols as isize, rows as isize);

        let (a, b) = (cols / 2, rows / 2);

        for (i, cell) in term.iter_mut().enumerate() {
            let y = i as isize / cols;
            let x = i as isize % cols;

            if ((x - a).pow(2) + (y - b).pow(2)) <= radius.pow(2) {
                cell.set_ch(BLOCK);
            } else {
                cell.set_ch(' ');
            }
        }
        term.swap_buffers().unwrap();
    }
}
<commit_msg>Circle examples draws better circle<commit_after>extern crate rustty;

use rustty::{
    Terminal,
    Event,
};

const BLOCK: char = '\u{25AA}';

fn main() {
    let mut term = Terminal::new().unwrap();
    let mut radius = 10u32;
    'main: loop {
        while let Some(Event::Key(ch)) = term.get_event(0).unwrap() {
            match ch {
                'q' => break 'main,
                '+' => radius = radius.saturating_add(1),
                '-' => radius = radius.saturating_sub(1),
                _ => {},
            }
        }

        let (cols, rows) = term.size();
        let (cols, rows) = (cols as isize, (rows - 3) as isize);

        let (a, b) = (cols / 2, rows / 2);

        for i in 0..cols*rows {
            let y = i as isize / cols;
            let x = i as isize % cols;

            let mut cell = &mut term[(x as usize, y as usize)];

            if ((x - a).pow(2)/4 + (y - b).pow(2)) <= radius.pow(2) as isize {
                cell.set_ch(BLOCK);
            } else {
                cell.set_ch(' ');
            }
        }
        term.swap_buffers().unwrap();
    }
}
<|endoftext|>


<commit_before>// Copyright 2014 Alexis Mousset. 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.

#![feature(core, old_io, rustc_private, os, collections)]
#[macro_use] extern crate log;

extern crate smtp;
extern crate getopts;

use std::old_io::stdin;
use std::old_io::net::ip::Port;
use std::string::String;
use std::os;
use getopts::{optopt, optflag, getopts, OptGroup, usage};

use smtp::client::Client;
use smtp::error::SmtpResult;
use smtp::mailer::Email;

fn sendmail(source_address: &str, recipient_addresses: &[&str], message: &str, subject: &str,
        server: &str, port: Port, my_hostname: &str) -> SmtpResult {

    let mut email = Email::new();
    for destination in recipient_addresses.iter() {
        email.to(*destination);
    }
    email.from(source_address);
    email.body(message);
    email.subject(subject);
    email.date_now();

    let mut client: Client = Client::new((server, port));
    client.set_hello_name(my_hostname);
    client.send(email)
}

fn print_usage(description: String, _opts: &[OptGroup]) {
    println!("{}", usage(description.as_slice(), _opts));
}

fn main() {
    let args = os::args();

    let mut args_string = Vec::new();
    for arg in args.iter() {
        args_string.push(arg.clone());
    };

    let program = args[0].clone();
    let description = format!("Usage: {0} [options...] recipients\n\n\
                               This program reads a message on standard input until it reaches\
                               EOF, then tries to send it using the given paramters.\n\n\
                               Example: {0} -r user@example.org user@example.com < message.txt",
                              program);

    let opts = [
        optopt("s", "subject", "set the email subject", "SUBJECT"),
        optopt("r", "reverse-path", "set the sender address", "FROM_ADDRESS"),
        optopt("p", "port", "set the port to use, default is 25", "PORT"),
        optopt("a", "server", "set the server to use, default is localhost", "SERVER"),
        optopt("m", "my-hostname", "set the hostname used by the client", "MY_HOSTNAME"),
        optflag("h", "help", "print this help menu"),
    ];

    let matches = match getopts(args_string.tail(), &opts) {
        Ok(m) => m,
        Err(f) => panic!("{}", f),
    };

    if matches.opt_present("h") {
        print_usage(description, &opts);
        return;
    }

    if !matches.opt_present("r") {
        println!("The sender option is required");
        print_usage(program, &opts);
        return;
    }

    let recipients_str: &str = if !matches.free.is_empty() {
        matches.free[0].as_slice()
    } else {
        print_usage(description, &opts);
        return;
    };

    let mut recipients = Vec::new();
    for recipient in recipients_str.split(' ') {
        recipients.push(recipient);
    }

    let mut message = String::new();

    let mut line = stdin().read_line();
    while line.is_ok() {
        message.push_str(line.unwrap().as_slice());
        line = stdin().read_line();
    }

    match sendmail(
        // sender
        matches.opt_str("r").unwrap().as_slice(),
        // recipients
        recipients.as_slice(),
        // message content
        message.as_slice(),
        // subject
        match matches.opt_str("s") {
            Some(ref subject) => subject.as_slice(),
            None => "(empty subject)"
        },
        // server
        match matches.opt_str("a") {
            Some(ref server) => server.as_slice(),
            None => "localhost"
        },
        // port
        match matches.opt_str("p") {
            Some(port) => port.as_slice().parse::<Port>().unwrap(),
            None => 25
        },
        // my hostname
        match matches.opt_str("m") {
            Some(ref my_hostname) => my_hostname.as_slice(),
            None => "localhost"
        },
    )
    {
        Ok(..) => info!("Email sent successfully"),
        Err(error) => error!("{}", error),
    }
}
<commit_msg>use std::env instead od std::os<commit_after>// Copyright 2014 Alexis Mousset. 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.

#![feature(core, old_io, rustc_private, env, collections)]
#[macro_use] extern crate log;

extern crate smtp;
extern crate getopts;

use std::old_io::stdin;
use std::old_io::net::ip::Port;
use std::string::String;
use std::env;
use getopts::{optopt, optflag, getopts, OptGroup, usage};

use smtp::client::Client;
use smtp::error::SmtpResult;
use smtp::mailer::Email;

fn sendmail(source_address: &str, recipient_addresses: &[&str], message: &str, subject: &str,
        server: &str, port: Port, my_hostname: &str) -> SmtpResult {

    let mut email = Email::new();
    for destination in recipient_addresses.iter() {
        email.to(*destination);
    }
    email.from(source_address);
    email.body(message);
    email.subject(subject);
    email.date_now();

    let mut client: Client = Client::new((server, port));
    client.set_hello_name(my_hostname);
    client.send(email)
}

fn print_usage(description: String, _opts: &[OptGroup]) {
    println!("{}", usage(description.as_slice(), _opts));
}

fn main() {
    let args = env::args();

    let mut args_string = Vec::new();
    for arg in args {
        args_string.push(arg.clone());
    };

    let program = args_string[0].clone();
    let description = format!("Usage: {0} [options...] recipients\n\n\
                               This program reads a message on standard input until it reaches\
                               EOF, then tries to send it using the given paramters.\n\n\
                               Example: {0} -r user@example.org user@example.com < message.txt",
                              program);

    let opts = [
        optopt("s", "subject", "set the email subject", "SUBJECT"),
        optopt("r", "reverse-path", "set the sender address", "FROM_ADDRESS"),
        optopt("p", "port", "set the port to use, default is 25", "PORT"),
        optopt("a", "server", "set the server to use, default is localhost", "SERVER"),
        optopt("m", "my-hostname", "set the hostname used by the client", "MY_HOSTNAME"),
        optflag("h", "help", "print this help menu"),
    ];

    let matches = match getopts(args_string.tail(), &opts) {
        Ok(m) => m,
        Err(f) => panic!("{}", f),
    };

    if matches.opt_present("h") {
        print_usage(description, &opts);
        return;
    }

    if !matches.opt_present("r") {
        println!("The sender option is required");
        print_usage(program, &opts);
        return;
    }

    let recipients_str: &str = if !matches.free.is_empty() {
        matches.free[0].as_slice()
    } else {
        print_usage(description, &opts);
        return;
    };

    let mut recipients = Vec::new();
    for recipient in recipients_str.split(' ') {
        recipients.push(recipient);
    }

    let mut message = String::new();

    let mut line = stdin().read_line();
    while line.is_ok() {
        message.push_str(line.unwrap().as_slice());
        line = stdin().read_line();
    }

    match sendmail(
        // sender
        matches.opt_str("r").unwrap().as_slice(),
        // recipients
        recipients.as_slice(),
        // message content
        message.as_slice(),
        // subject
        match matches.opt_str("s") {
            Some(ref subject) => subject.as_slice(),
            None => "(empty subject)"
        },
        // server
        match matches.opt_str("a") {
            Some(ref server) => server.as_slice(),
            None => "localhost"
        },
        // port
        match matches.opt_str("p") {
            Some(port) => port.as_slice().parse::<Port>().unwrap(),
            None => 25
        },
        // my hostname
        match matches.opt_str("m") {
            Some(ref my_hostname) => my_hostname.as_slice(),
            None => "localhost"
        },
    )
    {
        Ok(..) => info!("Email sent successfully"),
        Err(error) => error!("{}", error),
    }
}
<|endoftext|>


<commit_before>extern crate docopt;
extern crate flac;
extern crate hound;
extern crate rustc_serialize;

use docopt::Docopt;
use flac::StreamReader;

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

const USAGE: &'static str = "
Usage: decode <input> <output>
       decode <input>... <dir>
       decode --help

Options:
  -h, --help   Show this message.
";

#[derive(RustcDecodable)]
struct Arguments {
  arg_input: Vec<String>,
  arg_output: Option<String>,
  arg_dir: Option<String>,
}

fn decode_file(input_file: &str, output_file: &str)
               -> Result<(), hound::Error> {
  let mut stream = try! {
    StreamReader::<fs::File>::from_file(input_file)
      .map_err(hound::Error::IoError)
  };

  let info = stream.info();
  let spec = hound::WavSpec {
    channels: info.channels as u16,
    sample_rate: info.sample_rate,
    bits_per_sample: info.bits_per_sample as u16,
  };

  let mut output = try!(hound::WavWriter::create(output_file, spec));

  for sample in &mut stream {
    try!(output.write_sample(sample));
  }

  output.finalize()
}

fn to_output_file(buffer: &mut PathBuf, path: &Path, directory: &str)
                  -> Result<(), hound::Error> {
  buffer.push(directory);

  path.file_name().map(|name| {
    buffer.push(name);
    buffer.set_extension("wav");
  }).ok_or_else(|| {
    let kind    = io::ErrorKind::NotFound;
    let message = "no file name found";
    let error   = io::Error::new(kind, message);

    hound::Error::IoError(error)
  })
}

fn decode_all_files(input_files: &Vec<String>, directory: &str)
                    -> Result<(), hound::Error> {
  let dir_path = Path::new(directory);

  if !dir_path.exists() {
    try!(fs::create_dir(dir_path).map_err(hound::Error::IoError))
  }

  for ref input_file in input_files {
    let mut buffer = PathBuf::new();
    let path       = Path::new(input_file);

    try!(to_output_file(&mut buffer, path, directory));

    let output_file = try! {
      buffer.to_str().ok_or_else(|| {
        let kind    = io::ErrorKind::InvalidInput;
        let message = "invalid unicode with file path";
        let error   = io::Error::new(kind, message);

        hound::Error::IoError(error)
      })
    };

    let result = decode_file(input_file, output_file);

    if result.is_ok() {
      println!("decoded: {} -> {}", input_file, output_file);
    } else {
      return result;
    }
  }

  Ok(())
}

fn main() {
  let args: Arguments = Docopt::new(USAGE)
    .and_then(|d| d.argv(env::args()).decode())
    .unwrap_or_else(|e| e.exit());

  if let Some(ref output_file) = args.arg_output {
    let input_file = &args.arg_input[0];

    if let Err(e) = decode_file(input_file, output_file) {
      println!("{:?}", e);
    } else {
      println!("decoded: {} -> {}", input_file, output_file);
    }
  } else if let Some(ref directory) = args.arg_dir {
    if let Err(e) = decode_all_files(&args.arg_input, directory) {
      println!("{:?}", e);
    }
  }
}
<commit_msg>Update decoder for new API for Stream constructor<commit_after>extern crate docopt;
extern crate flac;
extern crate hound;
extern crate rustc_serialize;

use docopt::Docopt;
use flac::{ErrorKind, StreamReader};

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

const USAGE: &'static str = "
Usage: decode <input> <output>
       decode <input>... <dir>
       decode --help

Options:
  -h, --help   Show this message.
";

#[derive(RustcDecodable)]
struct Arguments {
  arg_input: Vec<String>,
  arg_output: Option<String>,
  arg_dir: Option<String>,
}

fn to_io_error(kind: ErrorKind) -> hound::Error {
  match kind {
    ErrorKind::IO(e) => {
      let io_error = io::Error::new(e, "io error");

      hound::Error::IoError(io_error)
    }
    _                => hound::Error::Unsupported,
  }
}

fn decode_file(input_file: &str, output_file: &str)
               -> Result<(), hound::Error> {
  let mut stream = try! {
    StreamReader::<fs::File>::from_file(input_file)
      .map_err(to_io_error)
  };

  let info = stream.info();
  let spec = hound::WavSpec {
    channels: info.channels as u16,
    sample_rate: info.sample_rate,
    bits_per_sample: info.bits_per_sample as u16,
  };

  let mut output = try!(hound::WavWriter::create(output_file, spec));

  for sample in &mut stream {
    try!(output.write_sample(sample));
  }

  output.finalize()
}

fn to_output_file(buffer: &mut PathBuf, path: &Path, directory: &str)
                  -> Result<(), hound::Error> {
  buffer.push(directory);

  path.file_name().map(|name| {
    buffer.push(name);
    buffer.set_extension("wav");
  }).ok_or_else(|| {
    let kind    = io::ErrorKind::NotFound;
    let message = "no file name found";
    let error   = io::Error::new(kind, message);

    hound::Error::IoError(error)
  })
}

fn decode_all_files(input_files: &Vec<String>, directory: &str)
                    -> Result<(), hound::Error> {
  let dir_path = Path::new(directory);

  if !dir_path.exists() {
    try!(fs::create_dir(dir_path).map_err(hound::Error::IoError))
  }

  for ref input_file in input_files {
    let mut buffer = PathBuf::new();
    let path       = Path::new(input_file);

    try!(to_output_file(&mut buffer, path, directory));

    let output_file = try! {
      buffer.to_str().ok_or_else(|| {
        let kind    = io::ErrorKind::InvalidInput;
        let message = "invalid unicode with file path";
        let error   = io::Error::new(kind, message);

        hound::Error::IoError(error)
      })
    };

    let result = decode_file(input_file, output_file);

    if result.is_ok() {
      println!("decoded: {} -> {}", input_file, output_file);
    } else {
      return result;
    }
  }

  Ok(())
}

fn main() {
  let args: Arguments = Docopt::new(USAGE)
    .and_then(|d| d.argv(env::args()).decode())
    .unwrap_or_else(|e| e.exit());

  if let Some(ref output_file) = args.arg_output {
    let input_file = &args.arg_input[0];

    if let Err(e) = decode_file(input_file, output_file) {
      println!("{:?}", e);
    } else {
      println!("decoded: {} -> {}", input_file, output_file);
    }
  } else if let Some(ref directory) = args.arg_dir {
    if let Err(e) = decode_all_files(&args.arg_input, directory) {
      println!("{:?}", e);
    }
  }
}
<|endoftext|>


<commit_before>// Copyright (C) 2016  ParadoxSpiral
//
// This file is part of libmpv-rs.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

use libmpv::{events::*, *};

use std::{env, thread, time::Duration};

fn main() {
    let path = env::args()
        .nth(1)
        .unwrap_or_else(|| String::from("https://www.youtube.com/watch?v=DLzxrzFCyOs"));

    // Create an `Mpv` and set some properties.
    let mpv = Mpv::new().unwrap();
    mpv.set_property("volume", 15).unwrap();
    mpv.set_property("vo", "null").unwrap();

    let mut ev_ctx = mpv.create_event_context();
    ev_ctx.disable_deprecated_events().unwrap();
    ev_ctx.observe_property("volume", Format::Int64, 0).unwrap();

    crossbeam::scope(|scope| {
        scope.spawn(|_| {
            mpv.playlist_load_files(&[(&path, FileState::AppendPlay, None)])
                .unwrap();

            thread::sleep(Duration::from_secs(3));

            mpv.set_property("volume", 25).unwrap();

            thread::sleep(Duration::from_secs(5));

            // Trigger `Event::EndFile`.
            mpv.playlist_next_force().unwrap();
        });
        scope.spawn(move |_| loop {
            let ev = ev_ctx.wait_event(600.);
            if let Some(Ok(Event::EndFile(r))) = ev {
                println!("Exiting! Reason: {:?}", r);
                break;
            } else if let Some(Ok(e)) = ev {
                println!("Event triggered: {:?}", e);
            } else if let Some(Err(e)) = ev {
                println!("Event errored: {:?}", e);
            }
        });
    })
    .unwrap();
}
<commit_msg>Update events example to show use of MpvNode.<commit_after>// Copyright (C) 2016  ParadoxSpiral
//
// This file is part of libmpv-rs.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

use libmpv::{events::*, *};

use std::{collections::HashMap, env, thread, time::Duration};

const VIDEO_URL: &str = "https://www.youtube.com/watch?v=DLzxrzFCyOs";

fn main() -> Result<()> {
    let path = env::args()
        .nth(1)
        .unwrap_or_else(|| String::from(VIDEO_URL));

    // Create an `Mpv` and set some properties.
    let mpv = Mpv::new()?;
    mpv.set_property("volume", 15)?;
    mpv.set_property("vo", "null")?;

    let mut ev_ctx = mpv.create_event_context();
    ev_ctx.disable_deprecated_events()?;
    ev_ctx.observe_property("volume", Format::Int64, 0)?;
    ev_ctx.observe_property("demuxer-cache-state", Format::Node, 0)?;

    crossbeam::scope(|scope| {
        scope.spawn(|_| {
            mpv.playlist_load_files(&[(&path, FileState::AppendPlay, None)])
                .unwrap();

            thread::sleep(Duration::from_secs(3));

            mpv.set_property("volume", 25).unwrap();

            thread::sleep(Duration::from_secs(5));

            // Trigger `Event::EndFile`.
            mpv.playlist_next_force().unwrap();
        });
        scope.spawn(move |_| loop {
            let ev = ev_ctx.wait_event(600.).unwrap_or(Err(Error::Null));

            match ev {
                Ok(Event::EndFile(r)) => {
                    println!("Exiting! Reason: {:?}", r);
                    break;
                }

                Ok(Event::PropertyChange {
                    name: "demuxer-cache-state",
                    change: PropertyData::Node(mpv_node),
                    ..
                }) => {
                    let ranges = seekable_ranges(mpv_node).unwrap();
                    println!("Seekable ranges updated: {:?}", ranges);
                }
                Ok(e) => println!("Event triggered: {:?}", e),
                Err(e) => println!("Event errored: {:?}", e),
            }
        });
    })
    .unwrap();
    Ok(())
}

fn seekable_ranges(demuxer_cache_state: &MpvNode) -> Option<Vec<(f64, f64)>> {
    let mut res = Vec::new();
    let props: HashMap<&str, MpvNode> = demuxer_cache_state.to_map()?.collect();
    let ranges = props.get("seekable-ranges")?.to_array()?;

    for node in ranges {
        let range: HashMap<&str, MpvNode> = node.to_map()?.collect();
        let start = range.get("start")?.to_f64()?;
        let end = range.get("end")?.to_f64()?;
        res.push((start, end));
    }

    Some(res)
}
<|endoftext|>


<commit_before>use common::debug::*;
use common::memory::*;
use common::string::*;

use drivers::disk::*;
use drivers::keyboard::*;
use drivers::mouse::*;

use filesystems::unfs::*;

use graphics::color::*;
use graphics::display::*;
use graphics::point::*;
use graphics::size::*;
use graphics::window::*;

use programs::program::*;

pub struct Editor {
    window: Window,
    filename: &'static str,
    string: String,
    offset: usize
}

impl Editor {
    pub unsafe fn new() -> Editor {
        Editor {
            window: Window{
                point: Point{ x:300, y:200 },
                size: Size { width:640, height:480 },
                title: "Press a function key to load a file",
                shaded: false,
                dragging: false,
                last_mouse_point: Point {
                    x: 0,
                    y: 0
                },
                last_mouse_event: MouseEvent {
                    x: 0,
                    y: 0,
                    left_button: false,
                    right_button: false,
                    middle_button: false,
                    valid: false
                }
            },
            filename: "",
            string: String::new(),
            offset: 0
        }
    }

    unsafe fn clear(&mut self){
        self.window.title = "Press a function key to load a file";
        self.filename = "";
        self.string = String::new();
        self.offset = 0;
    }

    unsafe fn load(&mut self, filename: &'static str){
        self.clear();
        let unfs = UnFS::new(Disk::new());
        let dest = unfs.load(filename);
        if dest > 0 {
            self.filename = filename;
            self.window.title = filename;
            self.string = String::from_c_str(dest as *const u8);
            self.offset = self.string.len();
            unalloc(dest);
        }else{
            d("Did not find '");
            d(filename);
            d("'\n");
        }
    }

    unsafe fn save(&self){
        let unfs = UnFS::new(Disk::new());
        let data = self.string.as_c_str() as usize;
        unfs.save(self.filename, data);
        unalloc(data);
        d("Saved\n");
    }
}

impl Program for Editor {
    unsafe fn draw(&self, display: &Display){
        self.window.draw(display);

		if ! self.window.shaded {
            let mut offset = 0;
            let mut row = 0;
            let mut col = 0;
            for c_ptr in self.string.as_slice() {
                if offset == self.offset && col < self.window.size.width / 8 && row < self.window.size.height / 16 {
                    display.char(Point::new(self.window.point.x + 8*col as i32, self.window.point.y + 16*row as i32), '_', Color::new(128, 128, 128));
                }

                let c = *c_ptr;
                if c == '\n' {
                    col = 0;
                    row += 1;
                }else if c == '\t' {
                    col += 8 - col % 8;
                }else{
                    if col < self.window.size.width / 8 && row < self.window.size.height / 16 {
                        let point = Point::new(self.window.point.x + 8*col as i32, self.window.point.y + 16*row as i32);
                        display.char(point, c, Color::new(255, 255, 255));
                        col += 1;
                    }
                }
                if col >= self.window.size.width / 8 {
                    col = 0;
                    row += 1;
                }

                offset += 1;
            }

            if offset == self.offset && col < self.window.size.width / 8 && row < self.window.size.height / 16 {
                display.char(Point::new(self.window.point.x + 8*col as i32, self.window.point.y + 16*row as i32), '_', Color::new(128, 128, 128));
            }
        }
    }

    unsafe fn on_key(&mut self, key_event: KeyEvent){
        if key_event.pressed {
            match key_event.scancode {
                0x3B => self.load("README.md"),
                0x3C => self.load("LICENSE.md"),
                0x40 => self.save(),
                0x47 => self.offset = 0,
                0x48 => for i in 1..self.offset {
                    match self.string.get(self.offset - i) {
                        '\0' => break,
                        '\n' => {
                            self.offset = self.offset - i;
                            break;
                        },
                        _ => ()
                    }
                },
                0x4B => if self.offset > 0 {
                            self.offset -= 1;
                        },
                0x4D => if self.offset < self.string.len() {
                            self.offset += 1;
                        },
                0x4F => self.offset = self.string.len(),
                0x50 => for i in self.offset + 1..self.string.len() {
                    match self.string.get(i) {
                        '\0' => break,
                        '\n' => {
                            self.offset = i;
                            break;
                        },
                        _ => ()
                    }
                },
                0x53 => if self.offset < self.string.len() {
                    self.string = self.string.substr(0, self.offset) + self.string.substr(self.offset + 1, self.string.len() - self.offset - 1);
                },
                _ => ()
            }

            match key_event.character {
                '\x00' => (),
                '\x08' => if self.offset > 0 {
                    self.string = self.string.substr(0, self.offset - 1) + self.string.substr(self.offset, self.string.len() - self.offset);
                    self.offset -= 1;
                },
                '\x1B' => self.clear(),
                _ => {
                    self.string = self.string.substr(0, self.offset) + key_event.character + self.string.substr(self.offset, self.string.len() - self.offset);
                    self.offset += 1;
                }
            }
        }
    }

    unsafe fn on_mouse(&mut self, mouse_point: Point, mouse_event: MouseEvent, allow_catch: bool) -> bool{
        return self.window.on_mouse(mouse_point, mouse_event, allow_catch);
    }
}<commit_msg>Repositioning of windows<commit_after>use common::debug::*;
use common::memory::*;
use common::string::*;

use drivers::disk::*;
use drivers::keyboard::*;
use drivers::mouse::*;

use filesystems::unfs::*;

use graphics::color::*;
use graphics::display::*;
use graphics::point::*;
use graphics::size::*;
use graphics::window::*;

use programs::program::*;

pub struct Editor {
    window: Window,
    filename: &'static str,
    string: String,
    offset: usize
}

impl Editor {
    pub unsafe fn new() -> Editor {
        Editor {
            window: Window{
                point: Point{ x:400, y:300 },
                size: Size { width:512, height:320 },
                title: "Press a function key to load a file",
                shaded: false,
                dragging: false,
                last_mouse_point: Point {
                    x: 0,
                    y: 0
                },
                last_mouse_event: MouseEvent {
                    x: 0,
                    y: 0,
                    left_button: false,
                    right_button: false,
                    middle_button: false,
                    valid: false
                }
            },
            filename: "",
            string: String::new(),
            offset: 0
        }
    }

    unsafe fn clear(&mut self){
        self.window.title = "Press a function key to load a file";
        self.filename = "";
        self.string = String::new();
        self.offset = 0;
    }

    unsafe fn load(&mut self, filename: &'static str){
        self.clear();
        let unfs = UnFS::new(Disk::new());
        let dest = unfs.load(filename);
        if dest > 0 {
            self.filename = filename;
            self.window.title = filename;
            self.string = String::from_c_str(dest as *const u8);
            self.offset = self.string.len();
            unalloc(dest);
        }else{
            d("Did not find '");
            d(filename);
            d("'\n");
        }
    }

    unsafe fn save(&self){
        let unfs = UnFS::new(Disk::new());
        let data = self.string.as_c_str() as usize;
        unfs.save(self.filename, data);
        unalloc(data);
        d("Saved\n");
    }
}

impl Program for Editor {
    unsafe fn draw(&self, display: &Display){
        self.window.draw(display);

		if ! self.window.shaded {
            let mut offset = 0;
            let mut row = 0;
            let mut col = 0;
            for c_ptr in self.string.as_slice() {
                if offset == self.offset && col < self.window.size.width / 8 && row < self.window.size.height / 16 {
                    display.char(Point::new(self.window.point.x + 8*col as i32, self.window.point.y + 16*row as i32), '_', Color::new(128, 128, 128));
                }

                let c = *c_ptr;
                if c == '\n' {
                    col = 0;
                    row += 1;
                }else if c == '\t' {
                    col += 8 - col % 8;
                }else{
                    if col < self.window.size.width / 8 && row < self.window.size.height / 16 {
                        let point = Point::new(self.window.point.x + 8*col as i32, self.window.point.y + 16*row as i32);
                        display.char(point, c, Color::new(255, 255, 255));
                        col += 1;
                    }
                }
                if col >= self.window.size.width / 8 {
                    col = 0;
                    row += 1;
                }

                offset += 1;
            }

            if offset == self.offset && col < self.window.size.width / 8 && row < self.window.size.height / 16 {
                display.char(Point::new(self.window.point.x + 8*col as i32, self.window.point.y + 16*row as i32), '_', Color::new(128, 128, 128));
            }
        }
    }

    unsafe fn on_key(&mut self, key_event: KeyEvent){
        if key_event.pressed {
            match key_event.scancode {
                0x3B => self.load("README.md"),
                0x3C => self.load("LICENSE.md"),
                0x40 => self.save(),
                0x47 => self.offset = 0,
                0x48 => for i in 1..self.offset {
                    match self.string.get(self.offset - i) {
                        '\0' => break,
                        '\n' => {
                            self.offset = self.offset - i;
                            break;
                        },
                        _ => ()
                    }
                },
                0x4B => if self.offset > 0 {
                            self.offset -= 1;
                        },
                0x4D => if self.offset < self.string.len() {
                            self.offset += 1;
                        },
                0x4F => self.offset = self.string.len(),
                0x50 => for i in self.offset + 1..self.string.len() {
                    match self.string.get(i) {
                        '\0' => break,
                        '\n' => {
                            self.offset = i;
                            break;
                        },
                        _ => ()
                    }
                },
                0x53 => if self.offset < self.string.len() {
                    self.string = self.string.substr(0, self.offset) + self.string.substr(self.offset + 1, self.string.len() - self.offset - 1);
                },
                _ => ()
            }

            match key_event.character {
                '\x00' => (),
                '\x08' => if self.offset > 0 {
                    self.string = self.string.substr(0, self.offset - 1) + self.string.substr(self.offset, self.string.len() - self.offset);
                    self.offset -= 1;
                },
                '\x1B' => self.clear(),
                _ => {
                    self.string = self.string.substr(0, self.offset) + key_event.character + self.string.substr(self.offset, self.string.len() - self.offset);
                    self.offset += 1;
                }
            }
        }
    }

    unsafe fn on_mouse(&mut self, mouse_point: Point, mouse_event: MouseEvent, allow_catch: bool) -> bool{
        return self.window.on_mouse(mouse_point, mouse_event, allow_catch);
    }
}<|endoftext|>


<commit_before><commit_msg>remove empty example file<commit_after><|endoftext|>


<commit_before>#[allow(unused)]
use log::{info, warn};
use hyper::{Body, Client, Method, Request};

use serde_json::json;
use std::fs;
use std::process::exit;

use librespot::core::authentication::Credentials;
use librespot::core::config::SessionConfig;
use librespot::core::keymaster;
use librespot::core::session::Session;
use librespot::core::spotify_id::SpotifyId;
use librespot::core::version;

use librespot::playback::audio_backend;
use librespot::playback::config::{
    AudioFormat, PlayerConfig
};
use librespot::playback::player::{
    Player, PlayerEvent
};

const VERSION: &'static str = concat!(env!("CARGO_PKG_NAME"), " v", env!("CARGO_PKG_VERSION"));

const SCOPES: &str = "user-read-private,playlist-read-private,playlist-read-collaborative,playlist-modify-public,playlist-modify-private,user-follow-modify,user-follow-read,user-library-read,user-library-modify,user-top-read,user-read-recently-played";

#[cfg(debug_assertions)]
const DEBUGMODE: bool = true;
#[cfg(not(debug_assertions))]
const DEBUGMODE: bool = false;

pub fn check() {
    println!(
        "ok {spottyvers} - using librespot {semver} {sha} (Built on {build_date}, Build ID: {build_id})",
        spottyvers = VERSION,
        semver = version::SEMVER,
        sha = version::SHA_SHORT,
        build_date = version::BUILD_DATE,
        build_id = version::BUILD_ID,
    );

    let capabilities = json!({
        "version": env!("CARGO_PKG_VERSION").to_string(),
        "lms-auth": false,
        "volume-normalisation": true,
        "debug": DEBUGMODE,
        "ogg-direct": true,
        "save-token": true,
        "podcasts": true,
        "zeroconf-port": true
    });

    println!("{}", capabilities.to_string());
    exit(0);
}

// inspired by examples/get_token.rs
pub async fn get_token(client_id: Option<String>, scopes: Option<String>, save_token: Option<String>, last_credentials: Option<Credentials>, session_config: SessionConfig) {
    match last_credentials {
        Some(last_credentials) => {
            if let Some(client_id) = client_id {
                let scopes = scopes.unwrap_or(SCOPES.to_string());

                let session = Session::connect(session_config, last_credentials, None)
                    .await
                    .unwrap();

                let token = keymaster::get_token(&session, &client_id, &scopes)
                    .await
                    .unwrap();

                let json_token = json!({
                    // keep backwards compatibility with older versions
                    "accessToken": token.access_token.to_string(),
                    "expiresIn": token.expires_in,
                });

                if let Some(save_token) = save_token {
                    fs::write(save_token.to_string(), format!("{}", json_token)).expect("Can't write token file");
                }
                else {
                    println!("{}", json_token);
                }
            }
            else {
                println!("Use --client-id to provide a CLIENT_ID");
            }
        }
        None => {
            println!("Missing credentials");
        }
    }
}

// inspired by examples/play.rs
pub async fn play_track(track_id: String, start_position: u32, last_credentials: Option<Credentials>, player_config: PlayerConfig, session_config: SessionConfig) {
    match last_credentials {
        Some(last_credentials) => {
            let backend = audio_backend::find(None).unwrap();
            let audio_format = AudioFormat::default();

            let track = SpotifyId::from_uri(
                track_id.replace("spotty://", "spotify:track:")
                .replace("://", ":")
                .as_str());

            match track {
                Ok(track) => {
                    let session = Session::connect(session_config, last_credentials, None)
                        .await
                        .unwrap();

                    let (mut player, _) = Player::new(player_config, session, None, move || {
                        backend(None, audio_format)
                    });

                    player.load(track, true, start_position);
                    player.await_end_of_track().await;
                }
                Err(error) => {
                    warn!("Problem getting a Spotify ID for {}: {:?}", track_id, error);
                    return;
                }
            };
        }
        None => {
            println!("Missing credentials");
        }
    }
}

// Connect mode support

#[derive(Clone)]
pub struct LMS {
    base_url: Option<String>,
    player_mac: Option<String>,
    auth: Option<String>
}

#[allow(unused)]
impl LMS {
    pub fn new(base_url: Option<String>, player_mac: Option<String>, auth: Option<String>) -> LMS {
        LMS {
            base_url: Some(format!("http://{}/jsonrpc.js", base_url.unwrap_or("localhost:9000".to_string()))),
            player_mac: player_mac,
            auth: auth
        }
    }

    pub fn is_configured(&self) -> bool {
        if self.base_url != None {
            if self.player_mac != None {
                return true;
            }
        }

        return false;
    }

    pub async fn signal_event(&self, event: PlayerEvent) {
        let mut command = r#"["spottyconnect","change"]"#.to_string();

        match event {
            PlayerEvent::Changed {
                old_track_id,
                new_track_id,
            } => {
                #[cfg(debug_assertions)]
                info!("event: changed, old track: {}, new track: {}", old_track_id.to_base62(), new_track_id.to_base62());
                command = format!(r#"["spottyconnect","change","{}","{}"]"#, new_track_id.to_base62().to_string(), old_track_id.to_base62().to_string());
            }
            PlayerEvent::Started { track_id, .. } => {
                #[cfg(debug_assertions)]
                info!("event: started, track: {}", track_id.to_base62());
                command = format!(r#"["spottyconnect","start","{}"]"#, track_id.to_base62().to_string());
            }
            PlayerEvent::Stopped { track_id, .. } => {
                #[cfg(debug_assertions)]
                info!("event: stopped, track: {}", track_id.to_base62());
                command = r#"["spottyconnect","stop"]"#.to_string();
            }
            PlayerEvent::Playing {
                track_id,
                duration_ms,
                position_ms,
                ..
            } => {
                #[cfg(debug_assertions)]
                info!("event: playing, track: {}, duration: {}, position: {}", track_id.to_base62(), duration_ms, position_ms);
                // we're not implementing the seek event here, as it's going to read player state anyway
                // but signal a change if the new position has changed and is > 0
                if position_ms <= 0 {
                    return;
                }
                command = r#"["spottyconnect","change"]"#.to_string();
            }
            PlayerEvent::Paused {
                track_id,
                duration_ms,
                position_ms,
                ..
            } => {
                #[cfg(debug_assertions)]
                info!("event: paused, track: {}, duration: {}, position: {}", track_id.to_base62(), duration_ms, position_ms);
                command = r#"["spottyconnect","stop"]"#.to_string();
            }
            PlayerEvent::VolumeSet { volume } => {
                let mut new_volume = volume as u32;
                if new_volume > 0 {
                    new_volume = new_volume * 100 / u32::pow(2, 16);
                }

                if new_volume > 100 {
                    new_volume = 100;
                };

                #[cfg(debug_assertions)]
                info!("event: volume: {}", volume);
                // we're not using the volume here, as LMS will read player state anyway
                command = format!(r#"["spottyconnect","volume",{}]"#, new_volume.to_string());
            }
            _ => return,
        }

        if !self.is_configured() {
            #[cfg(debug_assertions)]
            warn!("LMS connection is not configured");
            #[cfg(debug_assertions)]
            info!("{}", command);
            return;
        }

        if let Some(ref base_url) = self.base_url {
            #[cfg(debug_assertions)]
            info!("Base URL to talk to LMS: {}", base_url);

            if let Some(ref player_mac) = self.player_mac {
                #[cfg(debug_assertions)]
                info!("Player MAC address to control: {}", player_mac);

                #[cfg(debug_assertions)]
                info!("Command to send to player: {}", command);

                let json = format!(r#"{{"id": 1,"method":"slim.request","params":["{}",{}]}}"#, player_mac, command);

                let req = Request::builder()
                    .method(Method::POST)
                    .uri(base_url.to_string())
                    .header("content-type", "application/json")
                    .body(Body::from(json.clone())).unwrap();

                let client = Client::new();
                let resp = client.request(req).await;

                match resp {
                    Ok(resp) => {
                        println!("Response: {}", resp.status());
                    }
                    Err(error) => {
                        warn!("Problem posting to {} / {}: {:?}", base_url, json, error);
                        return;
                    }
                }
            }
        }
    }
}
<commit_msg>Re-introduce support for LMS authentication<commit_after>#[allow(unused)]
use log::{info, warn};
use hyper::{Body, Client, Method, Request};

use serde_json::json;
use std::fs;
use std::process::exit;

use librespot::core::authentication::Credentials;
use librespot::core::config::SessionConfig;
use librespot::core::keymaster;
use librespot::core::session::Session;
use librespot::core::spotify_id::SpotifyId;
use librespot::core::version;

use librespot::playback::audio_backend;
use librespot::playback::config::{
    AudioFormat, PlayerConfig
};
use librespot::playback::player::{
    Player, PlayerEvent
};

const VERSION: &'static str = concat!(env!("CARGO_PKG_NAME"), " v", env!("CARGO_PKG_VERSION"));

const SCOPES: &str = "user-read-private,playlist-read-private,playlist-read-collaborative,playlist-modify-public,playlist-modify-private,user-follow-modify,user-follow-read,user-library-read,user-library-modify,user-top-read,user-read-recently-played";

#[cfg(debug_assertions)]
const DEBUGMODE: bool = true;
#[cfg(not(debug_assertions))]
const DEBUGMODE: bool = false;

pub fn check() {
    println!(
        "ok {spottyvers} - using librespot {semver} {sha} (Built on {build_date}, Build ID: {build_id})",
        spottyvers = VERSION,
        semver = version::SEMVER,
        sha = version::SHA_SHORT,
        build_date = version::BUILD_DATE,
        build_id = version::BUILD_ID,
    );

    let capabilities = json!({
        "version": env!("CARGO_PKG_VERSION").to_string(),
        "autoplay": true,
        "lms-auth": true,
        "volume-normalisation": true,
        "debug": DEBUGMODE,
        "ogg-direct": true,
        "save-token": true,
        "podcasts": true,
        "zeroconf-port": true
    });

    println!("{}", capabilities.to_string());
    exit(0);
}

// inspired by examples/get_token.rs
pub async fn get_token(client_id: Option<String>, scopes: Option<String>, save_token: Option<String>, last_credentials: Option<Credentials>, session_config: SessionConfig) {
    match last_credentials {
        Some(last_credentials) => {
            if let Some(client_id) = client_id {
                let scopes = scopes.unwrap_or(SCOPES.to_string());

                let session = Session::connect(session_config, last_credentials, None)
                    .await
                    .unwrap();

                let token = keymaster::get_token(&session, &client_id, &scopes)
                    .await
                    .unwrap();

                let json_token = json!({
                    // keep backwards compatibility with older versions
                    "accessToken": token.access_token.to_string(),
                    "expiresIn": token.expires_in,
                });

                if let Some(save_token) = save_token {
                    fs::write(save_token.to_string(), format!("{}", json_token)).expect("Can't write token file");
                }
                else {
                    println!("{}", json_token);
                }
            }
            else {
                println!("Use --client-id to provide a CLIENT_ID");
            }
        }
        None => {
            println!("Missing credentials");
        }
    }
}

// inspired by examples/play.rs
pub async fn play_track(track_id: String, start_position: u32, last_credentials: Option<Credentials>, player_config: PlayerConfig, session_config: SessionConfig) {
    match last_credentials {
        Some(last_credentials) => {
            let backend = audio_backend::find(None).unwrap();
            let audio_format = AudioFormat::default();

            let track = SpotifyId::from_uri(
                track_id.replace("spotty://", "spotify:track:")
                .replace("://", ":")
                .as_str());

            match track {
                Ok(track) => {
                    let session = Session::connect(session_config, last_credentials, None)
                        .await
                        .unwrap();

                    let (mut player, _) = Player::new(player_config, session, None, move || {
                        backend(None, audio_format)
                    });

                    player.load(track, true, start_position);
                    player.await_end_of_track().await;
                }
                Err(error) => {
                    warn!("Problem getting a Spotify ID for {}: {:?}", track_id, error);
                    return;
                }
            };
        }
        None => {
            println!("Missing credentials");
        }
    }
}

// Connect mode support

#[derive(Clone)]
pub struct LMS {
    base_url: Option<String>,
    player_mac: Option<String>,
    auth: Option<String>
}

#[allow(unused)]
impl LMS {
    pub fn new(base_url: Option<String>, player_mac: Option<String>, auth: Option<String>) -> LMS {
        LMS {
            base_url: Some(format!("http://{}/jsonrpc.js", base_url.unwrap_or("localhost:9000".to_string()))),
            player_mac: player_mac,
            auth: auth
        }
    }

    pub fn is_configured(&self) -> bool {
        if self.base_url != None {
            if self.player_mac != None {
                return true;
            }
        }

        return false;
    }

    pub async fn signal_event(&self, event: PlayerEvent) {
        let mut command = r#"["spottyconnect","change"]"#.to_string();

        match event {
            PlayerEvent::Changed {
                old_track_id,
                new_track_id,
            } => {
                #[cfg(debug_assertions)]
                info!("event: changed, old track: {}, new track: {}", old_track_id.to_base62(), new_track_id.to_base62());
                command = format!(r#"["spottyconnect","change","{}","{}"]"#, new_track_id.to_base62().to_string(), old_track_id.to_base62().to_string());
            }
            PlayerEvent::Started { track_id, .. } => {
                #[cfg(debug_assertions)]
                info!("event: started, track: {}", track_id.to_base62());
                command = format!(r#"["spottyconnect","start","{}"]"#, track_id.to_base62().to_string());
            }
            PlayerEvent::Stopped { track_id, .. } => {
                #[cfg(debug_assertions)]
                info!("event: stopped, track: {}", track_id.to_base62());
                command = r#"["spottyconnect","stop"]"#.to_string();
            }
            PlayerEvent::Playing {
                track_id,
                duration_ms,
                position_ms,
                ..
            } => {
                #[cfg(debug_assertions)]
                info!("event: playing, track: {}, duration: {}, position: {}", track_id.to_base62(), duration_ms, position_ms);
                // we're not implementing the seek event here, as it's going to read player state anyway
                // but signal a change if the new position has changed and is > 0
                if position_ms <= 0 {
                    return;
                }
                command = r#"["spottyconnect","change"]"#.to_string();
            }
            PlayerEvent::Paused {
                track_id,
                duration_ms,
                position_ms,
                ..
            } => {
                #[cfg(debug_assertions)]
                info!("event: paused, track: {}, duration: {}, position: {}", track_id.to_base62(), duration_ms, position_ms);
                command = r#"["spottyconnect","stop"]"#.to_string();
            }
            PlayerEvent::VolumeSet { volume } => {
                let mut new_volume = volume as u32;
                if new_volume > 0 {
                    new_volume = new_volume * 100 / u32::pow(2, 16);
                }

                if new_volume > 100 {
                    new_volume = 100;
                };

                #[cfg(debug_assertions)]
                info!("event: volume: {}", volume);
                // we're not using the volume here, as LMS will read player state anyway
                command = format!(r#"["spottyconnect","volume",{}]"#, new_volume.to_string());
            }
            _ => return,
        }

        if !self.is_configured() {
            #[cfg(debug_assertions)]
            warn!("LMS connection is not configured");
            #[cfg(debug_assertions)]
            info!("{}", command);
            return;
        }

        if let Some(ref base_url) = self.base_url {
            #[cfg(debug_assertions)]
            info!("Base URL to talk to LMS: {}", base_url);

            if let Some(ref player_mac) = self.player_mac {
                #[cfg(debug_assertions)]
                info!("Player MAC address to control: {}", player_mac);

                #[cfg(debug_assertions)]
                info!("Command to send to player: {}", command);

                let json = format!(r#"{{"id": 1,"method":"slim.request","params":["{}",{}]}}"#, player_mac, command);

                let mut auth_header = "".to_string();
                if let Some(ref auth) = self.auth {
                    auth_header = auth.trim().to_string();
                }

                let req = Request::builder()
                    .method(Method::POST)
                    .uri(base_url.to_string())
                    .header("user-agent", format!("{}", VERSION))
                    .header("content-type", "application/json")
                    .header("authorization", format!("Basic {}", auth_header))
                    .header("x-scanner", "1")
                    .body(Body::from(json.clone())).unwrap();

                let client = Client::new();
                let resp = client.request(req).await;

                match resp {
                    Ok(resp) => {
                        #[cfg(debug_assertions)]
                        info!("Response: {}", resp.status());
                    }
                    Err(error) => {
                        warn!("Problem posting to {} / {}: {:?}", base_url, json, error);
                    }
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>// Lints
#![forbid(private_in_public)]
#![forbid(bare_trait_objects)]
#![deny(unused_must_use)]
#![deny(unused_assignments)]
#![deny(clippy::missing_safety_doc)]
#![allow(clippy::empty_loop)]
// no_std
#![no_std]
// Unstable features
#![feature(alloc_error_handler)]
#![feature(alloc_prelude)]
#![feature(allocator_api)]
#![feature(asm)]
#![feature(const_fn)]
#![feature(integer_atomics)]
#![feature(panic_info_message)]

mod allocator;

pub mod attachment;
pub mod console;
pub mod fs;
pub mod process;
pub mod syscall;

use core::alloc::Layout;
use core::panic::PanicInfo;

#[macro_use]
extern crate alloc;

extern "Rust" {
    fn main() -> u64;
}

#[no_mangle]
pub extern "C" fn _start() {
    let return_code = unsafe { main() };
    self::syscall::exit(return_code);
}

#[panic_handler]
#[no_mangle]
extern "C" fn panic(info: &PanicInfo) -> ! {
    use self::syscall::debug_print;
    if let Some(location) = info.location() {
        let _ = debug_print(&format!(
            "Error: file '{}', line {}",
            location.file(),
            location.line()
        ));
    } else {
        let _ = debug_print("Error: (location unavailable)");
    }

    if let Some(msg) = info.message() {
        let _ = debug_print(&format!("  {:?}", msg));
    } else {
        let _ = debug_print("  Info unavailable");
    }

    syscall::exit(1)
}

#[global_allocator]
static HEAP_ALLOCATOR: allocator::GlobAlloc =
    allocator::GlobAlloc::new(allocator::BlockAllocator::new());

#[alloc_error_handler]
fn out_of_memory(_: Layout) -> ! {
    unsafe {
        asm!("xchg bx, bx"::::"intel","volatile");
        asm!("cli"::::"intel","volatile");
        asm!("hlt"::::"intel","volatile");
    }
    loop {}
}
<commit_msg>Re-export some libraries in libd7<commit_after>// Lints
#![forbid(private_in_public)]
#![forbid(bare_trait_objects)]
#![deny(unused_must_use)]
#![deny(unused_assignments)]
#![deny(clippy::missing_safety_doc)]
#![allow(clippy::empty_loop)]
// no_std
#![no_std]
// Unstable features
#![feature(alloc_error_handler)]
#![feature(alloc_prelude)]
#![feature(allocator_api)]
#![feature(asm)]
#![feature(const_fn)]
#![feature(integer_atomics)]
#![feature(panic_info_message)]

mod allocator;

pub mod attachment;
pub mod console;
pub mod fs;
pub mod process;
pub mod syscall;

use core::alloc::Layout;
use core::panic::PanicInfo;

pub use d7abi;
pub use pinecone;

#[macro_use]
extern crate alloc;

extern "Rust" {
    fn main() -> u64;
}

#[no_mangle]
pub extern "C" fn _start() {
    let return_code = unsafe { main() };
    self::syscall::exit(return_code);
}

#[panic_handler]
#[no_mangle]
extern "C" fn panic(info: &PanicInfo) -> ! {
    use self::syscall::debug_print;
    if let Some(location) = info.location() {
        let _ = debug_print(&format!(
            "Error: file '{}', line {}",
            location.file(),
            location.line()
        ));
    } else {
        let _ = debug_print("Error: (location unavailable)");
    }

    if let Some(msg) = info.message() {
        let _ = debug_print(&format!("  {:?}", msg));
    } else {
        let _ = debug_print("  Info unavailable");
    }

    syscall::exit(1)
}

#[global_allocator]
static HEAP_ALLOCATOR: allocator::GlobAlloc =
    allocator::GlobAlloc::new(allocator::BlockAllocator::new());

#[alloc_error_handler]
fn out_of_memory(_: Layout) -> ! {
    unsafe {
        asm!("xchg bx, bx"::::"intel","volatile");
        asm!("cli"::::"intel","volatile");
        asm!("hlt"::::"intel","volatile");
    }
    loop {}
}
<|endoftext|>


<commit_before>#![cfg_attr(feature = "cargo-clippy", allow(unused_parens))]
#[macro_use]
extern crate serde_derive;

use toml;

use std::collections::HashSet;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};

mod error;

pub use crate::error::{Error, Result};
pub use askama_escape::MarkupDisplay;

use std::collections::BTreeMap;

pub mod filters;
pub mod helpers;

#[derive(Debug)]
pub struct Config<'a> {
    pub dirs: Vec<PathBuf>,
    pub syntaxes: BTreeMap<String, Syntax<'a>>,
    pub default_syntax: &'a str,
    pub escapers: Vec<(HashSet<String>, String)>,
}

impl<'a> Config<'a> {
    pub fn new(s: &str) -> Config<'_> {
        let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        let default_dirs = vec![root.join("templates")];

        let mut syntaxes = BTreeMap::new();
        syntaxes.insert(DEFAULT_SYNTAX_NAME.to_string(), Syntax::default());

        let raw: RawConfig<'_> =
            toml::from_str(&s).expect(&format!("invalid TOML in {}", CONFIG_FILE_NAME));

        let (dirs, default_syntax) = match raw.general {
            Some(General {
                dirs,
                default_syntax,
            }) => (
                dirs.map_or(default_dirs, |v| {
                    v.into_iter().map(|dir| root.join(dir)).collect()
                }),
                default_syntax.unwrap_or(DEFAULT_SYNTAX_NAME),
            ),
            None => (default_dirs, DEFAULT_SYNTAX_NAME),
        };

        if let Some(raw_syntaxes) = raw.syntax {
            for raw_s in raw_syntaxes {
                let name = raw_s.name;

                if let Some(_) = syntaxes.insert(name.to_string(), Syntax::from(raw_s)) {
                    panic!("syntax \"{}\" is already defined", name)
                }
            }
        }

        if !syntaxes.contains_key(default_syntax) {
            panic!("default syntax \"{}\" not found", default_syntax)
        }

        let mut escapers = Vec::new();
        if let Some(configured) = raw.escaper {
            for escaper in configured {
                escapers.push((
                    escaper
                        .extensions
                        .iter()
                        .map(|ext| ext.to_string())
                        .collect(),
                    escaper.path.to_string(),
                ));
            }
        }
        for (extensions, path) in DEFAULT_ESCAPERS {
            escapers.push((str_set(extensions), path.to_string()));
        }

        Config {
            dirs,
            syntaxes,
            default_syntax,
            escapers,
        }
    }

    pub fn find_template(&self, path: &str, start_at: Option<&Path>) -> PathBuf {
        if let Some(root) = start_at {
            let relative = root.with_file_name(path);
            if relative.exists() {
                return relative.to_owned();
            }
        }

        for dir in &self.dirs {
            let rooted = dir.join(path);
            if rooted.exists() {
                return rooted;
            }
        }

        panic!(
            "template {:?} not found in directories {:?}",
            path, self.dirs
        )
    }
}

#[derive(Debug)]
pub struct Syntax<'a> {
    pub block_start: &'a str,
    pub block_end: &'a str,
    pub expr_start: &'a str,
    pub expr_end: &'a str,
    pub comment_start: &'a str,
    pub comment_end: &'a str,
}

impl<'a> Default for Syntax<'a> {
    fn default() -> Self {
        Self {
            block_start: "{%",
            block_end: "%}",
            expr_start: "{{",
            expr_end: "}}",
            comment_start: "{#",
            comment_end: "#}",
        }
    }
}

impl<'a> From<RawSyntax<'a>> for Syntax<'a> {
    fn from(raw: RawSyntax<'a>) -> Self {
        let default = Self::default();
        let syntax = Self {
            block_start: raw.block_start.unwrap_or(default.block_start),
            block_end: raw.block_end.unwrap_or(default.block_end),
            expr_start: raw.expr_start.unwrap_or(default.expr_start),
            expr_end: raw.expr_end.unwrap_or(default.expr_end),
            comment_start: raw.comment_start.unwrap_or(default.comment_start),
            comment_end: raw.comment_end.unwrap_or(default.comment_end),
        };

        if syntax.block_start.len() != 2
            || syntax.block_end.len() != 2
            || syntax.expr_start.len() != 2
            || syntax.expr_end.len() != 2
            || syntax.comment_start.len() != 2
            || syntax.comment_end.len() != 2
        {
            panic!("length of delimiters must be two")
        }

        let bs = syntax.block_start.as_bytes()[0];
        let be = syntax.block_start.as_bytes()[1];
        let cs = syntax.comment_start.as_bytes()[0];
        let ce = syntax.comment_start.as_bytes()[1];
        let es = syntax.block_start.as_bytes()[0];
        let ee = syntax.block_start.as_bytes()[1];
        if !(bs == cs && bs == es) && !(be == ce && be == ee) {
            panic!("bad delimiters block_start: {}, comment_start: {}, expr_start: {}, needs one of the two characters in common", syntax.block_start, syntax.comment_start, syntax.expr_start);
        }

        syntax
    }
}

#[derive(Deserialize)]
struct RawConfig<'d> {
    #[serde(borrow)]
    general: Option<General<'d>>,
    syntax: Option<Vec<RawSyntax<'d>>>,
    escaper: Option<Vec<RawEscaper<'d>>>,
}

#[derive(Deserialize)]
struct General<'a> {
    #[serde(borrow)]
    dirs: Option<Vec<&'a str>>,
    default_syntax: Option<&'a str>,
}

#[derive(Deserialize)]
struct RawSyntax<'a> {
    name: &'a str,
    block_start: Option<&'a str>,
    block_end: Option<&'a str>,
    expr_start: Option<&'a str>,
    expr_end: Option<&'a str>,
    comment_start: Option<&'a str>,
    comment_end: Option<&'a str>,
}

#[derive(Deserialize)]
struct RawEscaper<'a> {
    path: &'a str,
    extensions: Vec<&'a str>,
}

pub fn read_config_file() -> String {
    let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
    let filename = root.join(CONFIG_FILE_NAME);
    if filename.exists() {
        fs::read_to_string(&filename)
            .expect(&format!("unable to read {}", filename.to_str().unwrap()))
    } else {
        "".to_string()
    }
}

fn str_set<T>(vals: &[T]) -> HashSet<String>
where
    T: ToString,
{
    vals.iter().map(|s| s.to_string()).collect()
}

static CONFIG_FILE_NAME: &str = "askama.toml";
static DEFAULT_SYNTAX_NAME: &str = "default";
static DEFAULT_ESCAPERS: &[(&[&str], &str)] = &[
    (&["html", "htm", "xml"], "::askama::Html"),
    (&["none", "txt", ""], "::askama::Text"),
    (&["j2", "jinja", "jinja2"], "::askama::Html"),
];

#[cfg(test)]
mod tests {
    use super::*;
    use std::env;
    use std::path::{Path, PathBuf};

    #[test]
    fn test_default_config() {
        let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        root.push("templates");
        let config = Config::new("");
        assert_eq!(config.dirs, vec![root]);
    }

    #[test]
    fn test_config_dirs() {
        let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        root.push("tpl");
        let config = Config::new("[general]\ndirs = [\"tpl\"]");
        assert_eq!(config.dirs, vec![root]);
    }

    fn assert_eq_rooted(actual: &Path, expected: &str) {
        let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        root.push("templates");
        let mut inner = PathBuf::new();
        inner.push(expected);
        assert_eq!(actual.strip_prefix(root).unwrap(), inner);
    }

    #[test]
    fn find_absolute() {
        let config = Config::new("");
        let root = config.find_template("a.html", None);
        let path = config.find_template("sub/b.html", Some(&root));
        assert_eq_rooted(&path, "sub/b.html");
    }

    #[test]
    #[should_panic]
    fn find_relative_nonexistent() {
        let config = Config::new("");
        let root = config.find_template("a.html", None);
        config.find_template("b.html", Some(&root));
    }

    #[test]
    fn find_relative() {
        let config = Config::new("");
        let root = config.find_template("sub/b.html", None);
        let path = config.find_template("c.html", Some(&root));
        assert_eq_rooted(&path, "sub/c.html");
    }

    #[test]
    fn find_relative_sub() {
        let config = Config::new("");
        let root = config.find_template("sub/b.html", None);
        let path = config.find_template("sub1/d.html", Some(&root));
        assert_eq_rooted(&path, "sub/sub1/d.html");
    }

    #[test]
    fn add_syntax() {
        let raw_config = r#"
        [general]
        default_syntax = "foo"

        [[syntax]]
        name = "foo"
        block_start = "{<"

        [[syntax]]
        name = "bar"
        expr_start = "{!"
        "#;

        let default_syntax = Syntax::default();
        let config = Config::new(raw_config);
        assert_eq!(config.default_syntax, "foo");

        let foo = config.syntaxes.get("foo").unwrap();
        assert_eq!(foo.block_start, "{<");
        assert_eq!(foo.block_end, default_syntax.block_end);
        assert_eq!(foo.expr_start, default_syntax.expr_start);
        assert_eq!(foo.expr_end, default_syntax.expr_end);
        assert_eq!(foo.comment_start, default_syntax.comment_start);
        assert_eq!(foo.comment_end, default_syntax.comment_end);

        let bar = config.syntaxes.get("bar").unwrap();
        assert_eq!(bar.block_start, default_syntax.block_start);
        assert_eq!(bar.block_end, default_syntax.block_end);
        assert_eq!(bar.expr_start, "{!");
        assert_eq!(bar.expr_end, default_syntax.expr_end);
        assert_eq!(bar.comment_start, default_syntax.comment_start);
        assert_eq!(bar.comment_end, default_syntax.comment_end);
    }

    #[test]
    fn add_syntax_two() {
        let raw_config = r#"
        syntax = [{ name = "foo", block_start = "{<" },
                  { name = "bar", expr_start = "{!" } ]

        [general]
        default_syntax = "foo"
        "#;

        let default_syntax = Syntax::default();
        let config = Config::new(raw_config);
        assert_eq!(config.default_syntax, "foo");

        let foo = config.syntaxes.get("foo").unwrap();
        assert_eq!(foo.block_start, "{<");
        assert_eq!(foo.block_end, default_syntax.block_end);
        assert_eq!(foo.expr_start, default_syntax.expr_start);
        assert_eq!(foo.expr_end, default_syntax.expr_end);
        assert_eq!(foo.comment_start, default_syntax.comment_start);
        assert_eq!(foo.comment_end, default_syntax.comment_end);

        let bar = config.syntaxes.get("bar").unwrap();
        assert_eq!(bar.block_start, default_syntax.block_start);
        assert_eq!(bar.block_end, default_syntax.block_end);
        assert_eq!(bar.expr_start, "{!");
        assert_eq!(bar.expr_end, default_syntax.expr_end);
        assert_eq!(bar.comment_start, default_syntax.comment_start);
        assert_eq!(bar.comment_end, default_syntax.comment_end);
    }

    #[should_panic]
    #[test]
    fn use_default_at_syntax_name() {
        let raw_config = r#"
        syntax = [{ name = "default" }]
        "#;

        let _config = Config::new(raw_config);
    }

    #[should_panic]
    #[test]
    fn duplicated_syntax_name_on_list() {
        let raw_config = r#"
        syntax = [{ name = "foo", block_start = "~<" },
                  { name = "foo", block_start = "%%" } ]
        "#;

        let _config = Config::new(raw_config);
    }

    #[should_panic]
    #[test]
    fn is_not_exist_default_syntax() {
        let raw_config = r#"
        [general]
        default_syntax = "foo"
        "#;

        let _config = Config::new(raw_config);
    }

    #[test]
    fn escape_modes() {
        let config = Config::new(
            r#"
            [[escaper]]
            path = "::askama::Js"
            extensions = ["js"]
        "#,
        );
        assert_eq!(
            config.escapers,
            vec![
                (str_set(&["js"]), "::askama::Js".into()),
                (str_set(&["html", "htm", "xml"]), "::askama::Html".into()),
                (str_set(&["md", "yml", "none", "txt", ""]), "::askama::Text".into()),
                (str_set(&["j2", "jinja", "jinja2"]), "::askama::Html".into()),
            ]
        );
    }
}
<commit_msg>Fix tests for extensions<commit_after>#![cfg_attr(feature = "cargo-clippy", allow(unused_parens))]
#[macro_use]
extern crate serde_derive;

use toml;

use std::collections::HashSet;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};

mod error;

pub use crate::error::{Error, Result};
pub use askama_escape::MarkupDisplay;

use std::collections::BTreeMap;

pub mod filters;
pub mod helpers;

#[derive(Debug)]
pub struct Config<'a> {
    pub dirs: Vec<PathBuf>,
    pub syntaxes: BTreeMap<String, Syntax<'a>>,
    pub default_syntax: &'a str,
    pub escapers: Vec<(HashSet<String>, String)>,
}

impl<'a> Config<'a> {
    pub fn new(s: &str) -> Config<'_> {
        let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        let default_dirs = vec![root.join("templates")];

        let mut syntaxes = BTreeMap::new();
        syntaxes.insert(DEFAULT_SYNTAX_NAME.to_string(), Syntax::default());

        let raw: RawConfig<'_> =
            toml::from_str(&s).expect(&format!("invalid TOML in {}", CONFIG_FILE_NAME));

        let (dirs, default_syntax) = match raw.general {
            Some(General {
                dirs,
                default_syntax,
            }) => (
                dirs.map_or(default_dirs, |v| {
                    v.into_iter().map(|dir| root.join(dir)).collect()
                }),
                default_syntax.unwrap_or(DEFAULT_SYNTAX_NAME),
            ),
            None => (default_dirs, DEFAULT_SYNTAX_NAME),
        };

        if let Some(raw_syntaxes) = raw.syntax {
            for raw_s in raw_syntaxes {
                let name = raw_s.name;

                if let Some(_) = syntaxes.insert(name.to_string(), Syntax::from(raw_s)) {
                    panic!("syntax \"{}\" is already defined", name)
                }
            }
        }

        if !syntaxes.contains_key(default_syntax) {
            panic!("default syntax \"{}\" not found", default_syntax)
        }

        let mut escapers = Vec::new();
        if let Some(configured) = raw.escaper {
            for escaper in configured {
                escapers.push((
                    escaper
                        .extensions
                        .iter()
                        .map(|ext| ext.to_string())
                        .collect(),
                    escaper.path.to_string(),
                ));
            }
        }
        for (extensions, path) in DEFAULT_ESCAPERS {
            escapers.push((str_set(extensions), path.to_string()));
        }

        Config {
            dirs,
            syntaxes,
            default_syntax,
            escapers,
        }
    }

    pub fn find_template(&self, path: &str, start_at: Option<&Path>) -> PathBuf {
        if let Some(root) = start_at {
            let relative = root.with_file_name(path);
            if relative.exists() {
                return relative.to_owned();
            }
        }

        for dir in &self.dirs {
            let rooted = dir.join(path);
            if rooted.exists() {
                return rooted;
            }
        }

        panic!(
            "template {:?} not found in directories {:?}",
            path, self.dirs
        )
    }
}

#[derive(Debug)]
pub struct Syntax<'a> {
    pub block_start: &'a str,
    pub block_end: &'a str,
    pub expr_start: &'a str,
    pub expr_end: &'a str,
    pub comment_start: &'a str,
    pub comment_end: &'a str,
}

impl<'a> Default for Syntax<'a> {
    fn default() -> Self {
        Self {
            block_start: "{%",
            block_end: "%}",
            expr_start: "{{",
            expr_end: "}}",
            comment_start: "{#",
            comment_end: "#}",
        }
    }
}

impl<'a> From<RawSyntax<'a>> for Syntax<'a> {
    fn from(raw: RawSyntax<'a>) -> Self {
        let default = Self::default();
        let syntax = Self {
            block_start: raw.block_start.unwrap_or(default.block_start),
            block_end: raw.block_end.unwrap_or(default.block_end),
            expr_start: raw.expr_start.unwrap_or(default.expr_start),
            expr_end: raw.expr_end.unwrap_or(default.expr_end),
            comment_start: raw.comment_start.unwrap_or(default.comment_start),
            comment_end: raw.comment_end.unwrap_or(default.comment_end),
        };

        if syntax.block_start.len() != 2
            || syntax.block_end.len() != 2
            || syntax.expr_start.len() != 2
            || syntax.expr_end.len() != 2
            || syntax.comment_start.len() != 2
            || syntax.comment_end.len() != 2
        {
            panic!("length of delimiters must be two")
        }

        let bs = syntax.block_start.as_bytes()[0];
        let be = syntax.block_start.as_bytes()[1];
        let cs = syntax.comment_start.as_bytes()[0];
        let ce = syntax.comment_start.as_bytes()[1];
        let es = syntax.block_start.as_bytes()[0];
        let ee = syntax.block_start.as_bytes()[1];
        if !(bs == cs && bs == es) && !(be == ce && be == ee) {
            panic!("bad delimiters block_start: {}, comment_start: {}, expr_start: {}, needs one of the two characters in common", syntax.block_start, syntax.comment_start, syntax.expr_start);
        }

        syntax
    }
}

#[derive(Deserialize)]
struct RawConfig<'d> {
    #[serde(borrow)]
    general: Option<General<'d>>,
    syntax: Option<Vec<RawSyntax<'d>>>,
    escaper: Option<Vec<RawEscaper<'d>>>,
}

#[derive(Deserialize)]
struct General<'a> {
    #[serde(borrow)]
    dirs: Option<Vec<&'a str>>,
    default_syntax: Option<&'a str>,
}

#[derive(Deserialize)]
struct RawSyntax<'a> {
    name: &'a str,
    block_start: Option<&'a str>,
    block_end: Option<&'a str>,
    expr_start: Option<&'a str>,
    expr_end: Option<&'a str>,
    comment_start: Option<&'a str>,
    comment_end: Option<&'a str>,
}

#[derive(Deserialize)]
struct RawEscaper<'a> {
    path: &'a str,
    extensions: Vec<&'a str>,
}

pub fn read_config_file() -> String {
    let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
    let filename = root.join(CONFIG_FILE_NAME);
    if filename.exists() {
        fs::read_to_string(&filename)
            .expect(&format!("unable to read {}", filename.to_str().unwrap()))
    } else {
        "".to_string()
    }
}

fn str_set<T>(vals: &[T]) -> HashSet<String>
where
    T: ToString,
{
    vals.iter().map(|s| s.to_string()).collect()
}

static CONFIG_FILE_NAME: &str = "askama.toml";
static DEFAULT_SYNTAX_NAME: &str = "default";
static DEFAULT_ESCAPERS: &[(&[&str], &str)] = &[
    (&["html", "htm", "xml"], "::askama::Html"),
    (&["md", "none", "txt", "yml", ""], "::askama::Text"),
    (&["j2", "jinja", "jinja2"], "::askama::Html"),
];

#[cfg(test)]
mod tests {
    use super::*;
    use std::env;
    use std::path::{Path, PathBuf};

    #[test]
    fn test_default_config() {
        let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        root.push("templates");
        let config = Config::new("");
        assert_eq!(config.dirs, vec![root]);
    }

    #[test]
    fn test_config_dirs() {
        let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        root.push("tpl");
        let config = Config::new("[general]\ndirs = [\"tpl\"]");
        assert_eq!(config.dirs, vec![root]);
    }

    fn assert_eq_rooted(actual: &Path, expected: &str) {
        let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        root.push("templates");
        let mut inner = PathBuf::new();
        inner.push(expected);
        assert_eq!(actual.strip_prefix(root).unwrap(), inner);
    }

    #[test]
    fn find_absolute() {
        let config = Config::new("");
        let root = config.find_template("a.html", None);
        let path = config.find_template("sub/b.html", Some(&root));
        assert_eq_rooted(&path, "sub/b.html");
    }

    #[test]
    #[should_panic]
    fn find_relative_nonexistent() {
        let config = Config::new("");
        let root = config.find_template("a.html", None);
        config.find_template("b.html", Some(&root));
    }

    #[test]
    fn find_relative() {
        let config = Config::new("");
        let root = config.find_template("sub/b.html", None);
        let path = config.find_template("c.html", Some(&root));
        assert_eq_rooted(&path, "sub/c.html");
    }

    #[test]
    fn find_relative_sub() {
        let config = Config::new("");
        let root = config.find_template("sub/b.html", None);
        let path = config.find_template("sub1/d.html", Some(&root));
        assert_eq_rooted(&path, "sub/sub1/d.html");
    }

    #[test]
    fn add_syntax() {
        let raw_config = r#"
        [general]
        default_syntax = "foo"

        [[syntax]]
        name = "foo"
        block_start = "{<"

        [[syntax]]
        name = "bar"
        expr_start = "{!"
        "#;

        let default_syntax = Syntax::default();
        let config = Config::new(raw_config);
        assert_eq!(config.default_syntax, "foo");

        let foo = config.syntaxes.get("foo").unwrap();
        assert_eq!(foo.block_start, "{<");
        assert_eq!(foo.block_end, default_syntax.block_end);
        assert_eq!(foo.expr_start, default_syntax.expr_start);
        assert_eq!(foo.expr_end, default_syntax.expr_end);
        assert_eq!(foo.comment_start, default_syntax.comment_start);
        assert_eq!(foo.comment_end, default_syntax.comment_end);

        let bar = config.syntaxes.get("bar").unwrap();
        assert_eq!(bar.block_start, default_syntax.block_start);
        assert_eq!(bar.block_end, default_syntax.block_end);
        assert_eq!(bar.expr_start, "{!");
        assert_eq!(bar.expr_end, default_syntax.expr_end);
        assert_eq!(bar.comment_start, default_syntax.comment_start);
        assert_eq!(bar.comment_end, default_syntax.comment_end);
    }

    #[test]
    fn add_syntax_two() {
        let raw_config = r#"
        syntax = [{ name = "foo", block_start = "{<" },
                  { name = "bar", expr_start = "{!" } ]

        [general]
        default_syntax = "foo"
        "#;

        let default_syntax = Syntax::default();
        let config = Config::new(raw_config);
        assert_eq!(config.default_syntax, "foo");

        let foo = config.syntaxes.get("foo").unwrap();
        assert_eq!(foo.block_start, "{<");
        assert_eq!(foo.block_end, default_syntax.block_end);
        assert_eq!(foo.expr_start, default_syntax.expr_start);
        assert_eq!(foo.expr_end, default_syntax.expr_end);
        assert_eq!(foo.comment_start, default_syntax.comment_start);
        assert_eq!(foo.comment_end, default_syntax.comment_end);

        let bar = config.syntaxes.get("bar").unwrap();
        assert_eq!(bar.block_start, default_syntax.block_start);
        assert_eq!(bar.block_end, default_syntax.block_end);
        assert_eq!(bar.expr_start, "{!");
        assert_eq!(bar.expr_end, default_syntax.expr_end);
        assert_eq!(bar.comment_start, default_syntax.comment_start);
        assert_eq!(bar.comment_end, default_syntax.comment_end);
    }

    #[should_panic]
    #[test]
    fn use_default_at_syntax_name() {
        let raw_config = r#"
        syntax = [{ name = "default" }]
        "#;

        let _config = Config::new(raw_config);
    }

    #[should_panic]
    #[test]
    fn duplicated_syntax_name_on_list() {
        let raw_config = r#"
        syntax = [{ name = "foo", block_start = "~<" },
                  { name = "foo", block_start = "%%" } ]
        "#;

        let _config = Config::new(raw_config);
    }

    #[should_panic]
    #[test]
    fn is_not_exist_default_syntax() {
        let raw_config = r#"
        [general]
        default_syntax = "foo"
        "#;

        let _config = Config::new(raw_config);
    }

    #[test]
    fn escape_modes() {
        let config = Config::new(
            r#"
            [[escaper]]
            path = "::askama::Js"
            extensions = ["js"]
        "#,
        );
        assert_eq!(
            config.escapers,
            vec![
                (str_set(&["js"]), "::askama::Js".into()),
                (str_set(&["html", "htm", "xml"]), "::askama::Html".into()),
                (str_set(&["md", "none", "txt", "yml", ""]), "::askama::Text".into()),
                (str_set(&["j2", "jinja", "jinja2"]), "::askama::Html".into()),
            ]
        );
    }
}
<|endoftext|>