<commit_before>extern crate image;
extern crate clap;
extern crate rand;
extern crate itertools;

use clap::*;

mod codec;

mod rgba;
mod rgb;
mod gray_alpha;

mod utils;

fn main()
{
    let matches = App::new("stag")
        .version("0.0.1")
        .about("Image steganography")
        .subcommand(SubCommand::with_name("encode")
            .about("encodes a file")
            .arg(Arg::with_name("mode")
                 .short("m")
                 .long("mode")
                 .value_name("MODE")
                 .help("Set the encoding mode, default depends on SOURCE type")
                 .takes_value(true))
            .arg(Arg::with_name("SOURCE")
                 .help("The image source")
                 .index(1)
                 .required(true))
            .arg(Arg::with_name("OUTPUT")
                 .help("The output image")
                 .index(2)
                 .required(true)))
        .subcommand(SubCommand::with_name("decode")
            .about("decodes a file")
            .arg(Arg::with_name("mode")
                 .short("m")
                 .long("mode")
                 .value_name("MODE")
                 .help("Set the decoding mode, default depends on SOURCE type")
                 .takes_value(true))
            .arg(Arg::with_name("SOURCE")
                 .help("The image source")
                 .index(1)
                 .required(true))
            .arg(Arg::with_name("length")
                 .short("l")
                 .long("length")
                 .help("the amount of bytes to decode")
                 .value_name("LENGTH")
                 .takes_value(true)
                 .required(true)))
        .subcommand(SubCommand::with_name("estimate")
                .about("estimate how many bytes will fit into a file")
                .arg(Arg::with_name("SOURCE")
                     .help("The image source")
                     .index(1)
                     .takes_value(true))
                .arg(Arg::with_name("mode")
                     .short("m")
                     .long("mode")
                     .value_name("MODE")
                     .help("The encoding mode, default depends on SOURCE type")
                     .takes_value(true)))
        .get_matches();

    if let Some(matches) = matches.subcommand_matches("encode")
    {
        dispatch_encode(
            matches.value_of("mode"),
            matches.value_of("SOURCE").unwrap(),
            matches.value_of("OUTPUT").unwrap()
        );
    }

    if let Some(matches) = matches.subcommand_matches("decode")
    {
        dispatch_decode(
            matches.value_of("mode"),
            matches.value_of("SOURCE").unwrap(),
            matches.value_of("length").unwrap()
        );
    }

    if let Some(matches) = matches.subcommand_matches("estimate")
    {
        dispatch_estimate(
            matches.value_of("mode"),
            matches.value_of("SOURCE").unwrap()
        );
    }
}

use std::io::{stdin, stdout, Read, Write};
use std::str::FromStr;

use image::{open, DynamicImage};
use rand::StdRng;

use codec::Codec;
use rgba::RgbaCodec;
use rgb::RgbCodec;

fn dispatch_encode(mode: Option<&str>, source: &str, output: &str)
{
    let dyimage = match open(source)
    {
        Ok(di) => di,
        Err(_) => error_out("Error opening source image for encoding")
    };

    match dyimage
    {
        DynamicImage::ImageRgba8(image) =>
        {
            match encode::<RgbaCodec>(image, mode).save(output)
            {
                Ok(_) => {},
                Err(_) => error_out("Error saving encoded output file"),
            };
        },
        DynamicImage::ImageRgb8(image) =>
        {
            match encode::<RgbCodec>(image, mode).save(output)
            {
                Ok(_) => {},
                Err(_) => error_out("Error saving encoded output file"),
            };
        },
        _ => error_out("Unsupported filetype"),
    }
}

fn dispatch_decode(mode: Option<&str>, source: &str, len: &str)
{
    let len = match len.parse::<usize>()
    {
        Ok(l) => l,
        Err(_) => error_out("len argument to decode is not a number"),
    };

    let dyimage = match open(source)
    {
        Ok(di) => di,
        Err(_) => error_out("Error opening source image for decoding"),
    };

    match dyimage
    {
        DynamicImage::ImageRgba8(image) =>
        {
            decode::<RgbaCodec>(image, mode, len);
        },
        DynamicImage::ImageRgb8(image) =>
        {
            decode::<RgbCodec>(image, mode, len);
        },
        _ => error_out("Unsupported filetype"),
    }
}

fn dispatch_estimate(mode: Option<&str>, source: &str)
{
    let dyimage = match open(source)
    {
        Ok(di) => di,
        Err(_) => error_out("Error opening source image for estimating"),
    };

    match dyimage
    {
        DynamicImage::ImageRgba8(image) =>
        {
            estimate::<RgbaCodec>(image, mode);
        },
        DynamicImage::ImageRgb8(image) =>
        {
            estimate::<RgbCodec>(image, mode);
        },
        _ => error_out("Unsupported filetype"),
    }
}

fn encode<C: Codec>(mut image: C::Input, mode: Option<&str>) -> C::Input
{
    let mode = if let Some(mode) = mode
    {
        match C::Mode::from_str(mode)
        {
            Ok(mode) => mode,
            Err(_) => error_out("Error parsing mode string"),
        }
    }
    else
    {
        C::Mode::default()
    };

    let rng = match StdRng::new()
    {
        Ok(r) => r,
        Err(_) => error_out("Error creating source of randomness"),
    };

    let mut payload = String::new();
    stdin().read_to_string(&mut payload).unwrap();

    C::encode(&mut image, payload.as_bytes(), mode, rng);

    image
}

fn decode<C: Codec>(image: C::Input, mode: Option<&str>, len: usize)
{
    let mode = if let Some(mode) = mode
    {
        match C::Mode::from_str(mode)
        {
            Ok(mode) => mode,
            Err(_) => error_out("Error parsing mode string"),
        }
    }
    else
    {
        C::Mode::default()
    };

    let mut buf = vec![0; len];

    C::decode(&image, &mut buf, len, mode);

    match stdout().write(&buf)
    {
        Ok(_) => {},
        Err(_) => error_out("Error writing decoded payload"),
    };
}

fn estimate<C: Codec>(image: C::Input, mode: Option<&str>)
{
    let mode = if let Some(mode) = mode
    {
        match C::Mode::from_str(mode)
        {
            Ok(mode) => mode,
            Err(_) => error_out("Error parsing mode string"),
        }
    }
    else
    {
        C::Mode::default()
    };

    match C::estimate(&image, mode)
    {
        Some(i) => println!("Estimate {} bytes", i),
        None => println!("Could not make an estimate"),
    }
}

fn error_out(msg: &str) -> !
{
    eprintln!("{}", msg);
    ::std::process::exit(1)
}
<commit_msg>Added gray alpha codec to frontend<commit_after>extern crate image;
extern crate clap;
extern crate rand;
extern crate itertools;

use clap::*;

mod codec;

mod rgba;
mod rgb;
mod gray_alpha;

mod utils;

fn main()
{
    let matches = App::new("stag")
        .version("0.0.1")
        .about("Image steganography")
        .subcommand(SubCommand::with_name("encode")
            .about("encodes a file")
            .arg(Arg::with_name("mode")
                 .short("m")
                 .long("mode")
                 .value_name("MODE")
                 .help("Set the encoding mode, default depends on SOURCE type")
                 .takes_value(true))
            .arg(Arg::with_name("SOURCE")
                 .help("The image source")
                 .index(1)
                 .required(true))
            .arg(Arg::with_name("OUTPUT")
                 .help("The output image")
                 .index(2)
                 .required(true)))
        .subcommand(SubCommand::with_name("decode")
            .about("decodes a file")
            .arg(Arg::with_name("mode")
                 .short("m")
                 .long("mode")
                 .value_name("MODE")
                 .help("Set the decoding mode, default depends on SOURCE type")
                 .takes_value(true))
            .arg(Arg::with_name("SOURCE")
                 .help("The image source")
                 .index(1)
                 .required(true))
            .arg(Arg::with_name("length")
                 .short("l")
                 .long("length")
                 .help("the amount of bytes to decode")
                 .value_name("LENGTH")
                 .takes_value(true)
                 .required(true)))
        .subcommand(SubCommand::with_name("estimate")
                .about("estimate how many bytes will fit into a file")
                .arg(Arg::with_name("SOURCE")
                     .help("The image source")
                     .index(1)
                     .takes_value(true))
                .arg(Arg::with_name("mode")
                     .short("m")
                     .long("mode")
                     .value_name("MODE")
                     .help("The encoding mode, default depends on SOURCE type")
                     .takes_value(true)))
        .get_matches();

    if let Some(matches) = matches.subcommand_matches("encode")
    {
        dispatch_encode(
            matches.value_of("mode"),
            matches.value_of("SOURCE").unwrap(),
            matches.value_of("OUTPUT").unwrap()
        );
    }

    if let Some(matches) = matches.subcommand_matches("decode")
    {
        dispatch_decode(
            matches.value_of("mode"),
            matches.value_of("SOURCE").unwrap(),
            matches.value_of("length").unwrap()
        );
    }

    if let Some(matches) = matches.subcommand_matches("estimate")
    {
        dispatch_estimate(
            matches.value_of("mode"),
            matches.value_of("SOURCE").unwrap()
        );
    }
}

use std::io::{stdin, stdout, Read, Write};
use std::str::FromStr;

use image::{open, DynamicImage};
use rand::StdRng;

use codec::Codec;
use rgba::RgbaCodec;
use rgb::RgbCodec;
use gray_alpha::GrayAlphaCodec;

fn dispatch_encode(mode: Option<&str>, source: &str, output: &str)
{
    let dyimage = match open(source)
    {
        Ok(di) => di,
        Err(_) => error_out("Error opening source image for encoding")
    };

    match dyimage
    {
        DynamicImage::ImageRgba8(image) =>
        {
            match encode::<RgbaCodec>(image, mode).save(output)
            {
                Ok(_) => {},
                Err(_) => error_out("Error saving encoded output file"),
            };
        },
        DynamicImage::ImageRgb8(image) =>
        {
            match encode::<RgbCodec>(image, mode).save(output)
            {
                Ok(_) => {},
                Err(_) => error_out("Error saving encoded output file"),
            };
        },
        DynamicImage::ImageLumaA8(image) =>
        {
            match encode::<GrayAlphaCodec>(image, mode).save(output)
            {
                Ok(_) => {},
                Err(_) => error_out("Error saving encoded output file"),
            };
        },
        _ => error_out("Unsupported filetype"),
    }
}

fn dispatch_decode(mode: Option<&str>, source: &str, len: &str)
{
    let len = match len.parse::<usize>()
    {
        Ok(l) => l,
        Err(_) => error_out("len argument to decode is not a number"),
    };

    let dyimage = match open(source)
    {
        Ok(di) => di,
        Err(_) => error_out("Error opening source image for decoding"),
    };

    match dyimage
    {
        DynamicImage::ImageRgba8(image) =>
        {
            decode::<RgbaCodec>(image, mode, len);
        },
        DynamicImage::ImageRgb8(image) =>
        {
            decode::<RgbCodec>(image, mode, len);
        },
        DynamicImage::ImageLumaA8(image) =>
        {
            decode::<GrayAlphaCodec>(image, mode, len);
        },
        _ => error_out("Unsupported filetype"),
    }
}

fn dispatch_estimate(mode: Option<&str>, source: &str)
{
    let dyimage = match open(source)
    {
        Ok(di) => di,
        Err(_) => error_out("Error opening source image for estimating"),
    };

    match dyimage
    {
        DynamicImage::ImageRgba8(image) =>
        {
            estimate::<RgbaCodec>(image, mode);
        },
        DynamicImage::ImageRgb8(image) =>
        {
            estimate::<RgbCodec>(image, mode);
        },
        DynamicImage::ImageLumaA8(image) =>
        {
            estimate::<GrayAlphaCodec>(image, mode);
        },
        _ => error_out("Unsupported filetype"),
    }
}

fn encode<C: Codec>(mut image: C::Input, mode: Option<&str>) -> C::Input
{
    let mode = if let Some(mode) = mode
    {
        match C::Mode::from_str(mode)
        {
            Ok(mode) => mode,
            Err(_) => error_out("Error parsing mode string"),
        }
    }
    else
    {
        C::Mode::default()
    };

    let rng = match StdRng::new()
    {
        Ok(r) => r,
        Err(_) => error_out("Error creating source of randomness"),
    };

    let mut payload = String::new();
    stdin().read_to_string(&mut payload).unwrap();

    C::encode(&mut image, payload.as_bytes(), mode, rng);

    image
}

fn decode<C: Codec>(image: C::Input, mode: Option<&str>, len: usize)
{
    let mode = if let Some(mode) = mode
    {
        match C::Mode::from_str(mode)
        {
            Ok(mode) => mode,
            Err(_) => error_out("Error parsing mode string"),
        }
    }
    else
    {
        C::Mode::default()
    };

    let mut buf = vec![0; len];

    C::decode(&image, &mut buf, len, mode);

    match stdout().write(&buf)
    {
        Ok(_) => {},
        Err(_) => error_out("Error writing decoded payload"),
    };
}

fn estimate<C: Codec>(image: C::Input, mode: Option<&str>)
{
    let mode = if let Some(mode) = mode
    {
        match C::Mode::from_str(mode)
        {
            Ok(mode) => mode,
            Err(_) => error_out("Error parsing mode string"),
        }
    }
    else
    {
        C::Mode::default()
    };

    match C::estimate(&image, mode)
    {
        Some(i) => println!("Estimate {} bytes", i),
        None => println!("Could not make an estimate"),
    }
}

fn error_out(msg: &str) -> !
{
    eprintln!("{}", msg);
    ::std::process::exit(1)
}
<|endoftext|>


<commit_before>extern crate libc;
extern crate termios;

use libc::{STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ, winsize};
use termios::{Termios, tcsetattr, TCSAFLUSH, VMIN, VTIME};
use termios::{BRKINT, ICRNL, INPCK, ISTRIP, IXON};
use termios::{OPOST, CS8};
use termios::{ECHO, ICANON, IEXTEN, ISIG};

use std::env;
use std::fs::File;
use std::io::{self, Stdin, Stdout, Read, BufRead, BufReader, Error, ErrorKind, Write};

const VERSION: &'static str = env!("CARGO_PKG_VERSION");

const TAB_STOP: usize = 8;

#[derive(PartialEq, Clone, Copy)]
enum Key {
    Character(u8),
    ArrowLeft,
    ArrowRight,
    ArrowUp,
    ArrowDown,
    Delete,
    Home,
    End,
    PageUp,
    PageDown,
}

macro_rules! ctrl_key {
    ($k:expr) => ($k & 0x1f);
}

struct RawMode {
    orig_term: Termios,
}

struct Row {
    chars: String,
    render: String,
}

struct Editor {
    _mode: RawMode,
    cx: usize,
    cy: usize,
    rx: usize,
    rowoff: usize,
    coloff: usize,
    screenrows: usize,
    screencols: usize,
    rows: Option<Vec<Row>>,
    stdin: Stdin,
    stdout: Stdout,
}

impl RawMode {
    fn enable_raw_mode() -> io::Result<RawMode> {
        let mut term = Termios::from_fd(STDIN_FILENO)?;
        let mode = RawMode { orig_term: term };

        term.c_iflag &= !(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
        term.c_oflag &= !OPOST;
        term.c_cflag |= CS8;
        term.c_lflag &= !(ECHO | ICANON | IEXTEN | ISIG);
        term.c_cc[VMIN] = 0;
        term.c_cc[VTIME] = 1;

        tcsetattr(STDIN_FILENO, TCSAFLUSH, &term)?;
        Ok(mode)
    }
}

impl Drop for RawMode {
    fn drop(&mut self) {
        tcsetattr(STDIN_FILENO, TCSAFLUSH, &self.orig_term).unwrap()
    }
}

impl Row {
    fn new<T>(s: T) -> Row
        where T: Into<String>
    {
        let chars = s.into();
        let mut idx = 0;
        let mut render = String::new();
        for ch in chars.chars() {
            if ch == '\t' {
                render.push(' ');
                idx += 1;
                while idx % TAB_STOP != 0 {
                    render.push(' ');
                    idx += 1;
                }
            } else {
                render.push(ch);
                idx += 1;
            }
        }
        Row { chars, render }
    }

    fn cx_to_rx(&self, cx: usize) -> usize {
        let mut rx = 0;
        for ch in self.chars.chars().take(cx) {
            if ch == '\t' {
                rx += (TAB_STOP - 1) - (rx % TAB_STOP);
            }
            rx += 1;
        }
        rx
    }
}

fn read_non_blocking<R: Read>(r: &mut R, buf: &mut [u8]) -> usize {
    r.read(buf)
        .or_else(|e| if e.kind() == ErrorKind::WouldBlock {
                     Ok(0)
                 } else {
                     Err(e)
                 })
        .expect("read_non_blocking")
}

fn byte_slice(s: &str, offset: usize, max_len: usize) -> &[u8] {
    if s.len() > max_len + offset {
        s[offset..(max_len + offset)].as_bytes()
    } else if s.len() > offset {
        s[offset..].as_bytes()
    } else {
        s[0..0].as_bytes()
    }
}

fn editor_read_key(stdin: &mut Stdin) -> Key {
    let mut buf = [0; 1];
    loop {
        let n = read_non_blocking(stdin, &mut buf);
        if n == 1 {
            if buf[0] == b'\x1b' {
                let mut seq = [0; 2];
                let n = read_non_blocking(stdin, &mut seq);
                if n == 2 && seq[0] == b'[' {
                    if seq[1] >= b'0' && seq[1] <= b'9' {
                        let mut last = [0; 1];
                        let n = read_non_blocking(stdin, &mut last);
                        if n == 1 && last[0] == b'~' {
                            return match seq[1] {
                                       b'1' | b'7' => Key::Home,
                                       b'3' => Key::Delete,
                                       b'4' | b'8' => Key::End,
                                       b'5' => Key::PageUp,
                                       b'6' => Key::PageDown,
                                       _ => Key::Character(b'\x1b'),
                                   };
                        }
                    } else {
                        return match seq[1] {
                                   b'A' => Key::ArrowUp,
                                   b'B' => Key::ArrowDown,
                                   b'C' => Key::ArrowRight,
                                   b'D' => Key::ArrowLeft,
                                   b'H' => Key::Home,
                                   b'F' => Key::End,
                                   _ => Key::Character(b'\x1b'),
                               };
                    }
                } else if n == 2 && seq[0] == b'O' {
                    return match seq[1] {
                               b'H' => Key::Home,
                               b'F' => Key::End,
                               _ => Key::Character(b'\x1b'),
                           };
                }
                return Key::Character(b'\x1b');
            } else {
                return Key::Character(buf[0]);
            }
        }
    }
}

fn get_window_size() -> io::Result<(u16, u16)> {
    let ws = winsize {
        ws_col: 0,
        ws_row: 0,
        ws_xpixel: 0,
        ws_ypixel: 0,
    };
    unsafe {
        if libc::ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0 {
            return Err(Error::new(ErrorKind::Other, "get_window_size: ioctl failed"));
        }
    }
    Ok((ws.ws_row, ws.ws_col))
}

impl Editor {
    fn new() -> io::Result<Editor> {
        let mode = RawMode::enable_raw_mode()?;
        let (rows, cols) = get_window_size()?;
        let stdin = io::stdin();
        let stdout = io::stdout();
        Ok(Editor {
               _mode: mode,
               cx: 0,
               cy: 0,
               rx: 0,
               rowoff: 0,
               coloff: 0,
               screenrows: rows as usize,
               screencols: cols as usize,
               rows: None,
               stdin: stdin,
               stdout: stdout,
           })
    }

    fn write(&mut self, buf: &[u8]) -> io::Result<()> {
        self.stdout.write_all(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.stdout.flush()
    }

    fn scroll(&mut self) {
        self.rx = self.cx;
        if self.cy < self.numrows() {
            self.rx = self.rows.as_ref().unwrap()[self.cy].cx_to_rx(self.cx);
        }

        if self.cy < self.rowoff {
            self.rowoff = self.cy;
        }
        if self.cy >= self.rowoff + self.screenrows {
            self.rowoff = self.cy - self.screenrows + 1;
        }
        if self.rx < self.coloff {
            self.coloff = self.rx;
        }
        if self.rx >= self.coloff + self.screencols {
            self.coloff = (1 + self.rx) - self.screencols;
        }
    }

    fn numrows(&self) -> usize {
        self.rows.as_ref().map_or(0, |v| v.len())
    }

    fn draw_rows(&mut self) -> io::Result<()> {
        let numrows = self.numrows();
        for y in 0..(self.screenrows) {
            let filerow = y + self.rowoff;
            if filerow >= numrows {
                if self.rows.is_none() && y == self.screenrows / 3 {
                    let mut msg = format!("Kilo-rs editor -- version {}", VERSION);
                    msg.truncate(self.screencols);
                    let padding = (self.screencols - msg.len()) / 2;
                    if padding > 0 {
                        self.write(b"~")?;
                        for _ in 1..padding {
                            self.write(b" ")?;
                        }
                    }
                    self.write(msg.as_bytes())?;
                } else {
                    self.write(b"~")?;
                }
            } else {
                let rows = self.rows.as_ref().unwrap();
                self.stdout
                    .write_all(byte_slice(&rows[filerow].render, self.coloff, self.screencols))?;
            }

            self.write(b"\x1b[K")?;
            if y < self.screenrows - 1 {
                self.write(b"\r\n")?;
            }
        }
        Ok(())
    }

    fn refresh_screen(&mut self) -> io::Result<()> {
        self.scroll();

        self.write(b"\x1b[?25l")?;
        self.write(b"\x1b[H")?;

        self.draw_rows()?;

        let move_cursor = format!("\x1b[{};{}H",
                                  (self.cy - self.rowoff) + 1,
                                  (self.rx - self.coloff) + 1)
                .into_bytes();
        self.write(&move_cursor)?;

        self.write(b"\x1b[?25h")?;
        self.flush()
    }

    fn rowlen(&self, index: usize) -> usize {
        let row = self.rows.as_ref().and_then(|r| r.get(index));
        row.map_or(0, |r| r.chars.len())
    }

    fn move_cursor(&mut self, k: Key) {
        match k {
            Key::ArrowUp => {
                if self.cy > 0 {
                    self.cy -= 1;
                }
            }
            Key::ArrowDown => {
                if self.cy < self.numrows() {
                    self.cy += 1;
                }
            }
            Key::ArrowLeft => {
                if self.cx > 0 {
                    self.cx -= 1;
                } else if self.cy > 0 {
                    self.cy -= 1;
                    self.cx = self.rowlen(self.cy);
                }
            }
            Key::ArrowRight => {
                let row = self.rows.as_ref().and_then(|r| r.get(self.cy));
                let rowlen = row.map_or(0, |r| r.chars.len());
                if self.cx < rowlen {
                    self.cx += 1;
                } else if row.is_some() && self.cx == rowlen {
                    self.cx = 0;
                    self.cy += 1;
                }
            }
            _ => (),
        }

        let rowlen = self.rowlen(self.cy);
        if self.cx > rowlen {
            self.cx = rowlen;
        }
    }

    fn process_keypress(&mut self) -> bool {
        let c = editor_read_key(&mut self.stdin);

        match c {
            Key::Character(k) if k == ctrl_key!(b'q') => return false,
            Key::Home => {
                self.cx = 0;
            }
            Key::End => {
                self.cx = self.screencols - 1;
            }
            Key::PageUp | Key::PageDown => {
                let key = if c == Key::PageUp {
                    Key::ArrowUp
                } else {
                    Key::ArrowDown
                };
                for _ in 0..(self.screenrows) {
                    self.move_cursor(key);
                }
            }
            Key::ArrowUp | Key::ArrowDown | Key::ArrowLeft | Key::ArrowRight => {
                self.move_cursor(c);
            }
            _ => (),
        };
        true
    }

    fn open(&mut self, filename: &str) -> io::Result<()> {
        let f = File::open(filename)?;
        let file = BufReader::new(&f);
        let results: io::Result<Vec<Row>> = file.lines().map(|r| r.map(Row::new)).collect();
        self.rows = Some(results?);
        Ok(())
    }
}

impl Drop for Editor {
    fn drop(&mut self) {
        // clear screen
        self.stdout.write_all(b"\x1b[2J").unwrap();
        self.stdout.write_all(b"\x1b[H").unwrap();
        self.stdout.flush().unwrap();
    }
}

fn main() {
    let mut editor = Editor::new().unwrap();
    if let Some(filename) = env::args().nth(1) {
        editor.open(&filename).unwrap();
    }

    editor.refresh_screen().unwrap();
    while editor.process_keypress() {
        editor.refresh_screen().unwrap();
    }
}
<commit_msg>Scrolling with Page Up and Page Down<commit_after>extern crate libc;
extern crate termios;

use libc::{STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ, winsize};
use termios::{Termios, tcsetattr, TCSAFLUSH, VMIN, VTIME};
use termios::{BRKINT, ICRNL, INPCK, ISTRIP, IXON};
use termios::{OPOST, CS8};
use termios::{ECHO, ICANON, IEXTEN, ISIG};

use std::cmp;
use std::env;
use std::fs::File;
use std::io::{self, Stdin, Stdout, Read, BufRead, BufReader, Error, ErrorKind, Write};

const VERSION: &'static str = env!("CARGO_PKG_VERSION");

const TAB_STOP: usize = 8;

#[derive(PartialEq, Clone, Copy)]
enum Key {
    Character(u8),
    ArrowLeft,
    ArrowRight,
    ArrowUp,
    ArrowDown,
    Delete,
    Home,
    End,
    PageUp,
    PageDown,
}

macro_rules! ctrl_key {
    ($k:expr) => ($k & 0x1f);
}

struct RawMode {
    orig_term: Termios,
}

struct Row {
    chars: String,
    render: String,
}

struct Editor {
    _mode: RawMode,
    cx: usize,
    cy: usize,
    rx: usize,
    rowoff: usize,
    coloff: usize,
    screenrows: usize,
    screencols: usize,
    rows: Option<Vec<Row>>,
    stdin: Stdin,
    stdout: Stdout,
}

impl RawMode {
    fn enable_raw_mode() -> io::Result<RawMode> {
        let mut term = Termios::from_fd(STDIN_FILENO)?;
        let mode = RawMode { orig_term: term };

        term.c_iflag &= !(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
        term.c_oflag &= !OPOST;
        term.c_cflag |= CS8;
        term.c_lflag &= !(ECHO | ICANON | IEXTEN | ISIG);
        term.c_cc[VMIN] = 0;
        term.c_cc[VTIME] = 1;

        tcsetattr(STDIN_FILENO, TCSAFLUSH, &term)?;
        Ok(mode)
    }
}

impl Drop for RawMode {
    fn drop(&mut self) {
        tcsetattr(STDIN_FILENO, TCSAFLUSH, &self.orig_term).unwrap()
    }
}

impl Row {
    fn new<T>(s: T) -> Row
        where T: Into<String>
    {
        let chars = s.into();
        let mut idx = 0;
        let mut render = String::new();
        for ch in chars.chars() {
            if ch == '\t' {
                render.push(' ');
                idx += 1;
                while idx % TAB_STOP != 0 {
                    render.push(' ');
                    idx += 1;
                }
            } else {
                render.push(ch);
                idx += 1;
            }
        }
        Row { chars, render }
    }

    fn cx_to_rx(&self, cx: usize) -> usize {
        let mut rx = 0;
        for ch in self.chars.chars().take(cx) {
            if ch == '\t' {
                rx += (TAB_STOP - 1) - (rx % TAB_STOP);
            }
            rx += 1;
        }
        rx
    }
}

fn read_non_blocking<R: Read>(r: &mut R, buf: &mut [u8]) -> usize {
    r.read(buf)
        .or_else(|e| if e.kind() == ErrorKind::WouldBlock {
                     Ok(0)
                 } else {
                     Err(e)
                 })
        .expect("read_non_blocking")
}

fn byte_slice(s: &str, offset: usize, max_len: usize) -> &[u8] {
    if s.len() > max_len + offset {
        s[offset..(max_len + offset)].as_bytes()
    } else if s.len() > offset {
        s[offset..].as_bytes()
    } else {
        s[0..0].as_bytes()
    }
}

fn editor_read_key(stdin: &mut Stdin) -> Key {
    let mut buf = [0; 1];
    loop {
        let n = read_non_blocking(stdin, &mut buf);
        if n == 1 {
            if buf[0] == b'\x1b' {
                let mut seq = [0; 2];
                let n = read_non_blocking(stdin, &mut seq);
                if n == 2 && seq[0] == b'[' {
                    if seq[1] >= b'0' && seq[1] <= b'9' {
                        let mut last = [0; 1];
                        let n = read_non_blocking(stdin, &mut last);
                        if n == 1 && last[0] == b'~' {
                            return match seq[1] {
                                       b'1' | b'7' => Key::Home,
                                       b'3' => Key::Delete,
                                       b'4' | b'8' => Key::End,
                                       b'5' => Key::PageUp,
                                       b'6' => Key::PageDown,
                                       _ => Key::Character(b'\x1b'),
                                   };
                        }
                    } else {
                        return match seq[1] {
                                   b'A' => Key::ArrowUp,
                                   b'B' => Key::ArrowDown,
                                   b'C' => Key::ArrowRight,
                                   b'D' => Key::ArrowLeft,
                                   b'H' => Key::Home,
                                   b'F' => Key::End,
                                   _ => Key::Character(b'\x1b'),
                               };
                    }
                } else if n == 2 && seq[0] == b'O' {
                    return match seq[1] {
                               b'H' => Key::Home,
                               b'F' => Key::End,
                               _ => Key::Character(b'\x1b'),
                           };
                }
                return Key::Character(b'\x1b');
            } else {
                return Key::Character(buf[0]);
            }
        }
    }
}

fn get_window_size() -> io::Result<(u16, u16)> {
    let ws = winsize {
        ws_col: 0,
        ws_row: 0,
        ws_xpixel: 0,
        ws_ypixel: 0,
    };
    unsafe {
        if libc::ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0 {
            return Err(Error::new(ErrorKind::Other, "get_window_size: ioctl failed"));
        }
    }
    Ok((ws.ws_row, ws.ws_col))
}

impl Editor {
    fn new() -> io::Result<Editor> {
        let mode = RawMode::enable_raw_mode()?;
        let (rows, cols) = get_window_size()?;
        let stdin = io::stdin();
        let stdout = io::stdout();
        Ok(Editor {
               _mode: mode,
               cx: 0,
               cy: 0,
               rx: 0,
               rowoff: 0,
               coloff: 0,
               screenrows: rows as usize,
               screencols: cols as usize,
               rows: None,
               stdin: stdin,
               stdout: stdout,
           })
    }

    fn write(&mut self, buf: &[u8]) -> io::Result<()> {
        self.stdout.write_all(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.stdout.flush()
    }

    fn scroll(&mut self) {
        self.rx = self.cx;
        if self.cy < self.numrows() {
            self.rx = self.rows.as_ref().unwrap()[self.cy].cx_to_rx(self.cx);
        }

        if self.cy < self.rowoff {
            self.rowoff = self.cy;
        }
        if self.cy >= self.rowoff + self.screenrows {
            self.rowoff = self.cy - self.screenrows + 1;
        }
        if self.rx < self.coloff {
            self.coloff = self.rx;
        }
        if self.rx >= self.coloff + self.screencols {
            self.coloff = (1 + self.rx) - self.screencols;
        }
    }

    fn numrows(&self) -> usize {
        self.rows.as_ref().map_or(0, |v| v.len())
    }

    fn draw_rows(&mut self) -> io::Result<()> {
        let numrows = self.numrows();
        for y in 0..(self.screenrows) {
            let filerow = y + self.rowoff;
            if filerow >= numrows {
                if self.rows.is_none() && y == self.screenrows / 3 {
                    let mut msg = format!("Kilo-rs editor -- version {}", VERSION);
                    msg.truncate(self.screencols);
                    let padding = (self.screencols - msg.len()) / 2;
                    if padding > 0 {
                        self.write(b"~")?;
                        for _ in 1..padding {
                            self.write(b" ")?;
                        }
                    }
                    self.write(msg.as_bytes())?;
                } else {
                    self.write(b"~")?;
                }
            } else {
                let rows = self.rows.as_ref().unwrap();
                self.stdout
                    .write_all(byte_slice(&rows[filerow].render, self.coloff, self.screencols))?;
            }

            self.write(b"\x1b[K")?;
            if y < self.screenrows - 1 {
                self.write(b"\r\n")?;
            }
        }
        Ok(())
    }

    fn refresh_screen(&mut self) -> io::Result<()> {
        self.scroll();

        self.write(b"\x1b[?25l")?;
        self.write(b"\x1b[H")?;

        self.draw_rows()?;

        let move_cursor = format!("\x1b[{};{}H",
                                  (self.cy - self.rowoff) + 1,
                                  (self.rx - self.coloff) + 1)
                .into_bytes();
        self.write(&move_cursor)?;

        self.write(b"\x1b[?25h")?;
        self.flush()
    }

    fn rowlen(&self, index: usize) -> usize {
        let row = self.rows.as_ref().and_then(|r| r.get(index));
        row.map_or(0, |r| r.chars.len())
    }

    fn move_cursor(&mut self, k: Key) {
        match k {
            Key::ArrowUp => {
                if self.cy > 0 {
                    self.cy -= 1;
                }
            }
            Key::ArrowDown => {
                if self.cy < self.numrows() {
                    self.cy += 1;
                }
            }
            Key::ArrowLeft => {
                if self.cx > 0 {
                    self.cx -= 1;
                } else if self.cy > 0 {
                    self.cy -= 1;
                    self.cx = self.rowlen(self.cy);
                }
            }
            Key::ArrowRight => {
                let row = self.rows.as_ref().and_then(|r| r.get(self.cy));
                let rowlen = row.map_or(0, |r| r.chars.len());
                if self.cx < rowlen {
                    self.cx += 1;
                } else if row.is_some() && self.cx == rowlen {
                    self.cx = 0;
                    self.cy += 1;
                }
            }
            _ => (),
        }

        let rowlen = self.rowlen(self.cy);
        if self.cx > rowlen {
            self.cx = rowlen;
        }
    }

    fn process_keypress(&mut self) -> bool {
        let c = editor_read_key(&mut self.stdin);

        match c {
            Key::Character(k) if k == ctrl_key!(b'q') => return false,
            Key::Home => {
                self.cx = 0;
            }
            Key::End => {
                self.cx = self.screencols - 1;
            }
            Key::PageUp | Key::PageDown => {
                let key = if c == Key::PageUp {
                    self.cy = self.rowoff;
                    Key::ArrowUp
                } else {
                    self.cy = cmp::min(self.rowoff + self.screenrows - 1, self.numrows());
                    Key::ArrowDown
                };
                for _ in 0..(self.screenrows) {
                    self.move_cursor(key);
                }
            }
            Key::ArrowUp | Key::ArrowDown | Key::ArrowLeft | Key::ArrowRight => {
                self.move_cursor(c);
            }
            _ => (),
        };
        true
    }

    fn open(&mut self, filename: &str) -> io::Result<()> {
        let f = File::open(filename)?;
        let file = BufReader::new(&f);
        let results: io::Result<Vec<Row>> = file.lines().map(|r| r.map(Row::new)).collect();
        self.rows = Some(results?);
        Ok(())
    }
}

impl Drop for Editor {
    fn drop(&mut self) {
        // clear screen
        self.stdout.write_all(b"\x1b[2J").unwrap();
        self.stdout.write_all(b"\x1b[H").unwrap();
        self.stdout.flush().unwrap();
    }
}

fn main() {
    let mut editor = Editor::new().unwrap();
    if let Some(filename) = env::args().nth(1) {
        editor.open(&filename).unwrap();
    }

    editor.refresh_screen().unwrap();
    while editor.process_keypress() {
        editor.refresh_screen().unwrap();
    }
}
<|endoftext|>


<commit_before>extern crate chrono;
extern crate libc;

fn main() {
    // TODO: argument parsing
    let countdown = Countdown::default();
    println!("Countdown Initiated!");
    for mark in countdown {
        match mark {
            Mark::Minute(t) => {
                println!("T minus {} minutes and counting...", t);
            },
            Mark::Second(t) => {
                println!("{}...", t);
            },
            Mark::Liftoff(s) => {
                println!("{}", s);
            }
        }
    }
}

type UTCTime = chrono::datetime::DateTime<chrono::offset::utc::UTC>;

struct Countdown {
    /// The power word used to end the countdown
    liftoff: String,
    /// When the countdown ends
    deadline: UTCTime,
    /// Final countdown
    countdown: i8
}

enum Mark {
    Minute(UTCTime),
    Second(UTCTime),
    Liftoff(String)
}

/// Default is: 3...2...1...Go!
impl Default for Countdown {
    fn default() -> Countdown {
        let t = chrono::offset::utc::UTC::now()
              + chrono::duration::Duration::seconds(3);
        Countdown { liftoff: "Go!".to_string(), deadline: t, countdown: 3 }
    }
}

impl Iterator for Countdown {
    type Item = Mark;

    fn next(&mut self) -> Option<Mark> {
        type SecondsAndNanos = libc::types::os::common::posix01::timespec;

        let req = SecondsAndNanos{tv_sec: 0, tv_nsec: 0};
        let mut rem = SecondsAndNanos{tv_sec: 0, tv_nsec: 0};

        unsafe { libc::funcs::posix88::unistd::nanosleep(&req, &mut rem); }
        Some(Mark::Liftoff(self.liftoff.clone()))
    }
}
<commit_msg>Implement remainder of timer<commit_after>extern crate chrono;
extern crate libc;

use chrono::Timelike;

fn main() {
    // TODO: argument parsing
    let countdown = Countdown::default();
    println!("Countdown Initiated!");
    for mark in countdown {
        match mark {
            Mark::Minute(t) => {
                println!("T minus {} minutes and counting...", t);
            },
            Mark::Second(t) => {
                println!("{}...", t);
            },
            Mark::Liftoff(s) => {
                println!("{}", s);
            }
        }
    }
}

type UTCTime = chrono::datetime::DateTime<chrono::offset::utc::UTC>;

struct Countdown {
    /// The power word used to end the countdown
    liftoff: String,
    /// When the countdown ends
    deadline: UTCTime,
    /// Final countdown
    countdown: i8
}

enum Mark {
    Minute(u32),
    Second(u32),
    Liftoff(String)
}

/// Default is: 3...2...1...Go!
impl Default for Countdown {
    fn default() -> Countdown {
        let t = chrono::offset::utc::UTC::now()
              + chrono::duration::Duration::minutes(2);
        Countdown { liftoff: "Go!".to_string(), deadline: t, countdown: 3 }
    }
}

impl Iterator for Countdown {
    type Item = Mark;

    fn next(&mut self) -> Option<Mark> {
        type SecondsAndNanos = libc::types::os::common::posix01::timespec;

        let req = SecondsAndNanos{tv_sec: 1, tv_nsec: 0};
        let mut rem = SecondsAndNanos{tv_sec: 0, tv_nsec: 0};

        loop {
            let t = chrono::offset::utc::UTC::now();
            unsafe { libc::funcs::posix88::unistd::nanosleep(&req, &mut rem); }

            if t.timestamp() == self.deadline.timestamp() {
                return Some(Mark::Liftoff(self.liftoff.clone()));
            } else if t > self.deadline {
                return None
            } else if t.time().second() == self.deadline.time().second() &&
                      t.time().minute() < self.deadline.time().minute() {
                let m = self.deadline.time().minute() - t.time().minute();
                return Some(Mark::Minute(m));
            } else if self.deadline.minute() == t.time().minute() &&
                      self.deadline.time().second() > t.time().second() &&
                      self.deadline.time().second() - t.time().second() <= self.countdown as u32 {
                let s = self.deadline.time().second() - t.time().second();
                return Some(Mark::Second(s));
            }
        }
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate log;
extern crate env_logger;
extern crate irc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate hyper;
extern crate hubcaps;
extern crate hyper_native_tls;
extern crate serde_json;

use std::fmt;
use std::thread;
use std::collections::HashMap;
use regex::Regex;

use irc::client::prelude::*;

use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use hubcaps::{Credentials, Github};
use hubcaps::comments::CommentOptions;

fn main() {
    env_logger::init().unwrap();

    let server = IrcServer::new("config.json").unwrap();
    server.identify().unwrap();

    let empty_map = HashMap::new();
    let options = if let Some(ref opts) = server.config().options {
        opts
    } else {
        &empty_map
    };

    // FIXME: Eventually this should support multiple channels, plus
    // options to ask the bot which channels it's in, and which channels
    // it currently has buffers in.  (Then we can do things like ask the
    // bot to reboot itself, but it will only do so if it's not busy.)
    let mut channel_data = ChannelData::new(&options);

    for message in server.iter() {
        let message = message.unwrap(); // panic if there's an error

        match message.command {
            Command::PRIVMSG(ref target, ref msg) => {
                match message.source_nickname() {
                    None => {
                        // FIXME: trailing \n
                        warn!("PRIVMSG without a source! {}", message);
                    }
                    Some(ref source) => {
                        let mynick = server.current_nickname();
                        if target == mynick {
                            handle_bot_command(&server, msg, source, None)
                        } else if target.starts_with('#') {
                            let source_ = String::from(*source);
                            let line = if msg.starts_with("\x01ACTION ") && msg.ends_with("\x01") {
                                ChannelLine {
                                    source: source_,
                                    is_action: true,
                                    message: String::from(&msg[8..msg.len() - 1]),
                                }
                            } else {
                                ChannelLine {
                                    source: source_,
                                    is_action: false,
                                    message: msg.clone(),
                                }
                            };

                            // FIXME: This needs to handle requests in /me
                            match check_command_in_channel(mynick, msg) {
                                Some(ref command) => {
                                    handle_bot_command(&server, command, target, Some(source))
                                }
                                None => {
                                    if let Some(response) = channel_data.add_line(line) {
                                        server.send_privmsg(target, &*response).unwrap();
                                    }
                                }
                            }
                        } else {
                            // FIXME: trailing \n
                            warn!("UNEXPECTED TARGET {} in message {}", target, message);
                        }
                    }
                }
            }
            _ => (),
        }
    }
}

// Take a message in the channel, and see if it was a message sent to
// this bot.
fn check_command_in_channel(mynick: &str, msg: &String) -> Option<String> {
    if !msg.starts_with(mynick) {
        return None;
    }
    let after_nick = &msg[mynick.len()..];
    if !after_nick.starts_with(":") && !after_nick.starts_with(",") {
        return None;
    }
    let after_punct = &after_nick[1..];
    Some(String::from(after_punct.trim_left()))
}

fn handle_bot_command(server: &IrcServer,
                      command: &str,
                      response_target: &str,
                      response_username: Option<&str>) {

    let send_line = |response_username: Option<&str>, line: &str| {
        let adjusted_line = match response_username {
            None => String::from(line),
            Some(username) => String::from(username) + ", " + line,
        };
        server
            .send_privmsg(response_target, &adjusted_line)
            .unwrap();
    };

    if command == "help" {
        send_line(response_username, "The commands I understand are:");
        send_line(None, "  help     Send this message.");
        return;
    }

    send_line(response_username,
              "Sorry, I don't understand that command.  Try 'help'.");
}

struct ChannelLine {
    source: String,
    is_action: bool,
    message: String,
}

struct TopicData {
    topic: String,
    github_url: Option<String>,
    lines: Vec<ChannelLine>,
}

struct ChannelData<'opts> {
    current_topic: Option<TopicData>,
    options: &'opts HashMap<String, String>,
}

impl fmt::Display for ChannelLine {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.is_action {
            write!(f, "* {} {}", self.source, self.message)
        } else {
            write!(f, "<{}> {}", self.source, self.message)
        }
    }
}

impl TopicData {
    fn new(topic: &str) -> TopicData {
        let topic_ = String::from(topic);
        TopicData {
            topic: topic_,
            github_url: None,
            lines: vec![],
        }
    }
}

impl fmt::Display for TopicData {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        try!(write!(f, "The CSS Working Group just discussed {}.\n\n", self.topic));
        try!(write!(f, "The full IRC log of that discussion\n"));
        try!(write!(f, "\n```\n"));
        for line in &self.lines {
            try!(write!(f, "{}\n", line));
        }
        try!(write!(f, "```\n\n"));
        Ok(())
    }
}

fn ci_starts_with(s: &str, prefix: &str) -> bool {
    assert!(prefix.to_lowercase() == prefix);

    s.len() >= prefix.len() && s[0..prefix.len()].to_lowercase() == prefix
}

fn strip_ci_prefix(s: &str, prefix: &str) -> Option<String> {
    if ci_starts_with(s, prefix) {
        Some(String::from(s[prefix.len()..].trim_left()))
    } else {
        None
    }
}

impl<'opts> ChannelData<'opts> {
    fn new(options_: &'opts HashMap<String, String>) -> ChannelData {
        ChannelData {
            current_topic: None,
            options: options_,
        }
    }

    // Returns the response that should be sent to the message over IRC.
    fn add_line(&mut self, line: ChannelLine) -> Option<String> {
        if let Some(ref topic) = strip_ci_prefix(&line.message, "topic:") {
            self.start_topic(topic);
        }
        if line.source == "trackbot" && line.is_action == true &&
           line.message == "is ending a teleconference." {
            self.end_topic();
        }
        match self.current_topic {
            None => None,
            Some(ref mut data) => {
                let new_url_option = extract_github_url(&line.message, self.options);
                let response = match (new_url_option.as_ref(), data.github_url.as_ref()) {
                    (None, _) => None,
                    // FIXME: Add and implement instructions to cancel!
                    (Some(new_url), None) => {
                        Some(format!("OK, I'll post this discussion to {}", new_url))
                    }
                    (Some(new_url), Some(old_url)) if old_url == new_url => None,
                    (Some(new_url), Some(old_url)) => {
                        Some(format!("OK, I'll post this discussion to {} instead of {} like you said before",
                                     new_url,
                                     old_url))
                    }
                };

                if let Some(new_url) = new_url_option {
                    data.github_url = Some(new_url);
                }

                data.lines.push(line);

                response
            }
        }
    }

    fn start_topic(&mut self, topic: &str) {
        if self.current_topic.is_some() {
            self.end_topic();
        }

        self.current_topic = Some(TopicData::new(topic));
    }

    fn end_topic(&mut self) {
        // TODO: Test the topic boundary code.
        if let Some(topic) = self.current_topic.take() {
            if topic.github_url.is_some() {
                let task = GithubCommentTask::new(topic, self.options);
                task.run();
            }
        }
    }
}

fn extract_github_url(message: &str, options: &HashMap<String, String>) -> Option<String> {
    lazy_static! {
        static ref GITHUB_URL_RE: Regex =
            Regex::new(r"^https://github.com/(?P<repo>[^/]*/[^/]*)/issues/(?P<number>[0-9]+)$")
            .unwrap();
    }
    let ref allowed_repos = options["github_repos_allowed"];
    for prefix in ["topic:", "github topic:"].into_iter() {
        if let Some(ref maybe_url) = strip_ci_prefix(&message, prefix) {
            if let Some(ref caps) = GITHUB_URL_RE.captures(maybe_url) {
                for repo in allowed_repos.split_whitespace() {
                    if caps["repo"] == *repo {
                        return Some(maybe_url.clone());
                    }
                }
            }
        }
    }
    None
}

struct GithubCommentTask {
    data: TopicData,
    github: Github,
}

impl GithubCommentTask {
    fn new(data_: TopicData, options: &HashMap<String, String>) -> GithubCommentTask {
        let github_ =
            Github::new(&*options["github_uastring"],
                        Client::with_connector(HttpsConnector::new(NativeTlsClient::new()
                                                                       .unwrap())),
                        Credentials::Token(options["github_access_token"].clone()));
        GithubCommentTask {
            data: data_,
            github: github_,
        }
    }
    fn run(self) {
        thread::spawn(move || { self.main(); });
    }
    fn main(&self) {
        lazy_static! {
            static ref GITHUB_URL_RE: Regex =
                Regex::new(r"^https://github.com/(?P<owner>[^/]*)/(?P<repo>[^/]*)/issues/(?P<number>[0-9]+)$")
                .unwrap();
        }

        if let Some(ref github_url) = self.data.github_url {
            if let Some(ref caps) = GITHUB_URL_RE.captures(github_url) {
                let repo = self.github
                    .repo(String::from(&caps["owner"]), String::from(&caps["repo"]));
                let issue = repo.issue(caps["number"].parse::<u64>().unwrap());
                let comments = issue.comments();

                let comment_text = format!("{}", self.data);
                comments.create(&CommentOptions { body: comment_text });
            } else {
                warn!("How does {} fail to match now when it matched before?",
                      github_url)
            }
        }
    }
}
<commit_msg>Forgot to 'cargo fmt'.<commit_after>#[macro_use]
extern crate log;
extern crate env_logger;
extern crate irc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate hyper;
extern crate hubcaps;
extern crate hyper_native_tls;
extern crate serde_json;

use std::fmt;
use std::thread;
use std::collections::HashMap;
use regex::Regex;

use irc::client::prelude::*;

use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use hubcaps::{Credentials, Github};
use hubcaps::comments::CommentOptions;

fn main() {
    env_logger::init().unwrap();

    let server = IrcServer::new("config.json").unwrap();
    server.identify().unwrap();

    let empty_map = HashMap::new();
    let options = if let Some(ref opts) = server.config().options {
        opts
    } else {
        &empty_map
    };

    // FIXME: Eventually this should support multiple channels, plus
    // options to ask the bot which channels it's in, and which channels
    // it currently has buffers in.  (Then we can do things like ask the
    // bot to reboot itself, but it will only do so if it's not busy.)
    let mut channel_data = ChannelData::new(&options);

    for message in server.iter() {
        let message = message.unwrap(); // panic if there's an error

        match message.command {
            Command::PRIVMSG(ref target, ref msg) => {
                match message.source_nickname() {
                    None => {
                        // FIXME: trailing \n
                        warn!("PRIVMSG without a source! {}", message);
                    }
                    Some(ref source) => {
                        let mynick = server.current_nickname();
                        if target == mynick {
                            handle_bot_command(&server, msg, source, None)
                        } else if target.starts_with('#') {
                            let source_ = String::from(*source);
                            let line = if msg.starts_with("\x01ACTION ") && msg.ends_with("\x01") {
                                ChannelLine {
                                    source: source_,
                                    is_action: true,
                                    message: String::from(&msg[8..msg.len() - 1]),
                                }
                            } else {
                                ChannelLine {
                                    source: source_,
                                    is_action: false,
                                    message: msg.clone(),
                                }
                            };

                            // FIXME: This needs to handle requests in /me
                            match check_command_in_channel(mynick, msg) {
                                Some(ref command) => {
                                    handle_bot_command(&server, command, target, Some(source))
                                }
                                None => {
                                    if let Some(response) = channel_data.add_line(line) {
                                        server.send_privmsg(target, &*response).unwrap();
                                    }
                                }
                            }
                        } else {
                            // FIXME: trailing \n
                            warn!("UNEXPECTED TARGET {} in message {}", target, message);
                        }
                    }
                }
            }
            _ => (),
        }
    }
}

// Take a message in the channel, and see if it was a message sent to
// this bot.
fn check_command_in_channel(mynick: &str, msg: &String) -> Option<String> {
    if !msg.starts_with(mynick) {
        return None;
    }
    let after_nick = &msg[mynick.len()..];
    if !after_nick.starts_with(":") && !after_nick.starts_with(",") {
        return None;
    }
    let after_punct = &after_nick[1..];
    Some(String::from(after_punct.trim_left()))
}

fn handle_bot_command(server: &IrcServer,
                      command: &str,
                      response_target: &str,
                      response_username: Option<&str>) {

    let send_line = |response_username: Option<&str>, line: &str| {
        let adjusted_line = match response_username {
            None => String::from(line),
            Some(username) => String::from(username) + ", " + line,
        };
        server
            .send_privmsg(response_target, &adjusted_line)
            .unwrap();
    };

    if command == "help" {
        send_line(response_username, "The commands I understand are:");
        send_line(None, "  help     Send this message.");
        return;
    }

    send_line(response_username,
              "Sorry, I don't understand that command.  Try 'help'.");
}

struct ChannelLine {
    source: String,
    is_action: bool,
    message: String,
}

struct TopicData {
    topic: String,
    github_url: Option<String>,
    lines: Vec<ChannelLine>,
}

struct ChannelData<'opts> {
    current_topic: Option<TopicData>,
    options: &'opts HashMap<String, String>,
}

impl fmt::Display for ChannelLine {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.is_action {
            write!(f, "* {} {}", self.source, self.message)
        } else {
            write!(f, "<{}> {}", self.source, self.message)
        }
    }
}

impl TopicData {
    fn new(topic: &str) -> TopicData {
        let topic_ = String::from(topic);
        TopicData {
            topic: topic_,
            github_url: None,
            lines: vec![],
        }
    }
}

impl fmt::Display for TopicData {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        try!(write!(f,
                    "The CSS Working Group just discussed {}.\n\n",
                    self.topic));
        try!(write!(f,
                    "The full IRC log of that discussion\n"));
        try!(write!(f, "\n```\n"));
        for line in &self.lines {
            try!(write!(f, "{}\n", line));
        }
        try!(write!(f, "```\n\n"));
        Ok(())
    }
}

fn ci_starts_with(s: &str, prefix: &str) -> bool {
    assert!(prefix.to_lowercase() == prefix);

    s.len() >= prefix.len() && s[0..prefix.len()].to_lowercase() == prefix
}

fn strip_ci_prefix(s: &str, prefix: &str) -> Option<String> {
    if ci_starts_with(s, prefix) {
        Some(String::from(s[prefix.len()..].trim_left()))
    } else {
        None
    }
}

impl<'opts> ChannelData<'opts> {
    fn new(options_: &'opts HashMap<String, String>) -> ChannelData {
        ChannelData {
            current_topic: None,
            options: options_,
        }
    }

    // Returns the response that should be sent to the message over IRC.
    fn add_line(&mut self, line: ChannelLine) -> Option<String> {
        if let Some(ref topic) = strip_ci_prefix(&line.message, "topic:") {
            self.start_topic(topic);
        }
        if line.source == "trackbot" && line.is_action == true &&
           line.message == "is ending a teleconference." {
            self.end_topic();
        }
        match self.current_topic {
            None => None,
            Some(ref mut data) => {
                let new_url_option = extract_github_url(&line.message, self.options);
                let response = match (new_url_option.as_ref(), data.github_url.as_ref()) {
                    (None, _) => None,
                    // FIXME: Add and implement instructions to cancel!
                    (Some(new_url), None) => {
                        Some(format!("OK, I'll post this discussion to {}", new_url))
                    }
                    (Some(new_url), Some(old_url)) if old_url == new_url => None,
                    (Some(new_url), Some(old_url)) => {
                        Some(format!("OK, I'll post this discussion to {} instead of {} like you said before",
                                     new_url,
                                     old_url))
                    }
                };

                if let Some(new_url) = new_url_option {
                    data.github_url = Some(new_url);
                }

                data.lines.push(line);

                response
            }
        }
    }

    fn start_topic(&mut self, topic: &str) {
        if self.current_topic.is_some() {
            self.end_topic();
        }

        self.current_topic = Some(TopicData::new(topic));
    }

    fn end_topic(&mut self) {
        // TODO: Test the topic boundary code.
        if let Some(topic) = self.current_topic.take() {
            if topic.github_url.is_some() {
                let task = GithubCommentTask::new(topic, self.options);
                task.run();
            }
        }
    }
}

fn extract_github_url(message: &str, options: &HashMap<String, String>) -> Option<String> {
    lazy_static! {
        static ref GITHUB_URL_RE: Regex =
            Regex::new(r"^https://github.com/(?P<repo>[^/]*/[^/]*)/issues/(?P<number>[0-9]+)$")
            .unwrap();
    }
    let ref allowed_repos = options["github_repos_allowed"];
    for prefix in ["topic:", "github topic:"].into_iter() {
        if let Some(ref maybe_url) = strip_ci_prefix(&message, prefix) {
            if let Some(ref caps) = GITHUB_URL_RE.captures(maybe_url) {
                for repo in allowed_repos.split_whitespace() {
                    if caps["repo"] == *repo {
                        return Some(maybe_url.clone());
                    }
                }
            }
        }
    }
    None
}

struct GithubCommentTask {
    data: TopicData,
    github: Github,
}

impl GithubCommentTask {
    fn new(data_: TopicData, options: &HashMap<String, String>) -> GithubCommentTask {
        let github_ =
            Github::new(&*options["github_uastring"],
                        Client::with_connector(HttpsConnector::new(NativeTlsClient::new()
                                                                       .unwrap())),
                        Credentials::Token(options["github_access_token"].clone()));
        GithubCommentTask {
            data: data_,
            github: github_,
        }
    }
    fn run(self) {
        thread::spawn(move || { self.main(); });
    }
    fn main(&self) {
        lazy_static! {
            static ref GITHUB_URL_RE: Regex =
                Regex::new(r"^https://github.com/(?P<owner>[^/]*)/(?P<repo>[^/]*)/issues/(?P<number>[0-9]+)$")
                .unwrap();
        }

        if let Some(ref github_url) = self.data.github_url {
            if let Some(ref caps) = GITHUB_URL_RE.captures(github_url) {
                let repo = self.github
                    .repo(String::from(&caps["owner"]), String::from(&caps["repo"]));
                let issue = repo.issue(caps["number"].parse::<u64>().unwrap());
                let comments = issue.comments();

                let comment_text = format!("{}", self.data);
                comments.create(&CommentOptions { body: comment_text });
            } else {
                warn!("How does {} fail to match now when it matched before?",
                      github_url)
            }
        }
    }
}
<|endoftext|>


<commit_before>#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(unused_mut)]

use std::slice::Iter;
use std::iter::Peekable;
use std::collections::HashMap;
use Data::{Nil, Symbol, Float, Cons};

#[derive(PartialEq,Debug)]
enum Data {
    Nil,
    Symbol(String),
    Float(f32),
    Cons(Box<(Data,Data)>)
}

fn cons (car:Data, cdr:Data) -> Data {
    Data::Cons( Box::new( (car,cdr) ) )
}

fn atom(v:&Data) -> bool {
    match *v {
        Cons(_) => false,
        _ => true,
    }
}

fn nreverse (list: Data) -> Data{
    let mut curr =  list;
    let mut prv = Nil;

    while curr != Nil {
        match curr {
            Cons(ref mut c) =>{
                let next = &mut c.1;
                std::mem::swap(next, &mut prv);
            }
            _ => panic!("Not a proper list!"),
        };
        std::mem::swap(&mut curr, &mut prv);
    }
    prv
}

fn car (d:&Data) -> &Data {
    match *d {
        Cons(ref c) => &c.0,
        ref v @ _ => v,
    }
}

fn cdr (d:&Data) -> &Data {
    match *d {
        Cons(ref c) => &c.1,
        ref v @ _ => v,
    }
}

fn cadr (d:&Data) -> &Data {
    car(cdr(d))
}

fn print(list:&Data) {
    /* Pretty prints a list. Calls a helper function. */
    printh(list);
    print!("\n");
}

fn printh(list:&Data) {
    match *list {
        Float(s) => print!("{}", s),
        Symbol(ref s) => print!("{}", *s),
        Nil => print!("Nil"),
        Cons(ref c) => {
            print!("(");
            printh(&c.0);
            let mut focus = &c.1;
            while *focus != Nil {
                match *focus {
                    Nil => {unreachable!();},
                    Cons(ref c) => {
                        print!(" ");
                        printh(&c.0);
                        focus = &c.1;
                    }
                    _ => { print!(" . ");
                           printh(&focus);
                           print!(")");
                           return;
                    }
                }
            }
            print!(")");
        }
    };
}

fn tokenize(chars:&str) -> Vec<String>{
    /* Takes a string of chars and returns a vector of tokens. */

    chars.replace("(", " ( ")
        .replace(")", " ) ")
        .split(' ')
        .filter(|s| !(s.is_empty()))
        .map(|s| s.to_string())
        .collect()
}

fn read_from_tokens(tokens:&mut Peekable<Iter<String>>) -> Data {
    /* Reads an expression from a sequence of tokens. " */

    if tokens.len() == 0 {
        panic!("Unexpected EOF while parsing.");
    }

    let token = tokens.next().unwrap();

    if token == "(" {
        let mut l = Nil;
        while *tokens.peek().unwrap() != ")" {
            l = cons(read_from_tokens(tokens), l);
        }
        tokens.next();
        return nreverse(l);
    }

    return Data::Symbol(token.to_string())
}

fn parse(program:&str) -> Data {
    return read_from_tokens(&mut tokenize(program).iter().peekable());
}

fn main() {
    let parsed = parse("(begin (define r 10) (* pi (* r r)))");
    print(&parsed);
    print(&cons(Symbol("Love".to_string()), Symbol("You".to_string())));
    print(&cons(Nil, Nil));
    print(&cons(Float(1.0), Float(2.0)));
    print(&cons(Nil, Symbol("Test".to_string())));
    print(&parse("(1 2 3 ())"));
}
<commit_msg>Moved functions on cons to an impl, and implemented Display trait for Data.<commit_after>#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(unused_mut)]
#![allow(unused_must_use)]

use std::slice::Iter;
use std::iter::Peekable;
use std::collections::HashMap;
use std::fmt;
use Data::{Nil, Symbol, Float, Cons};

#[derive(PartialEq,Debug)]
enum Data {
    Nil,
    Symbol(String),
    Float(f32),
    Cons(Box<(Data,Data)>)
}

impl Data {
    fn atom(&self) -> bool {
        match *self {
        Cons(_) => false,
            _ => true,
        }
    }

    fn car (&self) -> &Data {
        match *self {
            Cons(ref c) => &c.0,
            ref v @ _ => v,
        }
    }

    fn cdr (&self) -> &Data {
        match *self {
            Cons(ref c) => &c.1,
            ref v @ _ => v,
        }
    }

    fn cadr (&self) -> &Data {
        self.cdr().car()
    }

    fn nreverse (self) -> Data{
        let mut curr = self;
        let mut prv = Nil;

        while curr != Nil {
            match curr {
                Cons(ref mut c) =>{
                    let next = &mut c.1;
                    std::mem::swap(next, &mut prv);
                }
                _ => panic!("Not a proper list!"),
            };
            std::mem::swap(&mut curr, &mut prv);
        }
        prv
    }
}

impl fmt::Display for Data {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Float(s) => write!(f, "{}", s),
            Symbol(ref s) => write!(f, "{}", *s),
            Nil => write!(f, "Nil"),
            Cons(ref c) => {
                write!(f, "({}", &c.0);
                let mut focus = &c.1;
                while *focus != Nil {
                    match *focus {
                        Nil => {unreachable!();},
                        Cons(ref c) => {
                            write!(f, " {}", &c.0);
                            focus = &c.1;
                        }
                        _ => return write!(f, " . {})", focus)
                    }
                }
                write!(f, ")")
            }
        }
    }
}

fn cons (car:Data, cdr:Data) -> Data {
    Data::Cons( Box::new( (car,cdr) ) )
}

fn tokenize(chars:&str) -> Vec<String>{
    /* Takes a string of chars and returns a vector of tokens. */

    chars.replace("(", " ( ")
        .replace(")", " ) ")
        .split(' ')
        .filter(|s| !(s.is_empty()))
        .map(|s| s.to_string())
        .collect()
}

fn read_from_tokens(tokens:&mut Peekable<Iter<String>>) -> Data {
    /* Reads an expression from a sequence of tokens. " */

    if tokens.len() == 0 {
        panic!("Unexpected EOF while parsing.");
    }

    let token = tokens.next().unwrap();

    if token == "(" {
        let mut l = Nil;
        while *tokens.peek().unwrap() != ")" {
            l = cons(read_from_tokens(tokens), l);
        }
        tokens.next();
        return l.nreverse();
    }

    return Data::Symbol(token.to_string())
}

fn parse(program:&str) -> Data {
    return read_from_tokens(&mut tokenize(program).iter().peekable());
}

fn main() {
    let parsed = parse("(begin (define r 10) (* pi (* r r)))");
    println!("{}",&parsed);
    println!("{}",&cons(Symbol("Love".to_string()), Symbol("You".to_string())));
    println!("{}", cons(Nil, Nil));
    println!("{}",(&cons(Float(1.0), Float(2.0))));
    println!("{}",&cons(Nil, Symbol("Test".to_string())));
    println!("{}",&parse("(1 2 3 ())"));
}
<|endoftext|>


<commit_before>#![feature(slicing_syntax)]
#![feature(phase)]
extern crate cargo;
extern crate docopt;
#[phase(plugin)] extern crate docopt_macros;
extern crate graphviz;
extern crate serialize;

use cargo::core::{Resolve, SourceId, PackageId};
use graphviz as dot;
use std::io::File;
use std::str;

docopt!(Flags, "
Generate a graph of package dependencies in graphviz format

Usage: cargo dot [options]
       cargo dot --help

Options:
    -h, --help         Show this message
    -V, --version      Print version info and exit
    --lock-file=FILE   Specify location of input file, default \"Cargo.lock\"
    --dot-file=FILE    Output to file, default prints to stdout
    --source-labels    Use sources for the label instead of package names
")

fn main() {
    let flags: Flags = Flags::docopt()
                             // cargo passes the exe name first, so we skip it
                             .argv(std::os::args().into_iter().skip(1))
                             .version(Some("0.2".to_string()))
                             .decode()
                             .unwrap_or_else(|e| e.exit());
    println!("what")
    let lock_file  = unless_empty(flags.flag_lock_file, "Cargo.lock");
    let dot_f_flag = if flags.flag_dot_file.is_empty() { None } else { Some(flags.flag_dot_file) };
    let source_labels = flags.flag_source_labels;

    let lock_file = Path::new(lock_file);
    let project_dir = Path::new(lock_file.dirname());
    let project_dir = std::os::make_absolute(&project_dir).unwrap();
    let src_id = SourceId::for_path(&project_dir).unwrap();
    let resolved = cargo::ops::load_lockfile(&lock_file, &src_id)
                     .unwrap_or_else(|e| exit_with(e.description().as_slice()))
                     .unwrap_or_else(||  exit_with("Lock file not found."));

    let mut graph = Graph::with_root(resolved.root(), source_labels);
    graph.add_dependencies(&resolved);

    match dot_f_flag {
        None           => graph.render_to(&mut std::io::stdio::stdout()),
        Some(dot_file) => graph.render_to(&mut File::create(&Path::new(dot_file)))
    };

}

fn exit_with(s: &str) -> ! {
    panic!("Error parsing lockfile: {}", s)
}

fn unless_empty(s: String, default: &str) -> String {
    if s.is_empty() {
        default.to_string()
    } else {
        s
    }
}

pub type Nd = uint;
pub type Ed = (uint, uint);
pub struct Graph<'a> {
    nodes: Vec<&'a PackageId>,
    edges: Vec<Ed>,
    source_labels: bool
}

impl<'a> Graph<'a> {
    pub fn with_root(root: &PackageId, source_labels: bool) -> Graph {
        Graph { nodes: vec![root], edges: vec![], source_labels: source_labels }
    }

    pub fn add_dependencies(&mut self, resolved: &'a Resolve) {
        for crat in resolved.iter() {
            match resolved.deps(crat) {
                Some(mut crate_deps) => {
                    let idl = self.find_or_add(crat);
                    for dep in crate_deps {
                        let idr = self.find_or_add(dep);
                        self.edges.push((idl, idr));
                    };
                },
                None => { }
            }
        }
    }

    fn find_or_add(&mut self, new: &'a PackageId) -> uint {
        for (i, id) in self.nodes.iter().enumerate() {
            if *id == new {
                return i
            }
        }
        self.nodes.push(new);
        self.nodes.len() - 1
    }

    pub fn render_to<W:Writer>(&'a self, output: &mut W) {
        match dot::render(self, output) {
            Ok(_) => {},
            Err(e) => panic!("error rendering graph: {}", e)
        }
    }
}

impl<'a> dot::Labeller<'a, Nd, Ed> for Graph<'a> {
    fn graph_id(&self) -> dot::Id<'a> {
        dot::Id::new(self.nodes[0].get_name()).unwrap_or(dot::Id::new("dependencies").unwrap())
    }
    fn node_id(&self, n: &Nd) -> dot::Id {
        // unwrap is safe because N######## is a valid graphviz id
        dot::Id::new(format!("N{}", *n)).unwrap()
    }
    fn node_label<'a>(&'a self, i: &Nd) -> dot::LabelText<'a> {
        if !self.source_labels {
            dot::LabelStr(str::Slice(self.nodes[*i].get_name()))
        } else {
            dot::LabelStr(str::Owned(self.nodes[*i].get_source_id().get_url().to_string()))
        }
    }
}

impl<'a> dot::GraphWalk<'a, Nd, Ed> for Graph<'a> {
    fn nodes(&self) -> dot::Nodes<'a,Nd> {
        range(0, self.nodes.len()).collect()
    }
    fn edges(&'a self) -> dot::Edges<'a,Ed> {
        dot::maybe_owned_vec::Borrowed(self.edges.as_slice())
    }
    fn source(&self, &(s, _): &Ed) -> Nd { s }
    fn target(&self, &(_, t): &Ed) -> Nd { t }
}
<commit_msg>Get rid of spurious print<commit_after>#![feature(slicing_syntax)]
#![feature(phase)]
extern crate cargo;
extern crate docopt;
#[phase(plugin)] extern crate docopt_macros;
extern crate graphviz;
extern crate serialize;

use cargo::core::{Resolve, SourceId, PackageId};
use graphviz as dot;
use std::io::File;
use std::str;

docopt!(Flags, "
Generate a graph of package dependencies in graphviz format

Usage: cargo dot [options]
       cargo dot --help

Options:
    -h, --help         Show this message
    -V, --version      Print version info and exit
    --lock-file=FILE   Specify location of input file, default \"Cargo.lock\"
    --dot-file=FILE    Output to file, default prints to stdout
    --source-labels    Use sources for the label instead of package names
")

fn main() {
    let flags: Flags = Flags::docopt()
                             // cargo passes the exe name first, so we skip it
                             .argv(std::os::args().into_iter().skip(1))
                             .version(Some("0.2".to_string()))
                             .decode()
                             .unwrap_or_else(|e| e.exit());
    let lock_file  = unless_empty(flags.flag_lock_file, "Cargo.lock");
    let dot_f_flag = if flags.flag_dot_file.is_empty() { None } else { Some(flags.flag_dot_file) };
    let source_labels = flags.flag_source_labels;

    let lock_file = Path::new(lock_file);
    let project_dir = Path::new(lock_file.dirname());
    let project_dir = std::os::make_absolute(&project_dir).unwrap();
    let src_id = SourceId::for_path(&project_dir).unwrap();
    let resolved = cargo::ops::load_lockfile(&lock_file, &src_id)
                     .unwrap_or_else(|e| exit_with(e.description().as_slice()))
                     .unwrap_or_else(||  exit_with("Lock file not found."));

    let mut graph = Graph::with_root(resolved.root(), source_labels);
    graph.add_dependencies(&resolved);

    match dot_f_flag {
        None           => graph.render_to(&mut std::io::stdio::stdout()),
        Some(dot_file) => graph.render_to(&mut File::create(&Path::new(dot_file)))
    };

}

fn exit_with(s: &str) -> ! {
    panic!("Error parsing lockfile: {}", s)
}

fn unless_empty(s: String, default: &str) -> String {
    if s.is_empty() {
        default.to_string()
    } else {
        s
    }
}

pub type Nd = uint;
pub type Ed = (uint, uint);
pub struct Graph<'a> {
    nodes: Vec<&'a PackageId>,
    edges: Vec<Ed>,
    source_labels: bool
}

impl<'a> Graph<'a> {
    pub fn with_root(root: &PackageId, source_labels: bool) -> Graph {
        Graph { nodes: vec![root], edges: vec![], source_labels: source_labels }
    }

    pub fn add_dependencies(&mut self, resolved: &'a Resolve) {
        for crat in resolved.iter() {
            match resolved.deps(crat) {
                Some(mut crate_deps) => {
                    let idl = self.find_or_add(crat);
                    for dep in crate_deps {
                        let idr = self.find_or_add(dep);
                        self.edges.push((idl, idr));
                    };
                },
                None => { }
            }
        }
    }

    fn find_or_add(&mut self, new: &'a PackageId) -> uint {
        for (i, id) in self.nodes.iter().enumerate() {
            if *id == new {
                return i
            }
        }
        self.nodes.push(new);
        self.nodes.len() - 1
    }

    pub fn render_to<W:Writer>(&'a self, output: &mut W) {
        match dot::render(self, output) {
            Ok(_) => {},
            Err(e) => panic!("error rendering graph: {}", e)
        }
    }
}

impl<'a> dot::Labeller<'a, Nd, Ed> for Graph<'a> {
    fn graph_id(&self) -> dot::Id<'a> {
        dot::Id::new(self.nodes[0].get_name()).unwrap_or(dot::Id::new("dependencies").unwrap())
    }
    fn node_id(&self, n: &Nd) -> dot::Id {
        // unwrap is safe because N######## is a valid graphviz id
        dot::Id::new(format!("N{}", *n)).unwrap()
    }
    fn node_label<'a>(&'a self, i: &Nd) -> dot::LabelText<'a> {
        if !self.source_labels {
            dot::LabelStr(str::Slice(self.nodes[*i].get_name()))
        } else {
            dot::LabelStr(str::Owned(self.nodes[*i].get_source_id().get_url().to_string()))
        }
    }
}

impl<'a> dot::GraphWalk<'a, Nd, Ed> for Graph<'a> {
    fn nodes(&self) -> dot::Nodes<'a,Nd> {
        range(0, self.nodes.len()).collect()
    }
    fn edges(&'a self) -> dot::Edges<'a,Ed> {
        dot::maybe_owned_vec::Borrowed(self.edges.as_slice())
    }
    fn source(&self, &(s, _): &Ed) -> Nd { s }
    fn target(&self, &(_, t): &Ed) -> Nd { t }
}
<|endoftext|>