<commit_before>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Andres Vahter (andres.vahter@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#[macro_use]
extern crate log;
extern crate libc;

#[allow(non_camel_case_types)]
pub mod ffiairspy;

use std::mem;
use std::ptr;

#[derive(Debug)]
pub enum AirspyError {
    InvalidParam       = ffiairspy::AIRSPY_ERROR_INVALID_PARAM as isize,
    NotFound           = ffiairspy::AIRSPY_ERROR_NOT_FOUND as isize,
    Busy               = ffiairspy::AIRSPY_ERROR_BUSY as isize,
    NoMem              = ffiairspy::AIRSPY_ERROR_NO_MEM as isize,
    LibUsb             = ffiairspy::AIRSPY_ERROR_LIBUSB as isize,
    Thread             = ffiairspy::AIRSPY_ERROR_THREAD as isize,
    StreamingThreadErr = ffiairspy::AIRSPY_ERROR_STREAMING_THREAD_ERR as isize,
    StreamingStopped   = ffiairspy::AIRSPY_ERROR_STREAMING_STOPPED as isize,
    Other              = ffiairspy::AIRSPY_ERROR_OTHER as isize,
}

#[derive(Debug, Copy, Clone)]
pub enum AirspySamplerate {
    Sps10000000 = 10_000_000,
    Sps2500000  =  2_500_000,
}

#[derive(Debug)]
pub enum AirspySampleType {
    /// 2 * 32bit float per sample
    Float32IQ   = ffiairspy::AIRSPY_SAMPLE_FLOAT32_IQ as isize,
    /// 1 * 32bit float per sample
    Float32Real = ffiairspy::AIRSPY_SAMPLE_FLOAT32_REAL as isize,
    /// 2 * 16bit int per sample
    Int16IQ     = ffiairspy::AIRSPY_SAMPLE_INT16_IQ as isize,
    /// 1 * 16bit int per sample
    Int16Real   = ffiairspy::AIRSPY_SAMPLE_INT16_REAL as isize,
    /// 1 * 16bit unsigned int per sample
    Uint16Real  = ffiairspy::AIRSPY_SAMPLE_UINT16_REAL as isize,
}

pub struct Airspy {
    device: *mut ffiairspy::Struct_airspy_device,
}

impl Airspy {

    pub fn new() -> Result<Airspy, AirspyError> {
        let mut device = ptr::null_mut();

        let error = unsafe {ffiairspy::airspy_open(&mut device)};
        match error {
            ffiairspy::AIRSPY_SUCCESS => Ok(Airspy {device: device}),
            ffiairspy::AIRSPY_ERROR_NO_MEM => Err(AirspyError::NoMem),
            ffiairspy::AIRSPY_ERROR_LIBUSB => Err(AirspyError::LibUsb),
            err => panic!("airspy_open returned error ({:})", err),
        }
    }

    pub fn set_samplerate(&self, samplerate: AirspySamplerate) -> Result<(), AirspyError> {
        let mut supported_samplerates_count: u32 = 0;

        // get count of supported samplerates
        let mut error = unsafe {ffiairspy::airspy_get_samplerates(self.device, &mut supported_samplerates_count as *mut u32, 0)};
        // anything except SUCCESS is library failure, hence panic!
        match error {
            ffiairspy::AIRSPY_SUCCESS => {},
            err => panic!("airspy_get_samplerates returned error ({:})", err),
        }

        assert!(supported_samplerates_count == ffiairspy::AIRSPY_SAMPLERATE_END);

        // get list of supported samplerates
        let mut samplerates: Vec<u32> = vec![0_u32; supported_samplerates_count as usize];
        error = unsafe {ffiairspy::airspy_get_samplerates(self.device, mem::transmute::<&mut u32, *mut u32>(&mut samplerates[0]), supported_samplerates_count)};
        // supported_samplerates_count must be right because we just asked for it from library, there must be lib error if anything other than SUCCESS is returned
        match error {
            ffiairspy::AIRSPY_SUCCESS => {},
            err => panic!("airspy_get_samplerates returned error ({:})", err),
        }

        let sr = samplerate.clone() as u32;
        let mut index: Option<u32> = None;
        for (i, rate) in samplerates.iter().enumerate() {
            if *rate == sr {
                index = Some(i as u32);
                break;
            }
        }

        if index.is_some() {
            let error = unsafe {ffiairspy::airspy_set_samplerate(self.device, index.unwrap())};
            match error {
                ffiairspy::AIRSPY_SUCCESS => Ok(()),
                ffiairspy::AIRSPY_ERROR_LIBUSB => Err(AirspyError::LibUsb),
                err => panic!("airspy_set_samplerate returned error ({:})", err),
            }
        }
        else {
            panic!("samplerate {:?} not supported by libairspy, supported samplerates are {:?}", samplerate, samplerates);
        }
    }

    pub fn set_sample_type(&self, sampletype: AirspySampleType) -> Result<(), AirspyError> {
        match unsafe {ffiairspy::airspy_set_sample_type(self.device, sampletype as u32)} {
            ffiairspy::AIRSPY_SUCCESS => {
                Ok(())
            },
            err => {
                // airspy_set_sample_type should only return SUCCESS
                // however just in case panic here if for some reason it does anything else
                panic!("airspy_set_sample_type returned error {}", err)
            },
        }
    }

    pub fn set_bias_tee(&self, onoff: bool) -> Result<(), AirspyError> {
        let c_onoff = match onoff {
            true => 1,
            false => 0,
        };

        match unsafe {ffiairspy::airspy_set_rf_bias(self.device, c_onoff)} {
            ffiairspy::AIRSPY_SUCCESS => Ok(()),
            ffiairspy::AIRSPY_ERROR_LIBUSB => Err(AirspyError::LibUsb),
            err => {
                panic!("airspy_set_rf_bias returned error {}", err)
            }
        }
    }

    pub fn set_lna_gain(&self, gain: u8) -> Result<(), AirspyError> {
        if gain > 15 {
            Err(AirspyError::InvalidParam)
        }
        else {
            match unsafe {ffiairspy::airspy_set_lna_gain(self.device, gain)} {
                ffiairspy::AIRSPY_SUCCESS => Ok(()),
                ffiairspy::AIRSPY_ERROR_LIBUSB => Err(AirspyError::LibUsb),
                err => {
                    panic!("airspy_set_lna_gain returned error {}", err)
                }
            }
        }
    }

    pub fn set_mixer_gain(&self, gain: u8) -> Result<(), AirspyError> {
        if gain > 15 {
            Err(AirspyError::InvalidParam)
        }
        else {
            match unsafe {ffiairspy::airspy_set_mixer_gain(self.device, gain)} {
                ffiairspy::AIRSPY_SUCCESS => Ok(()),
                ffiairspy::AIRSPY_ERROR_LIBUSB => Err(AirspyError::LibUsb),
                err => {
                    panic!("airspy_set_mixer_gain returned error {}", err)
                }
            }
        }
    }

    pub fn set_vga_gain(&self, gain: u8) -> Result<(), AirspyError> {
        if gain > 15 {
            Err(AirspyError::InvalidParam)
        }
        else {
            match unsafe {ffiairspy::airspy_set_vga_gain(self.device, gain)} {
                ffiairspy::AIRSPY_SUCCESS => Ok(()),
                ffiairspy::AIRSPY_ERROR_LIBUSB => Err(AirspyError::LibUsb),
                err => {
                    panic!("airspy_set_vga_gain returned error {}", err)
                }
            }
        }
    }
}

impl Drop for Airspy {
    fn drop(&mut self) {
        unsafe{ffiairspy::airspy_close(self.device)};
        unsafe{ffiairspy::airspy_exit()};
    }
}
<commit_msg>add set_frequency<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Andres Vahter (andres.vahter@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#[macro_use]
extern crate log;
extern crate libc;

#[allow(non_camel_case_types)]
pub mod ffiairspy;

use std::mem;
use std::ptr;

#[derive(Debug)]
pub enum AirspyError {
    InvalidParam       = ffiairspy::AIRSPY_ERROR_INVALID_PARAM as isize,
    NotFound           = ffiairspy::AIRSPY_ERROR_NOT_FOUND as isize,
    Busy               = ffiairspy::AIRSPY_ERROR_BUSY as isize,
    NoMem              = ffiairspy::AIRSPY_ERROR_NO_MEM as isize,
    LibUsb             = ffiairspy::AIRSPY_ERROR_LIBUSB as isize,
    Thread             = ffiairspy::AIRSPY_ERROR_THREAD as isize,
    StreamingThreadErr = ffiairspy::AIRSPY_ERROR_STREAMING_THREAD_ERR as isize,
    StreamingStopped   = ffiairspy::AIRSPY_ERROR_STREAMING_STOPPED as isize,
    Other              = ffiairspy::AIRSPY_ERROR_OTHER as isize,
}

#[derive(Debug, Copy, Clone)]
pub enum AirspySamplerate {
    Sps10000000 = 10_000_000,
    Sps2500000  =  2_500_000,
}

#[derive(Debug)]
pub enum AirspySampleType {
    /// 2 * 32bit float per sample
    Float32IQ   = ffiairspy::AIRSPY_SAMPLE_FLOAT32_IQ as isize,
    /// 1 * 32bit float per sample
    Float32Real = ffiairspy::AIRSPY_SAMPLE_FLOAT32_REAL as isize,
    /// 2 * 16bit int per sample
    Int16IQ     = ffiairspy::AIRSPY_SAMPLE_INT16_IQ as isize,
    /// 1 * 16bit int per sample
    Int16Real   = ffiairspy::AIRSPY_SAMPLE_INT16_REAL as isize,
    /// 1 * 16bit unsigned int per sample
    Uint16Real  = ffiairspy::AIRSPY_SAMPLE_UINT16_REAL as isize,
}

pub struct Airspy {
    device: *mut ffiairspy::Struct_airspy_device,
}

impl Airspy {

    pub fn new() -> Result<Airspy, AirspyError> {
        let mut device = ptr::null_mut();

        let error = unsafe {ffiairspy::airspy_open(&mut device)};
        match error {
            ffiairspy::AIRSPY_SUCCESS => Ok(Airspy {device: device}),
            ffiairspy::AIRSPY_ERROR_NO_MEM => Err(AirspyError::NoMem),
            ffiairspy::AIRSPY_ERROR_LIBUSB => Err(AirspyError::LibUsb),
            err => panic!("airspy_open returned error ({:})", err),
        }
    }

    pub fn set_samplerate(&self, samplerate: AirspySamplerate) -> Result<(), AirspyError> {
        let mut supported_samplerates_count: u32 = 0;

        // get count of supported samplerates
        let mut error = unsafe {ffiairspy::airspy_get_samplerates(self.device, &mut supported_samplerates_count as *mut u32, 0)};
        // anything except SUCCESS is library failure, hence panic!
        match error {
            ffiairspy::AIRSPY_SUCCESS => {},
            err => panic!("airspy_get_samplerates returned error ({:})", err),
        }

        assert!(supported_samplerates_count == ffiairspy::AIRSPY_SAMPLERATE_END);

        // get list of supported samplerates
        let mut samplerates: Vec<u32> = vec![0_u32; supported_samplerates_count as usize];
        error = unsafe {ffiairspy::airspy_get_samplerates(self.device, mem::transmute::<&mut u32, *mut u32>(&mut samplerates[0]), supported_samplerates_count)};
        // supported_samplerates_count must be right because we just asked for it from library, there must be lib error if anything other than SUCCESS is returned
        match error {
            ffiairspy::AIRSPY_SUCCESS => {},
            err => panic!("airspy_get_samplerates returned error ({:})", err),
        }

        let sr = samplerate.clone() as u32;
        let mut index: Option<u32> = None;
        for (i, rate) in samplerates.iter().enumerate() {
            if *rate == sr {
                index = Some(i as u32);
                break;
            }
        }

        if index.is_some() {
            let error = unsafe {ffiairspy::airspy_set_samplerate(self.device, index.unwrap())};
            match error {
                ffiairspy::AIRSPY_SUCCESS => Ok(()),
                ffiairspy::AIRSPY_ERROR_LIBUSB => Err(AirspyError::LibUsb),
                err => panic!("airspy_set_samplerate returned error ({:})", err),
            }
        }
        else {
            panic!("samplerate {:?} not supported by libairspy, supported samplerates are {:?}", samplerate, samplerates);
        }
    }

    pub fn set_sample_type(&self, sampletype: AirspySampleType) -> Result<(), AirspyError> {
        match unsafe {ffiairspy::airspy_set_sample_type(self.device, sampletype as u32)} {
            ffiairspy::AIRSPY_SUCCESS => {
                Ok(())
            },
            err => {
                // airspy_set_sample_type should only return SUCCESS
                // however just in case panic here if for some reason it does anything else
                panic!("airspy_set_sample_type returned error {}", err)
            },
        }
    }

    pub fn set_bias_tee(&self, onoff: bool) -> Result<(), AirspyError> {
        let c_onoff = match onoff {
            true => 1,
            false => 0,
        };

        match unsafe {ffiairspy::airspy_set_rf_bias(self.device, c_onoff)} {
            ffiairspy::AIRSPY_SUCCESS => Ok(()),
            ffiairspy::AIRSPY_ERROR_LIBUSB => Err(AirspyError::LibUsb),
            err => {
                panic!("airspy_set_rf_bias returned error {}", err)
            }
        }
    }

    pub fn set_lna_gain(&self, gain: u8) -> Result<(), AirspyError> {
        if gain > 15 {
            Err(AirspyError::InvalidParam)
        }
        else {
            match unsafe {ffiairspy::airspy_set_lna_gain(self.device, gain)} {
                ffiairspy::AIRSPY_SUCCESS => Ok(()),
                ffiairspy::AIRSPY_ERROR_LIBUSB => Err(AirspyError::LibUsb),
                err => {
                    panic!("airspy_set_lna_gain returned error {}", err)
                }
            }
        }
    }

    pub fn set_mixer_gain(&self, gain: u8) -> Result<(), AirspyError> {
        if gain > 15 {
            Err(AirspyError::InvalidParam)
        }
        else {
            match unsafe {ffiairspy::airspy_set_mixer_gain(self.device, gain)} {
                ffiairspy::AIRSPY_SUCCESS => Ok(()),
                ffiairspy::AIRSPY_ERROR_LIBUSB => Err(AirspyError::LibUsb),
                err => {
                    panic!("airspy_set_mixer_gain returned error {}", err)
                }
            }
        }
    }

    pub fn set_vga_gain(&self, gain: u8) -> Result<(), AirspyError> {
        if gain > 15 {
            Err(AirspyError::InvalidParam)
        }
        else {
            match unsafe {ffiairspy::airspy_set_vga_gain(self.device, gain)} {
                ffiairspy::AIRSPY_SUCCESS => Ok(()),
                ffiairspy::AIRSPY_ERROR_LIBUSB => Err(AirspyError::LibUsb),
                err => {
                    panic!("airspy_set_vga_gain returned error {}", err)
                }
            }
        }
    }

    pub fn set_frequency(&self, freq_hz: u32) -> Result<(), AirspyError> {
        // 24 MHz to 1.8 GHz
        if freq_hz < 24_000_000 || freq_hz > 1_8_00_000_000 {
            Err(AirspyError::InvalidParam)
        }
        else {
            match unsafe {ffiairspy::airspy_set_freq(self.device, freq_hz)} {
                ffiairspy::AIRSPY_SUCCESS => Ok(()),
                ffiairspy::AIRSPY_ERROR_LIBUSB => Err(AirspyError::LibUsb),
                err => {
                    panic!("airspy_set_freq returned error {}", err)
                }
            }
        }
    }
}

impl Drop for Airspy {
    fn drop(&mut self) {
        unsafe{ffiairspy::airspy_close(self.device)};
        unsafe{ffiairspy::airspy_exit()};
    }
}
<|endoftext|>


<commit_before>// MIT License
//
// Copyright (c) 2017 Matthew Knight
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#![doc(html_root_url = "https://mattlknight.github.io/rust-playground/")]

/*!
This crate provides a library for experimenting and testing rust features on a
rapid create/test/modify cycle. This crate will provide examples of various
complex use cases for traits, implementations, associated types, etc..

This crate is licensed under the MIT license. You can find a copy of the license at the top of
`src/lib.rs`, `src/main.rs`, in the project root as `LICENSE`, as well as in the root of the
[GitHub Repo - rust-playground](https://github.com/mattlknight/rust-playground)

This crate's documentation provides some simple examples. Check out
[Modules](#modules) for the various types, traits, and other rust features used in this project.

Example enum Error [`SqlError`](enum.SqlError.html) type.

Example trait [`SqlSafe`](trait.SqlSafe.html) type.

# Usage

This crate is NOT [on crates.io](https://crates.io/), but can be obtained from my public
[GitHub Repo - rust-playground](https://github.com/mattlknight/rust-playground). This crate can be
used by adding the below lines to your dependencies in your project's `Cargo.toml`, if you want to
 have the main.rs and lib.rs in the same project src/ directory.

```toml
[lib]
name = "mylib"
path = "src/lib.rs"

[bin]
name = "mybin"
path = "src/main.rs"
```

and this to your crate root:

```rust
extern crate mylib;
```

After you have cloned a copy of this project from the GitHub repo, you can run any of the
following sommands from within the project root:

```text
cargo build     # Build the project from src
cargo run       # Build/Run the project
cargo test      # Test the embedded test cases
cargo bench     # Test the performance of the Bench test cases
cargo doc       # Generate this same documentation locally
```

# Example: Validate a string for SQL Injection Safety

To use a public type from this module, add the below to your crate root. This example `use`s the
 [`SqlSafe`](trait.SqlSafe.html) trait. This trait
overloads the `&str` type with a `is_sql_safe()` method.

```rust
extern crate mylib;

use mylib::types::SqlSafe;
# fn main() { }
```

The example shows how to use the method and handle it's return. You could put this in your
 `fn main() { }`

```rust
# extern crate mylib; use mylib::types::SqlSafe;
# fn main() {
let username = "Robert'); DROP TABLE Students;--?";
//   try valid username
// let username = "Bobby Tables";

match username.is_sql_safe() {
    Ok(name) => println!("Username \"{}\" is safe", name),
    Err(err) => {
        println!("Error: Username \"{}\" is NOT SQL safe!", username);
        println!("Error: {}", err);
        println!("Error: {}", err.description());
    },
};

assert!("username;".is_sql_safe().is_err());
assert!("username".is_sql_safe().is_ok());
# }
```

The result will look something like this in the console output:

```text
Error: Username "Robert'); DROP TABLE Students;--?" is NOT SQL safe!
Error: found unsafe sql characters in sql string `');'
Error: sql syntax not allowed
```

*/

#![feature(test)]
extern crate test;

#[macro_use] extern crate lazy_static;
extern crate regex;

mod sql;
pub mod errors;
pub mod types;
<commit_msg>Adds logo and favicon from rust-lang.org<commit_after>// MIT License
//
// Copyright (c) 2017 Matthew Knight
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
       html_favicon_url = "https://www.rust-lang.org/favicon.ico",
       html_root_url = "https://mattlknight.github.io/rust-playground/")]

/*!
This crate provides a library for experimenting and testing rust features on a
rapid create/test/modify cycle. This crate will provide examples of various
complex use cases for traits, implementations, associated types, etc..

This crate is licensed under the MIT license. You can find a copy of the license at the top of
`src/lib.rs`, `src/main.rs`, in the project root as `LICENSE`, as well as in the root of the
[GitHub Repo - rust-playground](https://github.com/mattlknight/rust-playground)

This crate's documentation provides some simple examples. Check out
[Modules](#modules) for the various types, traits, and other rust features used in this project.

Example enum Error [`SqlError`](enum.SqlError.html) type.

Example trait [`SqlSafe`](trait.SqlSafe.html) type.

# Usage

This crate is NOT [on crates.io](https://crates.io/), but can be obtained from my public
[GitHub Repo - rust-playground](https://github.com/mattlknight/rust-playground). This crate can be
used by adding the below lines to your dependencies in your project's `Cargo.toml`, if you want to
 have the main.rs and lib.rs in the same project src/ directory.

```toml
[lib]
name = "mylib"
path = "src/lib.rs"

[bin]
name = "mybin"
path = "src/main.rs"
```

and this to your crate root:

```rust
extern crate mylib;
```

After you have cloned a copy of this project from the GitHub repo, you can run any of the
following sommands from within the project root:

```text
cargo build     # Build the project from src
cargo run       # Build/Run the project
cargo test      # Test the embedded test cases
cargo bench     # Test the performance of the Bench test cases
cargo doc       # Generate this same documentation locally
```

# Example: Validate a string for SQL Injection Safety

To use a public type from this module, add the below to your crate root. This example `use`s the
 [`SqlSafe`](trait.SqlSafe.html) trait. This trait
overloads the `&str` type with a `is_sql_safe()` method.

```rust
extern crate mylib;

use mylib::types::SqlSafe;
# fn main() { }
```

The example shows how to use the method and handle it's return. You could put this in your
 `fn main() { }`

```rust
# extern crate mylib; use mylib::types::SqlSafe;
# fn main() {
let username = "Robert'); DROP TABLE Students;--?";
//   try valid username
// let username = "Bobby Tables";

match username.is_sql_safe() {
    Ok(name) => println!("Username \"{}\" is safe", name),
    Err(err) => {
        println!("Error: Username \"{}\" is NOT SQL safe!", username);
        println!("Error: {}", err);
        println!("Error: {}", err.description());
    },
};

assert!("username;".is_sql_safe().is_err());
assert!("username".is_sql_safe().is_ok());
# }
```

The result will look something like this in the console output:

```text
Error: Username "Robert'); DROP TABLE Students;--?" is NOT SQL safe!
Error: found unsafe sql characters in sql string `');'
Error: sql syntax not allowed
```

*/

#![feature(test)]
extern crate test;

#[macro_use] extern crate lazy_static;
extern crate regex;

mod sql;
pub mod errors;
pub mod types;
<|endoftext|>


<commit_before>//! `sled` is a high-performance embedded database with
//! an API that is similar to a `BTreeMap<[u8], [u8]>`,
//! but with several additional capabilities for
//! assisting creators of stateful systems.
//!
//! It is fully thread-safe, and all operations are
//! atomic. Multiple `Tree`s are supported with the
//! [`Db::open_tree`](struct.Db.html#method.open_tree) method.
//!
//! ACID transactions involving reads and writes to
//! multiple items are supported with the
//! [`Tree::transaction`](struct.Tree.html#method.transaction)
//! method. Transactions may also operate over
//! multiple `Tree`s (see
//! [`Tree::transaction`](struct.Tree.html#method.transaction)
//! docs for more info).
//!
//! Users may also subscribe to updates on individual
//! `Tree`s by using the
//! [`Tree::watch_prefix`](struct.Tree.html#method.watch_prefix)
//! method, which returns a blocking `Iterator` over
//! updates to keys that begin with the provided
//! prefix. You may supply an empty prefix to subscribe
//! to everything.
//!
//! [Merge operators](https://github.com/spacejam/sled/wiki/merge-operators)
//! (aka read-modify-write operators) are supported. A
//! merge operator is a function that specifies
//! how new data can be merged into an existing value
//! without requiring both a read and a write.
//! Using the
//! [`Tree::merge`](struct.Tree.html#method.merge)
//! method, you may "push" data to a `Tree` value
//! and have the provided merge operator combine
//! it with the existing value, if there was one.
//! They are set on a per-`Tree` basis, and essentially
//! allow any sort of data structure to be built
//! using merges as an atomic high-level operation.
//!
//! `sled` is built by experienced database engineers
//! who think users should spend less time tuning and
//! working against high-friction APIs. Expect
//! significant ergonomic and performance improvements
//! over time. Most surprises are bugs, so please
//! [let us know](mailto:t@jujit.su?subject=sled%20sucks!!!) if something
//! is high friction.
//!
//! # Examples
//!
//! ```
//! use sled::Db;
//!
//! let t = Db::open("my_db").unwrap();
//!
//! // insert and get
//! t.insert(b"yo!", b"v1");
//! assert_eq!(&t.get(b"yo!").unwrap().unwrap(), b"v1");
//!
//! // Atomic compare-and-swap.
//! t.compare_and_swap(
//!     b"yo!",      // key
//!     Some(b"v1"), // old value, None for not present
//!     Some(b"v2"), // new value, None for delete
//! )
//! .unwrap();
//!
//! // Iterates over key-value pairs, starting at the given key.
//! let scan_key: &[u8] = b"a non-present key before yo!";
//! let mut iter = t.range(scan_key..);
//! assert_eq!(&iter.next().unwrap().unwrap().0, b"yo!");
//! assert_eq!(iter.next(), None);
//!
//! t.remove(b"yo!");
//! assert_eq!(t.get(b"yo!"), Ok(None));
//! ```
#![doc(
    html_logo_url = "https://raw.githubusercontent.com/spacejam/sled/master/art/tree_face_anti-transphobia.png"
)]
#![cfg_attr(test, deny(warnings))]
#![deny(
    missing_docs,
    future_incompatible,
    nonstandard_style,
    rust_2018_idioms,
    missing_copy_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unsafe_code,
    unused_qualifications
)]
#![deny(
    // clippy::else_if_without_else,
    // clippy::float_arithmetic,
    // clippy::indexing_slicing,
    clippy::cast_lossless,
    clippy::cast_possible_truncation,
    clippy::cast_possible_wrap,
    clippy::cast_precision_loss,
    clippy::cast_sign_loss,
    clippy::checked_conversions,
    clippy::decimal_literal_representation,
    clippy::doc_markdown,
    clippy::empty_enum,
    clippy::expl_impl_clone_on_copy,
    clippy::explicit_into_iter_loop,
    clippy::explicit_iter_loop,
    clippy::explicit_iter_loop,
    clippy::fallible_impl_from,
    clippy::filter_map,
    clippy::filter_map_next,
    clippy::find_map,
    clippy::get_unwrap,
    clippy::if_not_else,
    clippy::inline_always,
    clippy::invalid_upcast_comparisons,
    clippy::items_after_statements,
    clippy::map_flatten,
    clippy::match_same_arms,
    clippy::maybe_infinite_iter,
    clippy::mem_forget,
    // clippy::missing_const_for_fn,
    // clippy::missing_docs_in_private_items,
    // clippy::module_name_repetitions,
    // clippy::multiple_crate_versions,
    clippy::multiple_inherent_impl,
    clippy::mut_mut,
    clippy::needless_borrow,
    clippy::needless_continue,
    clippy::needless_pass_by_value,
    clippy::non_ascii_literal,
    clippy::option_map_unwrap_or,
    clippy::option_map_unwrap_or_else,
    clippy::path_buf_push_overwrite,
    clippy::print_stdout,
    clippy::pub_enum_variant_names,
    clippy::redundant_closure_for_method_calls,
    clippy::replace_consts,
    clippy::result_map_unwrap_or_else,
    clippy::shadow_reuse,
    clippy::shadow_same,
    clippy::shadow_unrelated,
    clippy::single_match_else,
    clippy::string_add,
    clippy::string_add_assign,
    clippy::type_repetition_in_bounds,
    clippy::unicode_not_nfc,
    // clippy::unimplemented,
    clippy::unseparated_literal_suffix,
    clippy::used_underscore_binding,
    clippy::wildcard_dependencies,
    // clippy::wildcard_enum_match_arm,
    clippy::wrong_pub_self_convention,
)]

#[cfg(feature = "failpoints")]
use fail::fail_point;

macro_rules! maybe_fail {
    ($e:expr) => {
        #[cfg(feature = "failpoints")]
        fail_point!($e, |_| Err(Error::FailPoint));
    };
}

mod batch;
mod binary_search;
mod config;
mod context;
mod db;
mod dll;
mod histogram;
mod iter;
mod ivec;
mod lazy;
mod lru;
mod meta;
mod metrics;
mod node;
mod oneshot;
mod pagecache;
mod prefix;
mod result;
mod subscription;
mod sys_limits;
mod threadpool;
mod transaction;
mod tree;
mod vecset;

#[cfg(any(windows, target_os = "linux", target_os = "macos"))]
mod flusher;

#[cfg(feature = "event_log")]
/// The event log helps debug concurrency issues.
pub mod event_log;

#[cfg(feature = "event_log")]
mod stack;

#[cfg(feature = "measure_allocs")]
mod measure_allocs;

#[cfg(feature = "measure_allocs")]
#[global_allocator]
static ALLOCATOR: measure_allocs::TrackingAllocator =
    measure_allocs::TrackingAllocator;

const DEFAULT_TREE_ID: &[u8] = b"__sled__default";

/// hidden re-export of items for testing purposes
#[doc(hidden)]
pub use {
    self::{
        config::RunningConfig,
        lazy::Lazy,
        pagecache::{
            constants::{
                MAX_SPACE_AMPLIFICATION, MINIMUM_ITEMS_PER_SEGMENT,
                MSG_HEADER_LEN, SEG_HEADER_LEN,
            },
            DiskPtr, Log, LogKind, LogOffset, LogRead, Lsn, PageCache, PageId,
            SegmentMode,
        },
    },
    crossbeam_epoch::{pin, Atomic, Guard, Owned, Shared},
};

pub use self::{
    batch::Batch,
    config::Config,
    db::Db,
    iter::Iter,
    ivec::IVec,
    result::{Error, Result},
    subscription::{Event, Subscriber},
    transaction::{
        abort, ConflictableTransactionError, ConflictableTransactionResult,
        TransactionError, TransactionResult, Transactional, TransactionalTree,
    },
    tree::{CompareAndSwapError, CompareAndSwapResult, Tree},
};

use {
    self::{
        binary_search::binary_search_lub,
        context::Context,
        histogram::Histogram,
        lru::Lru,
        meta::Meta,
        metrics::{clock, measure, Measure, M},
        node::{Data, Node},
        oneshot::{OneShot, OneShotFiller},
        result::CasResult,
        subscription::Subscriptions,
        tree::TreeInner,
        vecset::VecSet,
    },
    bincode::{deserialize, serialize},
    crossbeam_utils::{Backoff, CachePadded},
    log::{debug, error, trace, warn},
    pagecache::RecoveryGuard,
    parking_lot::{Condvar, Mutex, RwLock},
    serde::{Deserialize, Serialize},
    std::{
        collections::BTreeMap,
        convert::TryFrom,
        fmt::{self, Debug},
        io::{Read, Write},
        sync::{
            atomic::{
                AtomicI64 as AtomicLsn, AtomicU64, AtomicUsize,
                Ordering::{Acquire, Relaxed, Release, SeqCst},
            },
            Arc,
        },
    },
};

fn crc32(buf: &[u8]) -> u32 {
    let mut hasher = crc32fast::Hasher::new();
    hasher.update(buf);
    hasher.finalize()
}

#[cfg(any(test, feature = "lock_free_delays"))]
mod debug_delay;

#[cfg(any(test, feature = "lock_free_delays"))]
use debug_delay::debug_delay;

/// This function is useful for inducing random jitter into our atomic
/// operations, shaking out more possible interleavings quickly. It gets
/// fully eliminated by the compiler in non-test code.
#[cfg(not(any(test, feature = "lock_free_delays")))]
const fn debug_delay() {}

/// Link denotes a tree node or its modification fragment such as
/// key addition or removal.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Link {
    Set(IVec, IVec),
    Del(IVec),
    ParentMergeIntention(PageId),
    ParentMergeConfirm,
    ChildMergeCap,
}

/// A fast map that is not resistant to collision attacks. Works
/// on 8 bytes at a time.
pub(crate) type FastMap8<K, V> = std::collections::HashMap<
    K,
    V,
    std::hash::BuildHasherDefault<fxhash::FxHasher64>,
>;

/// A fast set that is not resistant to collision attacks. Works
/// on 8 bytes at a time.
pub(crate) type FastSet8<V> = std::collections::HashSet<
    V,
    std::hash::BuildHasherDefault<fxhash::FxHasher64>,
>;

/// Allows arbitrary logic to be injected into mere operations of the
/// `PageCache`.
pub type MergeOperator = fn(
    key: &[u8],
    last_value: Option<&[u8]>,
    new_merge: &[u8],
) -> Option<Vec<u8>>;

fn is_sorted<T: PartialOrd>(xs: &[T]) -> bool {
    xs.windows(2).all(|pair| pair[0] <= pair[1])
}
<commit_msg>Better document some Link types<commit_after>//! `sled` is a high-performance embedded database with
//! an API that is similar to a `BTreeMap<[u8], [u8]>`,
//! but with several additional capabilities for
//! assisting creators of stateful systems.
//!
//! It is fully thread-safe, and all operations are
//! atomic. Multiple `Tree`s are supported with the
//! [`Db::open_tree`](struct.Db.html#method.open_tree) method.
//!
//! ACID transactions involving reads and writes to
//! multiple items are supported with the
//! [`Tree::transaction`](struct.Tree.html#method.transaction)
//! method. Transactions may also operate over
//! multiple `Tree`s (see
//! [`Tree::transaction`](struct.Tree.html#method.transaction)
//! docs for more info).
//!
//! Users may also subscribe to updates on individual
//! `Tree`s by using the
//! [`Tree::watch_prefix`](struct.Tree.html#method.watch_prefix)
//! method, which returns a blocking `Iterator` over
//! updates to keys that begin with the provided
//! prefix. You may supply an empty prefix to subscribe
//! to everything.
//!
//! [Merge operators](https://github.com/spacejam/sled/wiki/merge-operators)
//! (aka read-modify-write operators) are supported. A
//! merge operator is a function that specifies
//! how new data can be merged into an existing value
//! without requiring both a read and a write.
//! Using the
//! [`Tree::merge`](struct.Tree.html#method.merge)
//! method, you may "push" data to a `Tree` value
//! and have the provided merge operator combine
//! it with the existing value, if there was one.
//! They are set on a per-`Tree` basis, and essentially
//! allow any sort of data structure to be built
//! using merges as an atomic high-level operation.
//!
//! `sled` is built by experienced database engineers
//! who think users should spend less time tuning and
//! working against high-friction APIs. Expect
//! significant ergonomic and performance improvements
//! over time. Most surprises are bugs, so please
//! [let us know](mailto:t@jujit.su?subject=sled%20sucks!!!) if something
//! is high friction.
//!
//! # Examples
//!
//! ```
//! use sled::Db;
//!
//! let t = Db::open("my_db").unwrap();
//!
//! // insert and get
//! t.insert(b"yo!", b"v1");
//! assert_eq!(&t.get(b"yo!").unwrap().unwrap(), b"v1");
//!
//! // Atomic compare-and-swap.
//! t.compare_and_swap(
//!     b"yo!",      // key
//!     Some(b"v1"), // old value, None for not present
//!     Some(b"v2"), // new value, None for delete
//! )
//! .unwrap();
//!
//! // Iterates over key-value pairs, starting at the given key.
//! let scan_key: &[u8] = b"a non-present key before yo!";
//! let mut iter = t.range(scan_key..);
//! assert_eq!(&iter.next().unwrap().unwrap().0, b"yo!");
//! assert_eq!(iter.next(), None);
//!
//! t.remove(b"yo!");
//! assert_eq!(t.get(b"yo!"), Ok(None));
//! ```
#![doc(
    html_logo_url = "https://raw.githubusercontent.com/spacejam/sled/master/art/tree_face_anti-transphobia.png"
)]
#![cfg_attr(test, deny(warnings))]
#![deny(
    missing_docs,
    future_incompatible,
    nonstandard_style,
    rust_2018_idioms,
    missing_copy_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unsafe_code,
    unused_qualifications
)]
#![deny(
    // clippy::else_if_without_else,
    // clippy::float_arithmetic,
    // clippy::indexing_slicing,
    clippy::cast_lossless,
    clippy::cast_possible_truncation,
    clippy::cast_possible_wrap,
    clippy::cast_precision_loss,
    clippy::cast_sign_loss,
    clippy::checked_conversions,
    clippy::decimal_literal_representation,
    clippy::doc_markdown,
    clippy::empty_enum,
    clippy::expl_impl_clone_on_copy,
    clippy::explicit_into_iter_loop,
    clippy::explicit_iter_loop,
    clippy::explicit_iter_loop,
    clippy::fallible_impl_from,
    clippy::filter_map,
    clippy::filter_map_next,
    clippy::find_map,
    clippy::get_unwrap,
    clippy::if_not_else,
    clippy::inline_always,
    clippy::invalid_upcast_comparisons,
    clippy::items_after_statements,
    clippy::map_flatten,
    clippy::match_same_arms,
    clippy::maybe_infinite_iter,
    clippy::mem_forget,
    // clippy::missing_const_for_fn,
    // clippy::missing_docs_in_private_items,
    // clippy::module_name_repetitions,
    // clippy::multiple_crate_versions,
    clippy::multiple_inherent_impl,
    clippy::mut_mut,
    clippy::needless_borrow,
    clippy::needless_continue,
    clippy::needless_pass_by_value,
    clippy::non_ascii_literal,
    clippy::option_map_unwrap_or,
    clippy::option_map_unwrap_or_else,
    clippy::path_buf_push_overwrite,
    clippy::print_stdout,
    clippy::pub_enum_variant_names,
    clippy::redundant_closure_for_method_calls,
    clippy::replace_consts,
    clippy::result_map_unwrap_or_else,
    clippy::shadow_reuse,
    clippy::shadow_same,
    clippy::shadow_unrelated,
    clippy::single_match_else,
    clippy::string_add,
    clippy::string_add_assign,
    clippy::type_repetition_in_bounds,
    clippy::unicode_not_nfc,
    // clippy::unimplemented,
    clippy::unseparated_literal_suffix,
    clippy::used_underscore_binding,
    clippy::wildcard_dependencies,
    // clippy::wildcard_enum_match_arm,
    clippy::wrong_pub_self_convention,
)]

#[cfg(feature = "failpoints")]
use fail::fail_point;

macro_rules! maybe_fail {
    ($e:expr) => {
        #[cfg(feature = "failpoints")]
        fail_point!($e, |_| Err(Error::FailPoint));
    };
}

mod batch;
mod binary_search;
mod config;
mod context;
mod db;
mod dll;
mod histogram;
mod iter;
mod ivec;
mod lazy;
mod lru;
mod meta;
mod metrics;
mod node;
mod oneshot;
mod pagecache;
mod prefix;
mod result;
mod subscription;
mod sys_limits;
mod threadpool;
mod transaction;
mod tree;
mod vecset;

#[cfg(any(windows, target_os = "linux", target_os = "macos"))]
mod flusher;

#[cfg(feature = "event_log")]
/// The event log helps debug concurrency issues.
pub mod event_log;

#[cfg(feature = "event_log")]
mod stack;

#[cfg(feature = "measure_allocs")]
mod measure_allocs;

#[cfg(feature = "measure_allocs")]
#[global_allocator]
static ALLOCATOR: measure_allocs::TrackingAllocator =
    measure_allocs::TrackingAllocator;

const DEFAULT_TREE_ID: &[u8] = b"__sled__default";

/// hidden re-export of items for testing purposes
#[doc(hidden)]
pub use {
    self::{
        config::RunningConfig,
        lazy::Lazy,
        pagecache::{
            constants::{
                MAX_SPACE_AMPLIFICATION, MINIMUM_ITEMS_PER_SEGMENT,
                MSG_HEADER_LEN, SEG_HEADER_LEN,
            },
            DiskPtr, Log, LogKind, LogOffset, LogRead, Lsn, PageCache, PageId,
            SegmentMode,
        },
    },
    crossbeam_epoch::{pin, Atomic, Guard, Owned, Shared},
};

pub use self::{
    batch::Batch,
    config::Config,
    db::Db,
    iter::Iter,
    ivec::IVec,
    result::{Error, Result},
    subscription::{Event, Subscriber},
    transaction::{
        abort, ConflictableTransactionError, ConflictableTransactionResult,
        TransactionError, TransactionResult, Transactional, TransactionalTree,
    },
    tree::{CompareAndSwapError, CompareAndSwapResult, Tree},
};

use {
    self::{
        binary_search::binary_search_lub,
        context::Context,
        histogram::Histogram,
        lru::Lru,
        meta::Meta,
        metrics::{clock, measure, Measure, M},
        node::{Data, Node},
        oneshot::{OneShot, OneShotFiller},
        result::CasResult,
        subscription::Subscriptions,
        tree::TreeInner,
        vecset::VecSet,
    },
    bincode::{deserialize, serialize},
    crossbeam_utils::{Backoff, CachePadded},
    log::{debug, error, trace, warn},
    pagecache::RecoveryGuard,
    parking_lot::{Condvar, Mutex, RwLock},
    serde::{Deserialize, Serialize},
    std::{
        collections::BTreeMap,
        convert::TryFrom,
        fmt::{self, Debug},
        io::{Read, Write},
        sync::{
            atomic::{
                AtomicI64 as AtomicLsn, AtomicU64, AtomicUsize,
                Ordering::{Acquire, Relaxed, Release, SeqCst},
            },
            Arc,
        },
    },
};

fn crc32(buf: &[u8]) -> u32 {
    let mut hasher = crc32fast::Hasher::new();
    hasher.update(buf);
    hasher.finalize()
}

#[cfg(any(test, feature = "lock_free_delays"))]
mod debug_delay;

#[cfg(any(test, feature = "lock_free_delays"))]
use debug_delay::debug_delay;

/// This function is useful for inducing random jitter into our atomic
/// operations, shaking out more possible interleavings quickly. It gets
/// fully eliminated by the compiler in non-test code.
#[cfg(not(any(test, feature = "lock_free_delays")))]
const fn debug_delay() {}

/// Link denotes a tree node or its modification fragment such as
/// key addition or removal.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Link {
    /// A new value is set for a given key
    Set(IVec, IVec),
    /// The associated value is removed for a given key
    Del(IVec),
    /// A child of this Index node is marked as mergable
    ParentMergeIntention(PageId),
    /// The merging child has been completely merged into its left sibling
    ParentMergeConfirm,
    /// A Node is marked for being merged into its left sibling
    ChildMergeCap,
}

/// A fast map that is not resistant to collision attacks. Works
/// on 8 bytes at a time.
pub(crate) type FastMap8<K, V> = std::collections::HashMap<
    K,
    V,
    std::hash::BuildHasherDefault<fxhash::FxHasher64>,
>;

/// A fast set that is not resistant to collision attacks. Works
/// on 8 bytes at a time.
pub(crate) type FastSet8<V> = std::collections::HashSet<
    V,
    std::hash::BuildHasherDefault<fxhash::FxHasher64>,
>;

/// Allows arbitrary logic to be injected into mere operations of the
/// `PageCache`.
pub type MergeOperator = fn(
    key: &[u8],
    last_value: Option<&[u8]>,
    new_merge: &[u8],
) -> Option<Vec<u8>>;

fn is_sorted<T: PartialOrd>(xs: &[T]) -> bool {
    xs.windows(2).all(|pair| pair[0] <= pair[1])
}
<|endoftext|>


<commit_before>#![feature(io)]

extern crate xml;

use std::ascii::AsciiExt;
use std::default::Default;
use std::iter::IteratorExt;
use std::io;
use xml::{Element, ElementBuilder, Parser, Xml};

trait ElementUtils {
    fn tag_with_text(&mut self, child_name: &'static str, child_body: &str);
    fn tag_with_text_opt(&mut self, child_name: &'static str, child_body: &Option<String>);
}

impl ElementUtils for Element {
    fn tag_with_text(&mut self, child_name: &'static str, child_body: &str) {
        self.tag(elem_with_text(child_name, child_body));
    }

    fn tag_with_text_opt(&mut self, child_name: &'static str, child_body: &Option<String>) {
        if let Some(ref c) = *child_body {
            self.tag_with_text(child_name, &c);
        }
    }
}

fn elem_with_text(tag_name: &'static str, chars: &str) -> Element {
    let mut elem = Element::new(tag_name, None, &[]);
    elem.text(chars);
    elem
}

trait ViaXml {
    fn to_xml(&self) -> Element;
    fn from_xml(element: Element) -> Result<Self, &'static str>;
}

/// RSS
///
/// "At the top level, a RSS document is a \<rss\> element, with a mandatory attribute called
/// version, that specifies the version of RSS that the document conforms to. If it conforms to
/// this specification, the version attribute must be 2.0."
///
/// [RSS 2.0 Specification § RSS]
/// (http://cyber.law.harvard.edu/rss/rss.html#whatIsRss)
#[derive(Default)]
pub struct Rss(pub Channel);

impl ViaXml for Rss {
    fn to_xml(&self) -> Element {
        let mut rss = Element::new("rss", None, &[("version", None, "2.0")]);

        let &Rss(ref channel) = self;
        rss.tag(channel.to_xml());

        rss
    }

    fn from_xml(rss_elem: Element) -> Result<Self, &'static str> {
        if rss_elem.name.to_ascii_lowercase() != "rss" {
            panic!("Expected <rss>, found <{}>", rss_elem.name);
        }

        let channel_elem = match rss_elem.get_child("channel", None) {
            Some(elem) => elem,
            None => return Err("No <channel> element found in <rss>"),
        };

        let channel = try!(ViaXml::from_xml(channel_elem.clone()));

        Ok(Rss(channel))
    }
}

impl Rss {
    pub fn to_string(&self) -> String {
        let mut ret = format!("{}", Xml::PINode("xml version='1.0' encoding='UTF-8'".to_string()));
        ret.push_str(&format!("{}", self.to_xml()));
        ret
    }

    pub fn from_read(reader: &mut io::Read) -> Result<Self, &'static str> {
        let mut rss_string = String::new();

        match reader.read_to_string(&mut rss_string) {
            Ok(..) => (),
            Err(..) => return Err("Error reading string from reader"),
        }

        let mut parser = Parser::new();
        parser.feed_str(&rss_string);

        let mut builder = ElementBuilder::new();

        for event in parser {
            match builder.push_event(event) {
                Ok(Some(element)) => return ViaXml::from_xml(element),
                _ => (),
            }
        }

        Err("RSS read error")
    }
}

/// Channel
///
/// "Subordinate to the \<rss\> element is a single \<channel\> element, which contains information
/// about the channel (metadata) and its contents."
///
/// [RSS 2.0 Specification § Channel]
/// (http://cyber.law.harvard.edu/rss/rss.html#requiredChannelElements)
///
/// ## Examples
///
/// ```
/// use rss::Channel;
/// use std::default::Default;
///
/// let channel = Channel {
///     title: "My Blog".to_string(),
///     link: "http://myblog.com".to_string(),
///     description: "Where I write stuff".to_string(),
///     items: vec![],
///     ..Default::default()
/// };
/// ```
#[derive(Default)]
pub struct Channel {
    pub title: String,
    pub link: String,
    pub description: String,
    pub items: Vec<Item>,
    pub language: Option<String>,
    pub copyright: Option<String>,
    pub managing_editor: Option<String>,
    pub web_master: Option<String>,
    pub pub_date: Option<String>,
    pub last_build_date: Option<String>,
    pub category: Vec<Category>,
    pub generator: Option<String>,
    pub docs: Option<String>,
    // pub cloud:
    // pub ttl:
    pub image: Option<String>,
    pub rating: Option<String>,
    // pub text_input:
    pub skip_hours: Option<String>,
    pub skip_days: Option<String>,
}

impl ViaXml for Channel {
    fn to_xml(&self) -> Element {
        let mut channel = Element::new("channel", None, &[]);

        channel.tag_with_text("title", &self.title);
        channel.tag_with_text("link", &self.link);
        channel.tag_with_text("description", &self.description);

        for item in &self.items {
            channel.tag(item.to_xml());
        }

        channel.tag_with_text_opt("language", &self.language);
        channel.tag_with_text_opt("copyright", &self.copyright);
        channel.tag_with_text_opt("managingEditor", &self.managing_editor);
        channel.tag_with_text_opt("webMaster", &self.web_master);
        channel.tag_with_text_opt("pubDate", &self.pub_date);
        channel.tag_with_text_opt("lastBuildDate", &self.last_build_date);
        channel.tag_with_text_opt("generator", &self.generator);
        channel.tag_with_text_opt("docs", &self.docs);
        channel.tag_with_text_opt("image", &self.image);
        channel.tag_with_text_opt("rating", &self.rating);
        channel.tag_with_text_opt("skipHours", &self.skip_hours);
        channel.tag_with_text_opt("skipDays", &self.skip_days);

        channel
    }

    fn from_xml(element: Element) -> Result<Self, &'static str> {
        let mut channel: Channel = Default::default();

        match element.get_child("title", None) {
            Some(element) => channel.title = element.content_str(),
            None => return Err("<channel> is missing required <title> element"),
        }

        match element.get_child("link", None) {
            Some(element) => channel.link = element.content_str(),
            None => return Err("<channel> is missing required <link> element"),
        }

        match element.get_child("description", None) {
            Some(element) => channel.description = element.content_str(),
            None => return Err("<channel> is missing required <description> element"),
        }

        channel.items = element.get_children("item", None)
            .into_iter()
            .map(|e| ViaXml::from_xml(e.clone()).unwrap())
            .collect();

        Ok(channel)
    }
}

#[derive(Default)]
pub struct Item {
    pub title: Option<String>,
    pub link: Option<String>,
    pub description: Option<String>,
    // pub author
    pub category: Vec<Category>,
    // pub comments
    // pub enclosure
    // pub guid
    // pub pubDate
    // pub source
}

impl ViaXml for Item {
    fn to_xml(&self) -> Element {
        let mut item = Element::new("item", None, &[]);

        item.tag_with_text_opt("title", &self.title);
        item.tag_with_text_opt("link", &self.link);
        item.tag_with_text_opt("description", &self.description);

        item
    }

    fn from_xml(element: Element) -> Result<Self, &'static str> {
        let mut item: Item = Default::default();

        match element.get_child("title", None) {
            Some(element) => item.title = Some(element.content_str()),
            None => (),
        }

        match element.get_child("link", None) {
            Some(element) => item.link = Some(element.content_str()),
            None => (),
        }

        match element.get_child("description", None) {
            Some(element) => item.description = Some(element.content_str()),
            None => (),
        }

        Ok(item)
    }
}

#[derive(Default)]
pub struct Category {
    pub domain: Option<String>,
    pub value: String,
}

#[cfg(test)]
mod test {
    use std::default::Default;
    use std::fs::File;
    use super::{Rss, Item, Channel};

    #[test]
    fn test_basic_to_string() {
        let item = Item {
            title: Some("My first post!".to_string()),
            link: Some("http://myblog.com/post1".to_string()),
            description: Some("This is my first post".to_string()),
            ..Default::default()
        };

        let channel = Channel {
            title: "My Blog".to_string(),
            link: "http://myblog.com".to_string(),
            description: "Where I write stuff".to_string(),
            items: vec![item],
            ..Default::default()
        };

        let rss = Rss(channel);
        assert_eq!(rss.to_string(), "<?xml version=\'1.0\' encoding=\'UTF-8\'?><rss version=\'2.0\'><channel><title>My Blog</title><link>http://myblog.com</link><description>Where I write stuff</description><item><title>My first post!</title><link>http://myblog.com/post1</link><description>This is my first post</description></item></channel></rss>");
    }

    #[test]
    fn test_from_file() {
        let mut file = File::open("test-data/pinboard.xml").unwrap();
        let rss = Rss::from_read(&mut file).unwrap();
        assert!(rss.to_string().len() > 0);
    }

    #[test]
    #[should_panic]
    fn test_from_read_no_channels() {
        let mut rss_bytes = "<rss></rss>".as_bytes();
        let Rss(_) = Rss::from_read(&mut rss_bytes).unwrap();
    }

    #[test]
    #[should_panic]
    fn test_from_read_one_channel_no_properties() {
        let mut rss_bytes = "<rss><channel></channel></rss>".as_bytes();
        let Rss(_) = Rss::from_read(&mut rss_bytes).unwrap();
    }

    #[test]
    fn test_from_read_one_channel() {
        let mut rss_bytes = "<rss><channel><title>Hello world!</title><description></description><link></link></channel></rss>".as_bytes();
        let Rss(channel) = Rss::from_read(&mut rss_bytes).unwrap();
        assert_eq!("Hello world!", channel.title);
    }
}
<commit_msg>Implement to_xml for Category<commit_after>#![feature(io)]

extern crate xml;

use std::ascii::AsciiExt;
use std::default::Default;
use std::iter::IteratorExt;
use std::io;
use xml::{Element, ElementBuilder, Parser, Xml};

trait ElementUtils {
    fn tag_with_text(&mut self, child_name: &'static str, child_body: &str);
    fn tag_with_text_opt(&mut self, child_name: &'static str, child_body: &Option<String>);
}

impl ElementUtils for Element {
    fn tag_with_text(&mut self, child_name: &'static str, child_body: &str) {
        self.tag(elem_with_text(child_name, child_body));
    }

    fn tag_with_text_opt(&mut self, child_name: &'static str, child_body: &Option<String>) {
        if let Some(ref c) = *child_body {
            self.tag_with_text(child_name, &c);
        }
    }
}

fn elem_with_text(tag_name: &'static str, chars: &str) -> Element {
    let mut elem = Element::new(tag_name, None, &[]);
    elem.text(chars);
    elem
}

trait ViaXml {
    fn to_xml(&self) -> Element;
    fn from_xml(element: Element) -> Result<Self, &'static str>;
}

/// RSS
///
/// "At the top level, a RSS document is a \<rss\> element, with a mandatory attribute called
/// version, that specifies the version of RSS that the document conforms to. If it conforms to
/// this specification, the version attribute must be 2.0."
///
/// [RSS 2.0 Specification § RSS]
/// (http://cyber.law.harvard.edu/rss/rss.html#whatIsRss)
#[derive(Default)]
pub struct Rss(pub Channel);

impl ViaXml for Rss {
    fn to_xml(&self) -> Element {
        let mut rss = Element::new("rss", None, &[("version", None, "2.0")]);

        let &Rss(ref channel) = self;
        rss.tag(channel.to_xml());

        rss
    }

    fn from_xml(rss_elem: Element) -> Result<Self, &'static str> {
        if rss_elem.name.to_ascii_lowercase() != "rss" {
            panic!("Expected <rss>, found <{}>", rss_elem.name);
        }

        let channel_elem = match rss_elem.get_child("channel", None) {
            Some(elem) => elem,
            None => return Err("No <channel> element found in <rss>"),
        };

        let channel = try!(ViaXml::from_xml(channel_elem.clone()));

        Ok(Rss(channel))
    }
}

impl Rss {
    pub fn to_string(&self) -> String {
        let mut ret = format!("{}", Xml::PINode("xml version='1.0' encoding='UTF-8'".to_string()));
        ret.push_str(&format!("{}", self.to_xml()));
        ret
    }

    pub fn from_read(reader: &mut io::Read) -> Result<Self, &'static str> {
        let mut rss_string = String::new();

        match reader.read_to_string(&mut rss_string) {
            Ok(..) => (),
            Err(..) => return Err("Error reading string from reader"),
        }

        let mut parser = Parser::new();
        parser.feed_str(&rss_string);

        let mut builder = ElementBuilder::new();

        for event in parser {
            match builder.push_event(event) {
                Ok(Some(element)) => return ViaXml::from_xml(element),
                _ => (),
            }
        }

        Err("RSS read error")
    }
}

/// Channel
///
/// "Subordinate to the \<rss\> element is a single \<channel\> element, which contains information
/// about the channel (metadata) and its contents."
///
/// [RSS 2.0 Specification § Channel]
/// (http://cyber.law.harvard.edu/rss/rss.html#requiredChannelElements)
///
/// ## Examples
///
/// ```
/// use rss::Channel;
/// use std::default::Default;
///
/// let channel = Channel {
///     title: "My Blog".to_string(),
///     link: "http://myblog.com".to_string(),
///     description: "Where I write stuff".to_string(),
///     items: vec![],
///     ..Default::default()
/// };
/// ```
#[derive(Default)]
pub struct Channel {
    pub title: String,
    pub link: String,
    pub description: String,
    pub items: Vec<Item>,
    pub language: Option<String>,
    pub copyright: Option<String>,
    pub managing_editor: Option<String>,
    pub web_master: Option<String>,
    pub pub_date: Option<String>,
    pub last_build_date: Option<String>,
    pub categories: Vec<Category>,
    pub generator: Option<String>,
    pub docs: Option<String>,
    // pub cloud:
    // pub ttl:
    pub image: Option<String>,
    pub rating: Option<String>,
    // pub text_input:
    pub skip_hours: Option<String>,
    pub skip_days: Option<String>,
}

impl ViaXml for Channel {
    fn to_xml(&self) -> Element {
        let mut channel = Element::new("channel", None, &[]);

        channel.tag_with_text("title", &self.title);
        channel.tag_with_text("link", &self.link);
        channel.tag_with_text("description", &self.description);

        for item in &self.items {
            channel.tag(item.to_xml());
        }

        channel.tag_with_text_opt("language", &self.language);
        channel.tag_with_text_opt("copyright", &self.copyright);
        channel.tag_with_text_opt("managingEditor", &self.managing_editor);
        channel.tag_with_text_opt("webMaster", &self.web_master);
        channel.tag_with_text_opt("pubDate", &self.pub_date);
        channel.tag_with_text_opt("lastBuildDate", &self.last_build_date);
        channel.tag_with_text_opt("generator", &self.generator);
        channel.tag_with_text_opt("docs", &self.docs);
        channel.tag_with_text_opt("image", &self.image);
        channel.tag_with_text_opt("rating", &self.rating);
        channel.tag_with_text_opt("skipHours", &self.skip_hours);
        channel.tag_with_text_opt("skipDays", &self.skip_days);

        for category in &self.categories {
            channel.tag(category.to_xml());
        }

        channel
    }

    fn from_xml(element: Element) -> Result<Self, &'static str> {
        let mut channel: Channel = Default::default();

        match element.get_child("title", None) {
            Some(element) => channel.title = element.content_str(),
            None => return Err("<channel> is missing required <title> element"),
        }

        match element.get_child("link", None) {
            Some(element) => channel.link = element.content_str(),
            None => return Err("<channel> is missing required <link> element"),
        }

        match element.get_child("description", None) {
            Some(element) => channel.description = element.content_str(),
            None => return Err("<channel> is missing required <description> element"),
        }

        channel.items = element.get_children("item", None)
            .into_iter()
            .map(|e| ViaXml::from_xml(e.clone()).unwrap())
            .collect();

        Ok(channel)
    }
}

#[derive(Default)]
pub struct Item {
    pub title: Option<String>,
    pub link: Option<String>,
    pub description: Option<String>,
    // pub author
    pub categories: Vec<Category>,
    // pub comments
    // pub enclosure
    // pub guid
    // pub pubDate
    // pub source
}

impl ViaXml for Item {
    fn to_xml(&self) -> Element {
        let mut item = Element::new("item", None, &[]);

        item.tag_with_text_opt("title", &self.title);
        item.tag_with_text_opt("link", &self.link);
        item.tag_with_text_opt("description", &self.description);

        for category in &self.categories {
            item.tag(category.to_xml());
        }

        item
    }

    fn from_xml(element: Element) -> Result<Self, &'static str> {
        let mut item: Item = Default::default();

        match element.get_child("title", None) {
            Some(element) => item.title = Some(element.content_str()),
            None => (),
        }

        match element.get_child("link", None) {
            Some(element) => item.link = Some(element.content_str()),
            None => (),
        }

        match element.get_child("description", None) {
            Some(element) => item.description = Some(element.content_str()),
            None => (),
        }

        Ok(item)
    }
}

#[derive(Default)]
pub struct Category {
    pub domain: Option<String>,
    pub value: String,
}

impl ViaXml for Category {
    fn to_xml(&self) -> Element {
        let mut category = match self.domain {
            Some(ref d) => Element::new("category", None, &[("domain", None, d)]),
            None => Element::new("category", None, &[]),
        };
        category.text(&self.value);
        category
    }

    fn from_xml(element: Element) -> Result<Self, &'static str> {
        panic!("TODO")
    }
}

#[cfg(test)]
mod test {
    use std::default::Default;
    use std::fs::File;
    use super::{Rss, Item, Channel};

    #[test]
    fn test_basic_to_string() {
        let item = Item {
            title: Some("My first post!".to_string()),
            link: Some("http://myblog.com/post1".to_string()),
            description: Some("This is my first post".to_string()),
            ..Default::default()
        };

        let channel = Channel {
            title: "My Blog".to_string(),
            link: "http://myblog.com".to_string(),
            description: "Where I write stuff".to_string(),
            items: vec![item],
            ..Default::default()
        };

        let rss = Rss(channel);
        assert_eq!(rss.to_string(), "<?xml version=\'1.0\' encoding=\'UTF-8\'?><rss version=\'2.0\'><channel><title>My Blog</title><link>http://myblog.com</link><description>Where I write stuff</description><item><title>My first post!</title><link>http://myblog.com/post1</link><description>This is my first post</description></item></channel></rss>");
    }

    #[test]
    fn test_from_file() {
        let mut file = File::open("test-data/pinboard.xml").unwrap();
        let rss = Rss::from_read(&mut file).unwrap();
        assert!(rss.to_string().len() > 0);
    }

    #[test]
    #[should_panic]
    fn test_from_read_no_channels() {
        let mut rss_bytes = "<rss></rss>".as_bytes();
        let Rss(_) = Rss::from_read(&mut rss_bytes).unwrap();
    }

    #[test]
    #[should_panic]
    fn test_from_read_one_channel_no_properties() {
        let mut rss_bytes = "<rss><channel></channel></rss>".as_bytes();
        let Rss(_) = Rss::from_read(&mut rss_bytes).unwrap();
    }

    #[test]
    fn test_from_read_one_channel() {
        let mut rss_bytes = "<rss><channel><title>Hello world!</title><description></description><link></link></channel></rss>".as_bytes();
        let Rss(channel) = Rss::from_read(&mut rss_bytes).unwrap();
        assert_eq!("Hello world!", channel.title);
    }
}
<|endoftext|>


<commit_before>#[crate_id = "fragments#0.1-pre"];

#[comment = "A simple template library for Rust"];
#[license = "MIT"];
#[crate_type = "lib"];
#[crate_type = "rlib"];

extern crate collections;

use std::fmt;
use std::from_str::FromStr;
use std::io::BufReader;
use collections::hashmap::{HashMap, HashSet};

type ParserState = Option<fn(&mut Buffer) -> ParserResult>;

struct ParserResult {
	token: Token,
	next_parser: ParserState
}

#[deriving(Eq, TotalEq, Show)]
enum Token {
	String(~str),
	Placeholder(~str),
	Conditional(~str, bool, ~[Token])
}

pub struct Template {
	content: ~HashMap<~str, ~fmt::Show>,
	conditions: ~HashSet<~str>,
	priv tokens: ~[Token]
}

impl Template {
	pub fn from_buffer(b: &mut Buffer) -> Template {
		Template {
			content: ~HashMap::new(),
			conditions: ~HashSet::new(),
			tokens: parse_block(b)
		}
	}

	pub fn insert<T: fmt::Show + Send>(&mut self, placeholder: ~str, item: ~T) {
		self.content.insert(placeholder, item as ~fmt::Show);
	}

	fn format_tokens(&self, tokens: &[Token], f: &mut fmt::Formatter) -> fmt::Result {
		for token in tokens.iter() {
			let res = match token {
				&String(ref s) => f.buf.write_str(*s),

				&Placeholder(ref k) => {
					match self.content.find(k) {
						Some(value) => value.fmt(f),
						None => Ok(())
					}
				},

				&Conditional(ref k, expected, ref tokens) => {
					if self.conditions.contains(k) == expected {
						self.format_tokens(*tokens, f)
					} else {
						Ok(())
					}
				}
			};

			match res {
				Err(e) => return Err(e),
				_ => {}
			}
		}

		Ok(())
	}
}

impl FromStr for Template {
	fn from_str(s: &str) -> Option<Template> {
		Some(Template::from_buffer(&mut BufReader::new(s.as_bytes())))
	}
}

impl fmt::Show for Template {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		self.format_tokens(self.tokens, f)
	}
}

fn parse_block(b: &mut Buffer) -> ~[Token] {
	let mut tokens = ~[];
	let mut parser = parse_string;

	loop {
		let ParserResult{token: token, next_parser: next} = parser(b);

		tokens.push(token);

		match next {
			Some(p) => parser = p,
			None => break
		}
	}

	tokens
}

fn parse_string(b: &mut Buffer) -> ParserResult {
	let mut content = ~"";

	loop {
		match b.read_char() {
			Ok('\\') => {
				match b.read_char() {
					Ok(c) => {
						content.push_char(c);
					},
					_ => break
				}
			},
			Ok('[') => {
				match b.read_char() {
					Ok('[') => {
						match b.read_char() {
							Ok(':') => return ParserResult{
								token: String(content),
								next_parser: Some(parse_placeholder)
							},
							Ok('?') => return ParserResult{
								token: String(content),
								next_parser: Some(parse_conditional)
							},
							Ok('/') => {
								skip_to_token_end(b);

								return ParserResult{
									token: String(content),
									next_parser: None
								}
							},
							_ => break
						}
					},
					Ok(c) => {
						content.push_char('[');
						content.push_char(c);
					},
					Err(_) => content.push_char('[')
				}
			},
			Ok(c) => content.push_char(c),
			Err(_) => break
		}
	}

	ParserResult{
		token: String(content),
		next_parser: None
	}
}

fn skip_to_token_end(b: &mut Buffer) {
	loop {
		match b.read_char() {
			Ok('\\') => {
				match b.read_char() {
					Ok(_) => {},
					_ => break
				}
			},
			Ok(']') => {
				match b.read_char() {
					Ok(']') => break,
					_ => {}
				}
			},
			Err(_) => break,
			_ => {}
		}
	}
}

fn parse_placeholder(b: &mut Buffer) -> ParserResult {
	let mut label = ~"";

	loop {
		match b.read_char() {
			Ok('\\') => {
				match b.read_char() {
					Ok(c) => {
						label.push_char(c);
					},
					_ => break
				}
			},
			Ok(']') => {
				match b.read_char() {
					Ok(']') => {
						return ParserResult{
							token: Placeholder(label),
							next_parser: Some(parse_string)
						}
					},
					Ok(c) => {
						label.push_char(']');
						label.push_char(c);
					},
					Err(_) => label.push_char(']')
				}
			},
			Ok(c) => label.push_char(c),
			Err(_) => break
		}
	}

	ParserResult{
		token: Placeholder(label),
		next_parser: None
	}
}

fn parse_conditional(b: &mut Buffer) -> ParserResult {
	let mut label = ~"";
	let mut expected = true;
	let mut expected_set = false;

	loop {
		match b.read_char() {
			Ok('\\') => {
				match b.read_char() {
					Ok(c) => {
						label.push_char(c);
					},
					_ => break
				}
			},
			Ok(']') => {
				match b.read_char() {
					Ok(']') => {
						return ParserResult{
							token: Conditional(label, expected, parse_block(b)),
							next_parser: Some(parse_string)
						}
					},
					Ok(c) => {
						label.push_char(']');
						label.push_char(c);
					},
					Err(_) => label.push_char(']')
				}
			},
			Ok('!') => {
				if label.len() == 0 && !expected_set {
					expected = false;
					expected_set = true;
				} else {
					label.push_char('!');
				}
			},
			Ok(c) => {
				label.push_char(c)
			},
			Err(_) => break
		}
	}

	ParserResult{
		token: Conditional(label, expected, ~[]),
		next_parser: None
	}
}

#[cfg(test)]
mod test {
	use super::{Template, Placeholder, String};

	#[test]
	fn basic_tokens() {
		let template: Template = from_str("Hello, [[:name]]! This is a [[:something]] template.").unwrap();
		assert_eq!(template.tokens[0], String(~"Hello, "));
		assert_eq!(template.tokens[1], Placeholder(~"name"));
		assert_eq!(template.tokens[2], String(~"! This is a "));
		assert_eq!(template.tokens[3], Placeholder(~"something"));
		assert_eq!(template.tokens[4], String(~" template."));
	}

	#[test]
	fn strange_tokens() {
		let template: Template = from_str("Hello, [[[:name]]]! This is a [[[[:something]] template.").unwrap();
		assert_eq!(template.tokens[0], String(~"Hello, "));
	}

	#[test]
	fn escaped_tokens() {
		let template: Template = from_str("Hello, [[:name]]! Write placeholders like \\[[:this]] and escape them like \\\\\\[[:this]]").unwrap();
		assert_eq!(template.tokens[0], String(~"Hello, "));
		assert_eq!(template.tokens[1], Placeholder(~"name"));
		assert_eq!(template.tokens[2], String(~"! Write placeholders like [[:this]] and escape them like \\[[:this]]"));
	}

	#[test]
	fn replacement() {
		let mut template: Template = from_str("Hello, [[:name]]! This is a [[:something]] template.").unwrap();
		template.insert(~"name", ~("Peter"));
		template.insert(~"something", ~("nice"));
		assert_eq!(template.to_str(), ~"Hello, Peter! This is a nice template.");
	}

	#[test]
	fn templates_in_templates() {
		let mut template1: Template = from_str("Hello, [[:name]]! This is a [[:something]] template.").unwrap();
		let mut template2: ~Template = ~from_str("really [[:something]]").unwrap();
		template1.insert(~"name", ~("Peter"));
		template2.insert(~"something", ~("nice"));

		template1.insert(~"something", template2);

		assert_eq!(template1.to_str(), ~"Hello, Peter! This is a really nice template.");
	}

	#[test]
	fn conditional() {
		let mut template: Template = from_str("Hello, [[:name]]![[?condition]] The condition is true.[[/condition]]").unwrap();
		template.insert(~"name", ~("Peter"));
		assert_eq!(template.to_str(), ~"Hello, Peter!");
		template.conditions.insert(~"condition");
		assert_eq!(template.to_str(), ~"Hello, Peter! The condition is true.");
	}

	#[test]
	fn conditional_switch() {
		let mut template: Template = from_str("Hello, [[:name]]! The condition is [[?condition]]true[[/condition]][[?!condition]]false[[/condition]].").unwrap();
		template.insert(~"name", ~("Peter"));
		assert_eq!(template.to_str(), ~"Hello, Peter! The condition is false.");
		template.conditions.insert(~"condition");
		assert_eq!(template.to_str(), ~"Hello, Peter! The condition is true.");
	}
}<commit_msg>Wrote some documentation<commit_after>#[crate_id = "fragments#0.1-pre"];

#[comment = "A simple template library for Rust"];
#[license = "MIT"];
#[crate_type = "lib"];
#[crate_type = "rlib"];

extern crate collections;

use std::fmt;
use std::from_str::FromStr;
use std::io::BufReader;
use collections::hashmap::{HashMap, HashSet};

type ParserState = Option<fn(&mut Buffer) -> ParserResult>;

struct ParserResult {
	token: Token,
	next_parser: ParserState
}

#[deriving(Eq, TotalEq, Show)]
enum Token {
	String(~str),
	Placeholder(~str),
	Conditional(~str, bool, ~[Token])
}

///A string template with placeholders and conditional content.
///
///Placehoders are written as `[[:label]]`, where `label` becomes the name of the placeholder.
///The label is then used to insert content: `my_template.insert(~"label", my_content);`.
///The assigned content for a placeholder can be anything that implements `Show`.
///Even other templates may be inserted, which allows a more atomic structure.
///
///Conditional segments are surrounded by `[[?label]]...[[/]]`, where `label` becomes the name of the condition,
///and they are used to display content depending on wether its label exists in the `conditions` set.
///`[[/]]` marks the end of a block and may contain other characters after the `/`, which may be useful for labeling the end mark.
///Conditions can be made negative by writing `[[?!label]]...[[/]]`, which makes the content visible if the label
///is missing from the `conditions` set.
///
///Any character can be escabed by writing `\` before it. It can be used like this: `\[[[:label1]], [[:label2]]]`
///which will result in `[content1, content2]`, since the first `[` will be ignored by the parser and just added to the
///rest of the content.
pub struct Template {
	///Content for the placeholders
	content: ~HashMap<~str, ~fmt::Show>,
	///Conditional switches
	conditions: ~HashSet<~str>,
	priv tokens: ~[Token]
}

impl Template {
	pub fn from_buffer(b: &mut Buffer) -> Template {
		Template {
			content: ~HashMap::new(),
			conditions: ~HashSet::new(),
			tokens: parse_block(b)
		}
	}

	///Conveience method for inserting content.
	pub fn insert<T: fmt::Show + Send>(&mut self, placeholder: ~str, item: ~T) {
		self.content.insert(placeholder, item as ~fmt::Show);
	}

	fn format_tokens(&self, tokens: &[Token], f: &mut fmt::Formatter) -> fmt::Result {
		for token in tokens.iter() {
			let res = match token {
				&String(ref s) => f.buf.write_str(*s),

				&Placeholder(ref k) => {
					match self.content.find(k) {
						Some(value) => value.fmt(f),
						None => Ok(())
					}
				},

				&Conditional(ref k, expected, ref tokens) => {
					if self.conditions.contains(k) == expected {
						self.format_tokens(*tokens, f)
					} else {
						Ok(())
					}
				}
			};

			match res {
				Err(e) => return Err(e),
				_ => {}
			}
		}

		Ok(())
	}
}

impl FromStr for Template {
	///Creates a new `Template` from a string.
	fn from_str(s: &str) -> Option<Template> {
		Some(Template::from_buffer(&mut BufReader::new(s.as_bytes())))
	}
}

impl fmt::Show for Template {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		self.format_tokens(self.tokens, f)
	}
}

fn parse_block(b: &mut Buffer) -> ~[Token] {
	let mut tokens = ~[];
	let mut parser = parse_string;

	loop {
		let ParserResult{token: token, next_parser: next} = parser(b);

		tokens.push(token);

		match next {
			Some(p) => parser = p,
			None => break
		}
	}

	tokens
}

fn parse_string(b: &mut Buffer) -> ParserResult {
	let mut content = ~"";

	loop {
		match b.read_char() {
			Ok('\\') => {
				match b.read_char() {
					Ok(c) => {
						content.push_char(c);
					},
					_ => break
				}
			},
			Ok('[') => {
				match b.read_char() {
					Ok('[') => {
						match b.read_char() {
							Ok(':') => return ParserResult{
								token: String(content),
								next_parser: Some(parse_placeholder)
							},
							Ok('?') => return ParserResult{
								token: String(content),
								next_parser: Some(parse_conditional)
							},
							Ok('/') => {
								skip_to_token_end(b);

								return ParserResult{
									token: String(content),
									next_parser: None
								}
							},
							_ => break
						}
					},
					Ok(c) => {
						content.push_char('[');
						content.push_char(c);
					},
					Err(_) => content.push_char('[')
				}
			},
			Ok(c) => content.push_char(c),
			Err(_) => break
		}
	}

	ParserResult{
		token: String(content),
		next_parser: None
	}
}

fn skip_to_token_end(b: &mut Buffer) {
	loop {
		match b.read_char() {
			Ok('\\') => {
				match b.read_char() {
					Ok(_) => {},
					_ => break
				}
			},
			Ok(']') => {
				match b.read_char() {
					Ok(']') => break,
					_ => {}
				}
			},
			Err(_) => break,
			_ => {}
		}
	}
}

fn parse_placeholder(b: &mut Buffer) -> ParserResult {
	let mut label = ~"";

	loop {
		match b.read_char() {
			Ok('\\') => {
				match b.read_char() {
					Ok(c) => {
						label.push_char(c);
					},
					_ => break
				}
			},
			Ok(']') => {
				match b.read_char() {
					Ok(']') => {
						return ParserResult{
							token: Placeholder(label),
							next_parser: Some(parse_string)
						}
					},
					Ok(c) => {
						label.push_char(']');
						label.push_char(c);
					},
					Err(_) => label.push_char(']')
				}
			},
			Ok(c) => label.push_char(c),
			Err(_) => break
		}
	}

	ParserResult{
		token: Placeholder(label),
		next_parser: None
	}
}

fn parse_conditional(b: &mut Buffer) -> ParserResult {
	let mut label = ~"";
	let mut expected = true;
	let mut expected_set = false;

	loop {
		match b.read_char() {
			Ok('\\') => {
				match b.read_char() {
					Ok(c) => {
						label.push_char(c);
					},
					_ => break
				}
			},
			Ok(']') => {
				match b.read_char() {
					Ok(']') => {
						return ParserResult{
							token: Conditional(label, expected, parse_block(b)),
							next_parser: Some(parse_string)
						}
					},
					Ok(c) => {
						label.push_char(']');
						label.push_char(c);
					},
					Err(_) => label.push_char(']')
				}
			},
			Ok('!') => {
				if label.len() == 0 && !expected_set {
					expected = false;
					expected_set = true;
				} else {
					label.push_char('!');
				}
			},
			Ok(c) => {
				label.push_char(c)
			},
			Err(_) => break
		}
	}

	ParserResult{
		token: Conditional(label, expected, ~[]),
		next_parser: None
	}
}

#[cfg(test)]
mod test {
	use super::{Template, Placeholder, String};

	#[test]
	fn basic_tokens() {
		let template: Template = from_str("Hello, [[:name]]! This is a [[:something]] template.").unwrap();
		assert_eq!(template.tokens[0], String(~"Hello, "));
		assert_eq!(template.tokens[1], Placeholder(~"name"));
		assert_eq!(template.tokens[2], String(~"! This is a "));
		assert_eq!(template.tokens[3], Placeholder(~"something"));
		assert_eq!(template.tokens[4], String(~" template."));
	}

	#[test]
	fn strange_tokens() {
		let template: Template = from_str("Hello, [[[:name]]]! This is a [[[[:something]] template.").unwrap();
		assert_eq!(template.tokens[0], String(~"Hello, "));
	}

	#[test]
	fn escaped_tokens() {
		let template: Template = from_str("Hello, [[:name]]! Write placeholders like \\[[:this]] and escape them like \\\\\\[[:this]]").unwrap();
		assert_eq!(template.tokens[0], String(~"Hello, "));
		assert_eq!(template.tokens[1], Placeholder(~"name"));
		assert_eq!(template.tokens[2], String(~"! Write placeholders like [[:this]] and escape them like \\[[:this]]"));
	}

	#[test]
	fn replacement() {
		let mut template: Template = from_str("Hello, [[:name]]! This is a [[:something]] template.").unwrap();
		template.insert(~"name", ~("Peter"));
		template.insert(~"something", ~("nice"));
		assert_eq!(template.to_str(), ~"Hello, Peter! This is a nice template.");
	}

	#[test]
	fn templates_in_templates() {
		let mut template1: Template = from_str("Hello, [[:name]]! This is a [[:something]] template.").unwrap();
		let mut template2: ~Template = ~from_str("really [[:something]]").unwrap();
		template1.insert(~"name", ~("Peter"));
		template2.insert(~"something", ~("nice"));

		template1.insert(~"something", template2);

		assert_eq!(template1.to_str(), ~"Hello, Peter! This is a really nice template.");
	}

	#[test]
	fn conditional() {
		let mut template: Template = from_str("Hello, [[:name]]![[?condition]] The condition is true.[[/condition]]").unwrap();
		template.insert(~"name", ~("Peter"));
		assert_eq!(template.to_str(), ~"Hello, Peter!");
		template.conditions.insert(~"condition");
		assert_eq!(template.to_str(), ~"Hello, Peter! The condition is true.");
	}

	#[test]
	fn conditional_switch() {
		let mut template: Template = from_str("Hello, [[:name]]! The condition is [[?condition]]true[[/condition]][[?!condition]]false[[/condition]].").unwrap();
		template.insert(~"name", ~("Peter"));
		assert_eq!(template.to_str(), ~"Hello, Peter! The condition is false.");
		template.conditions.insert(~"condition");
		assert_eq!(template.to_str(), ~"Hello, Peter! The condition is true.");
	}
}<|endoftext|>


<commit_before>#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/7853871?s=128", html_favicon_url = "https://avatars0.githubusercontent.com/u/7853871?s=256", html_root_url = "http://ironframework.io/core/router")]
#![crate_name = "router"]
#![license = "MIT"]
#![deny(missing_doc)]
#![deny(unused_result, unused_result, unnecessary_qualification,
        non_camel_case_types, unused_variable, unnecessary_typecast)]
#![feature(phase, globs)]

//! `Router` provides a fast router middleware for the Iron web framework.

extern crate http;
extern crate iron;
extern crate recognizer = "route-recognizer";

#[cfg(test)] extern crate test;

pub use router::Router;
pub use recognizer::Params;

mod router;

<commit_msg>(docs) Name and href.<commit_after>#![crate_name = "router"]
#![license = "MIT"]
#![deny(missing_doc)]
#![deny(unused_result, unused_result, unnecessary_qualification,
        non_camel_case_types, unused_variable, unnecessary_typecast)]
#![feature(phase, globs)]

//! `Router` provides a fast router middleware for the Iron web framework.

extern crate http;
extern crate iron;
extern crate recognizer = "route-recognizer";

#[cfg(test)] extern crate test;

pub use router::Router;
pub use recognizer::Params;

mod router;

<|endoftext|>