<commit_before>// Module for parsing ISO Base Media Format aka video/mp4 streams.

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

/// Basic ISO box structure.
pub struct BoxHeader {
    /// Four character box type
pub name: u32,
    /// Size of the box in bytes
pub size: u64,
    /// Offset to the start of the contained data (or header size).
pub offset: u64,
}

/// File type box 'ftyp'.
pub struct FileTypeBox {
    name: u32,
    size: u64,
    major_brand: u32,
    minor_version: u32,
    compatible_brands: Vec<u32>,
}

/// Movie header box 'mvhd'.
pub struct MovieHeaderBox {
    pub name: u32,
    pub size: u64,
    pub timescale: u32,
    pub duration: u64,
    // Ignore other fields.
}

pub struct TrackHeaderBox {
    pub name: u32,
    pub size: u64,
    pub track_id: u32,
    pub duration: u64,
    pub width: u32,
    pub height: u32,
}

extern crate byteorder;
use byteorder::{BigEndian, ReadBytesExt};
use std::io::{Read, Result, Seek, SeekFrom, Take};
use std::io::Cursor;

/// Parse a box out of a data buffer.
pub fn read_box_header<T: ReadBytesExt>(src: &mut T) -> Result<BoxHeader> {
    let tmp_size = try!(src.read_u32::<BigEndian>());
    let name = try!(src.read_u32::<BigEndian>());
    let size = match tmp_size {
        1 => try!(src.read_u64::<BigEndian>()),
        _ => tmp_size as u64,
    };
    assert!(size >= 8);
    if tmp_size == 1 {
        assert!(size >= 16);
    }
    let offset = match tmp_size {
        1 => 4 + 4 + 8,
        _ => 4 + 4,
    };
    assert!(offset <= size);
    Ok(BoxHeader{
      name: name,
      size: size,
      offset: offset,
    })
}

/// Parse the extra header fields for a full box.
fn read_fullbox_extra<T: ReadBytesExt>(src: &mut T) -> (u8, u32) {
    let version = src.read_u8().unwrap();
    let flags_a = src.read_u8().unwrap();
    let flags_b = src.read_u8().unwrap();
    let flags_c = src.read_u8().unwrap();
    (version, (flags_a as u32) << 16 |
              (flags_b as u32) <<  8 |
              (flags_c as u32))
}

/// Skip over the contents of a box.
pub fn skip_box_content<T: ReadBytesExt + Seek>
  (src: &mut T, header: &BoxHeader)
  -> std::io::Result<u64>
{
    src.seek(SeekFrom::Current((header.size - header.offset) as i64))
}

/// Helper to construct a Take over the contents of a box.
fn limit<'a, T: Read>(f: &'a mut T, h: &BoxHeader) -> Take<&'a mut T> {
    f.take(h.size - h.offset)
}

/// Helper to construct a Cursor over the contents of a box.
fn recurse<T: Read>(f: &mut T, h: &BoxHeader) {
    println!("{} -- recursing", h);
    // FIXME: I couldn't figure out how to do this without copying.
    // We use Seek on the Read we return in skip_box_content, but
    // that trait isn't implemented for a Take like our limit()
    // returns. Slurping the buffer and wrapping it in a Cursor
    // functions as a work around.
    let buf: Vec<u8> = limit(f, &h)
        .bytes()
        .map(|u| u.unwrap())
        .collect();
    let mut content = Cursor::new(buf);
    loop {
        match read_box(&mut content) {
            Ok(_) => {},
            Err(e) => {
                println!("Error '{:?}' reading box", e.kind());
                break;
            },
        }
    }
    println!("{} -- end", h);
}

/// Read the contents of a box, including sub boxes.
/// Right now it just prints the box value rather than
/// returning anything.
pub fn read_box<T: Read + Seek>(f: &mut T) -> Result<()> {
    read_box_header(f).and_then(|h| {
        match &(fourcc_to_string(h.name))[..] {
            "ftyp" => {
                let mut content = limit(f, &h);
                let ftyp = read_ftyp(&mut content, &h).unwrap();
                println!("{}", ftyp);
            },
            "moov" => recurse(f, &h),
            "mvhd" => {
                let mut content = limit(f, &h);
                let mvhd = read_mvhd(&mut content, &h).unwrap();
                println!("  {}", mvhd);
            },
            "trak" => recurse(f, &h),
            "tkhd" => {
                let mut content = limit(f, &h);
                let tkhd = read_tkhd(&mut content, &h).unwrap();
                println!("  {}", tkhd);
            },
            _ => {
                // Skip the contents of unknown chunks.
                println!("{} (skipped)", h);
                skip_box_content(f, &h).unwrap();
            },
        };
        Ok(()) // and_then needs a Result to return.
    })
}

/// Entry point for C language callers.
/// Take a buffer and call read_box() on it.
#[no_mangle]
pub unsafe extern fn read_box_from_buffer(buffer: *const u8, size: usize)
  -> bool {
    use std::slice;
    use std::thread;

    // Validate arguments from C.
    if buffer.is_null() || size < 8 {
        return false;
    }

    // Wrap the buffer we've been give in a slice.
    let b = slice::from_raw_parts(buffer, size);
    let mut c = Cursor::new(b);

    // Parse in a subthread.
    let task = thread::spawn(move || {
        read_box(&mut c).unwrap();
    });
    // Catch any panics.
    task.join().is_ok()
}

/// Parse an ftype box.
pub fn read_ftyp<T: ReadBytesExt>(src: &mut T, head: &BoxHeader)
  -> Option<FileTypeBox> {
    let major = src.read_u32::<BigEndian>().unwrap();
    let minor = src.read_u32::<BigEndian>().unwrap();
    let brand_count = (head.size - 8 - 8) /4;
    let mut brands = Vec::new();
    for _ in 0..brand_count {
        brands.push(src.read_u32::<BigEndian>().unwrap());
    }
    Some(FileTypeBox{
        name: head.name,
        size: head.size,
        major_brand: major,
        minor_version: minor,
        compatible_brands: brands,
    })
}

/// Parse an mvhd box.
pub fn read_mvhd<T: ReadBytesExt>(src: &mut T, head: &BoxHeader)
  -> Option<MovieHeaderBox> {
    let (version, _) = read_fullbox_extra(src);
    match version {
        1 => {
            // 64 bit creation and modification times.
            let mut skip: Vec<u8> = vec![0; 16];
            let r = src.read(&mut skip).unwrap();
            assert!(r == skip.len());
        },
        0 => {
            // 32 bit creation and modification times.
            // 64 bit creation and modification times.
            let mut skip: Vec<u8> = vec![0; 8];
            let r = src.read(&mut skip).unwrap();
            assert!(r == skip.len());
        },
        _ => panic!("invalid mhdr version"),
    }
    let timescale = src.read_u32::<BigEndian>().unwrap();
    let duration = match version {
        1 => src.read_u64::<BigEndian>().unwrap(),
        0 => src.read_u32::<BigEndian>().unwrap() as u64,
        _ => panic!("invalid mhdr version"),
    };
    // Skip remaining fields.
    let mut skip: Vec<u8> = vec![0; 80];
    let r = src.read(&mut skip).unwrap();
    assert!(r == skip.len());
    Some(MovieHeaderBox {
        name: head.name,
        size: head.size,
        timescale: timescale,
        duration: duration,
    })
}

/// Parse a tkhd box.
pub fn read_tkhd<T: ReadBytesExt>(src: &mut T, head: &BoxHeader)
  -> Option<TrackHeaderBox> {
    let (version, flags) = read_fullbox_extra(src);
    if flags & 0x1u32 == 0 || flags & 0x2u32 == 0 {
        // Track is disabled.
        return None;
    }
    match version {
        1 => {
            // 64 bit creation and modification times.
            let mut skip: Vec<u8> = vec![0; 16];
            let r = src.read(&mut skip).unwrap();
            assert!(r == skip.len());
        },
        0 => {
            // 32 bit creation and modification times.
            // 64 bit creation and modification times.
            let mut skip: Vec<u8> = vec![0; 8];
            let r = src.read(&mut skip).unwrap();
            assert!(r == skip.len());
        },
        _ => panic!("invalid tkhd version"),
    }
    let track_id = src.read_u32::<BigEndian>().unwrap();
    let _reserved = src.read_u32::<BigEndian>().unwrap();
    assert!(_reserved == 0);
    let duration = match version {
        1 => {
            src.read_u64::<BigEndian>().unwrap()
        },
        0 => src.read_u32::<BigEndian>().unwrap() as u64,
        _ => panic!("invalid tkhd version"),
    };
    let _reserved = src.read_u32::<BigEndian>().unwrap();
    let _reserved = src.read_u32::<BigEndian>().unwrap();
    // Skip uninterested fields.
    let mut skip: Vec<u8> = vec![0; 44];
    let r = src.read(&mut skip).unwrap();
    assert!(r == skip.len());
    let width = src.read_u32::<BigEndian>().unwrap();
    let height = src.read_u32::<BigEndian>().unwrap();
    Some(TrackHeaderBox {
        name: head.name,
        size: head.size,
        track_id: track_id,
        duration: duration,
        width: width,
        height: height,
    })
}

/// Convert the iso box type or other 4-character value to a string.
fn fourcc_to_string(name: u32) -> String {
    let u32_to_vec = |u| {
        vec!((u >> 24 & 0xffu32) as u8,
             (u >> 16 & 0xffu32) as u8,
             (u >>  8 & 0xffu32) as u8,
             (u & 0xffu32) as u8)
    };
    let name_bytes = u32_to_vec(name);
    String::from_utf8_lossy(&name_bytes).into_owned()
}

use std::fmt;
impl fmt::Display for BoxHeader {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "'{}' {} bytes", fourcc_to_string(self.name), self.size)
    }
}

impl fmt::Display for FileTypeBox {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let name = fourcc_to_string(self.name);
        let brand = fourcc_to_string(self.major_brand);
        write!(f, "'{}' {} bytes '{}' v{}", name, self.size,
            brand, self.minor_version)
    }
}

impl fmt::Display for MovieHeaderBox {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let name = fourcc_to_string(self.name);
        write!(f, "'{}' {} bytes duration {}s", name, self.size,
            (self.duration as f64)/(self.timescale as f64))
    }
}

use std::u16;
impl fmt::Display for TrackHeaderBox {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let name = fourcc_to_string(self.name);
        // Dimensions are 16.16 fixed-point.
        let base = u16::MAX as f64 + 1.0;
        let width = (self.width as f64) / base;
        let height = (self.height as f64) / base;
        write!(f, "'{}' {} bytes duration {} id {} {}x{}",
            name, self.size, self.duration, self.track_id,
            width, height)
    }
}

#[test]
fn test_read_box_header() {
    use std::io::Cursor;
    use std::io::Write;
    let mut test: Vec<u8> = vec![0, 0, 0, 8];  // minimal box length
    write!(&mut test, "test").unwrap(); // box type
    let mut stream = Cursor::new(test);
    let parsed = read_box_header(&mut stream).unwrap();
    assert_eq!(parsed.name, 1952805748);
    assert_eq!(parsed.size, 8);
    println!("box {}", parsed);
}

#[test]
fn test_read_box_header_long() {
    use std::io::Cursor;
    let mut test: Vec<u8> = vec![0, 0, 0, 1]; // long box extension code
    test.extend("long".to_string().into_bytes()); // box type
    test.extend(vec![0, 0, 0, 0, 0, 0, 16, 0]); // 64 bit size
    // Skip generating box content.
    let mut stream = Cursor::new(test);
    let parsed = read_box_header(&mut stream).unwrap();
    assert_eq!(parsed.name, 1819242087);
    assert_eq!(parsed.size, 4096);
    println!("box {}", parsed);
}

#[test]
fn test_read_ftyp() {
    use std::io::Cursor;
    use std::io::Write;
    let mut test: Vec<u8> = vec![0, 0, 0, 24]; // size
    write!(&mut test, "ftyp").unwrap(); // type
    write!(&mut test, "mp42").unwrap(); // major brand
    test.extend(vec![0, 0, 0, 0]);      // minor version
    write!(&mut test, "isom").unwrap(); // compatible brands...
    write!(&mut test, "mp42").unwrap();
    assert_eq!(test.len(), 24);

    let mut stream = Cursor::new(test);
    let header = read_box_header(&mut stream).unwrap();
    let parsed = read_ftyp(&mut stream, &header).unwrap();
    assert_eq!(parsed.name, 1718909296);
    assert_eq!(parsed.size, 24);
    assert_eq!(parsed.major_brand, 1836069938);
    assert_eq!(parsed.minor_version, 0);
    assert_eq!(parsed.compatible_brands.len(), 2);
    println!("box {}", parsed);
}
<commit_msg>Return io::Result from recurse.<commit_after>// Module for parsing ISO Base Media Format aka video/mp4 streams.

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

/// Basic ISO box structure.
pub struct BoxHeader {
    /// Four character box type
pub name: u32,
    /// Size of the box in bytes
pub size: u64,
    /// Offset to the start of the contained data (or header size).
pub offset: u64,
}

/// File type box 'ftyp'.
pub struct FileTypeBox {
    name: u32,
    size: u64,
    major_brand: u32,
    minor_version: u32,
    compatible_brands: Vec<u32>,
}

/// Movie header box 'mvhd'.
pub struct MovieHeaderBox {
    pub name: u32,
    pub size: u64,
    pub timescale: u32,
    pub duration: u64,
    // Ignore other fields.
}

pub struct TrackHeaderBox {
    pub name: u32,
    pub size: u64,
    pub track_id: u32,
    pub duration: u64,
    pub width: u32,
    pub height: u32,
}

extern crate byteorder;
use byteorder::{BigEndian, ReadBytesExt};
use std::io::{Read, Result, Seek, SeekFrom, Take};
use std::io::Cursor;

/// Parse a box out of a data buffer.
pub fn read_box_header<T: ReadBytesExt>(src: &mut T) -> Result<BoxHeader> {
    let tmp_size = try!(src.read_u32::<BigEndian>());
    let name = try!(src.read_u32::<BigEndian>());
    let size = match tmp_size {
        1 => try!(src.read_u64::<BigEndian>()),
        _ => tmp_size as u64,
    };
    assert!(size >= 8);
    if tmp_size == 1 {
        assert!(size >= 16);
    }
    let offset = match tmp_size {
        1 => 4 + 4 + 8,
        _ => 4 + 4,
    };
    assert!(offset <= size);
    Ok(BoxHeader{
      name: name,
      size: size,
      offset: offset,
    })
}

/// Parse the extra header fields for a full box.
fn read_fullbox_extra<T: ReadBytesExt>(src: &mut T) -> (u8, u32) {
    let version = src.read_u8().unwrap();
    let flags_a = src.read_u8().unwrap();
    let flags_b = src.read_u8().unwrap();
    let flags_c = src.read_u8().unwrap();
    (version, (flags_a as u32) << 16 |
              (flags_b as u32) <<  8 |
              (flags_c as u32))
}

/// Skip over the contents of a box.
pub fn skip_box_content<T: ReadBytesExt + Seek>
  (src: &mut T, header: &BoxHeader)
  -> std::io::Result<u64>
{
    src.seek(SeekFrom::Current((header.size - header.offset) as i64))
}

/// Helper to construct a Take over the contents of a box.
fn limit<'a, T: Read>(f: &'a mut T, h: &BoxHeader) -> Take<&'a mut T> {
    f.take(h.size - h.offset)
}

/// Helper to construct a Cursor over the contents of a box.
fn recurse<T: Read>(f: &mut T, h: &BoxHeader) -> Result<()> {
    println!("{} -- recursing", h);
    // FIXME: I couldn't figure out how to do this without copying.
    // We use Seek on the Read we return in skip_box_content, but
    // that trait isn't implemented for a Take like our limit()
    // returns. Slurping the buffer and wrapping it in a Cursor
    // functions as a work around.
    let buf: Vec<u8> = limit(f, &h)
        .bytes()
        .map(|u| u.unwrap())
        .collect();
    let mut content = Cursor::new(buf);
    loop {
        match read_box(&mut content) {
            Ok(_) => {},
            Err(e) => {
                println!("Error '{:?}' reading box", e.kind());
                return Err(e);
            },
        }
    }
    println!("{} -- end", h);
    Ok(())
}

/// Read the contents of a box, including sub boxes.
/// Right now it just prints the box value rather than
/// returning anything.
pub fn read_box<T: Read + Seek>(f: &mut T) -> Result<()> {
    read_box_header(f).and_then(|h| {
        match &(fourcc_to_string(h.name))[..] {
            "ftyp" => {
                let mut content = limit(f, &h);
                let ftyp = read_ftyp(&mut content, &h).unwrap();
                println!("{}", ftyp);
            },
            "moov" => try!(recurse(f, &h)),
            "mvhd" => {
                let mut content = limit(f, &h);
                let mvhd = read_mvhd(&mut content, &h).unwrap();
                println!("  {}", mvhd);
            },
            "trak" => try!(recurse(f, &h)),
            "tkhd" => {
                let mut content = limit(f, &h);
                let tkhd = read_tkhd(&mut content, &h).unwrap();
                println!("  {}", tkhd);
            },
            _ => {
                // Skip the contents of unknown chunks.
                println!("{} (skipped)", h);
                skip_box_content(f, &h).unwrap();
            },
        };
        Ok(()) // and_then needs a Result to return.
    })
}

/// Entry point for C language callers.
/// Take a buffer and call read_box() on it.
#[no_mangle]
pub unsafe extern fn read_box_from_buffer(buffer: *const u8, size: usize)
  -> bool {
    use std::slice;
    use std::thread;

    // Validate arguments from C.
    if buffer.is_null() || size < 8 {
        return false;
    }

    // Wrap the buffer we've been give in a slice.
    let b = slice::from_raw_parts(buffer, size);
    let mut c = Cursor::new(b);

    // Parse in a subthread.
    let task = thread::spawn(move || {
        read_box(&mut c).unwrap();
    });
    // Catch any panics.
    task.join().is_ok()
}

/// Parse an ftype box.
pub fn read_ftyp<T: ReadBytesExt>(src: &mut T, head: &BoxHeader)
  -> Option<FileTypeBox> {
    let major = src.read_u32::<BigEndian>().unwrap();
    let minor = src.read_u32::<BigEndian>().unwrap();
    let brand_count = (head.size - 8 - 8) /4;
    let mut brands = Vec::new();
    for _ in 0..brand_count {
        brands.push(src.read_u32::<BigEndian>().unwrap());
    }
    Some(FileTypeBox{
        name: head.name,
        size: head.size,
        major_brand: major,
        minor_version: minor,
        compatible_brands: brands,
    })
}

/// Parse an mvhd box.
pub fn read_mvhd<T: ReadBytesExt>(src: &mut T, head: &BoxHeader)
  -> Option<MovieHeaderBox> {
    let (version, _) = read_fullbox_extra(src);
    match version {
        1 => {
            // 64 bit creation and modification times.
            let mut skip: Vec<u8> = vec![0; 16];
            let r = src.read(&mut skip).unwrap();
            assert!(r == skip.len());
        },
        0 => {
            // 32 bit creation and modification times.
            // 64 bit creation and modification times.
            let mut skip: Vec<u8> = vec![0; 8];
            let r = src.read(&mut skip).unwrap();
            assert!(r == skip.len());
        },
        _ => panic!("invalid mhdr version"),
    }
    let timescale = src.read_u32::<BigEndian>().unwrap();
    let duration = match version {
        1 => src.read_u64::<BigEndian>().unwrap(),
        0 => src.read_u32::<BigEndian>().unwrap() as u64,
        _ => panic!("invalid mhdr version"),
    };
    // Skip remaining fields.
    let mut skip: Vec<u8> = vec![0; 80];
    let r = src.read(&mut skip).unwrap();
    assert!(r == skip.len());
    Some(MovieHeaderBox {
        name: head.name,
        size: head.size,
        timescale: timescale,
        duration: duration,
    })
}

/// Parse a tkhd box.
pub fn read_tkhd<T: ReadBytesExt>(src: &mut T, head: &BoxHeader)
  -> Option<TrackHeaderBox> {
    let (version, flags) = read_fullbox_extra(src);
    if flags & 0x1u32 == 0 || flags & 0x2u32 == 0 {
        // Track is disabled.
        return None;
    }
    match version {
        1 => {
            // 64 bit creation and modification times.
            let mut skip: Vec<u8> = vec![0; 16];
            let r = src.read(&mut skip).unwrap();
            assert!(r == skip.len());
        },
        0 => {
            // 32 bit creation and modification times.
            // 64 bit creation and modification times.
            let mut skip: Vec<u8> = vec![0; 8];
            let r = src.read(&mut skip).unwrap();
            assert!(r == skip.len());
        },
        _ => panic!("invalid tkhd version"),
    }
    let track_id = src.read_u32::<BigEndian>().unwrap();
    let _reserved = src.read_u32::<BigEndian>().unwrap();
    assert!(_reserved == 0);
    let duration = match version {
        1 => {
            src.read_u64::<BigEndian>().unwrap()
        },
        0 => src.read_u32::<BigEndian>().unwrap() as u64,
        _ => panic!("invalid tkhd version"),
    };
    let _reserved = src.read_u32::<BigEndian>().unwrap();
    let _reserved = src.read_u32::<BigEndian>().unwrap();
    // Skip uninterested fields.
    let mut skip: Vec<u8> = vec![0; 44];
    let r = src.read(&mut skip).unwrap();
    assert!(r == skip.len());
    let width = src.read_u32::<BigEndian>().unwrap();
    let height = src.read_u32::<BigEndian>().unwrap();
    Some(TrackHeaderBox {
        name: head.name,
        size: head.size,
        track_id: track_id,
        duration: duration,
        width: width,
        height: height,
    })
}

/// Convert the iso box type or other 4-character value to a string.
fn fourcc_to_string(name: u32) -> String {
    let u32_to_vec = |u| {
        vec!((u >> 24 & 0xffu32) as u8,
             (u >> 16 & 0xffu32) as u8,
             (u >>  8 & 0xffu32) as u8,
             (u & 0xffu32) as u8)
    };
    let name_bytes = u32_to_vec(name);
    String::from_utf8_lossy(&name_bytes).into_owned()
}

use std::fmt;
impl fmt::Display for BoxHeader {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "'{}' {} bytes", fourcc_to_string(self.name), self.size)
    }
}

impl fmt::Display for FileTypeBox {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let name = fourcc_to_string(self.name);
        let brand = fourcc_to_string(self.major_brand);
        write!(f, "'{}' {} bytes '{}' v{}", name, self.size,
            brand, self.minor_version)
    }
}

impl fmt::Display for MovieHeaderBox {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let name = fourcc_to_string(self.name);
        write!(f, "'{}' {} bytes duration {}s", name, self.size,
            (self.duration as f64)/(self.timescale as f64))
    }
}

use std::u16;
impl fmt::Display for TrackHeaderBox {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let name = fourcc_to_string(self.name);
        // Dimensions are 16.16 fixed-point.
        let base = u16::MAX as f64 + 1.0;
        let width = (self.width as f64) / base;
        let height = (self.height as f64) / base;
        write!(f, "'{}' {} bytes duration {} id {} {}x{}",
            name, self.size, self.duration, self.track_id,
            width, height)
    }
}

#[test]
fn test_read_box_header() {
    use std::io::Cursor;
    use std::io::Write;
    let mut test: Vec<u8> = vec![0, 0, 0, 8];  // minimal box length
    write!(&mut test, "test").unwrap(); // box type
    let mut stream = Cursor::new(test);
    let parsed = read_box_header(&mut stream).unwrap();
    assert_eq!(parsed.name, 1952805748);
    assert_eq!(parsed.size, 8);
    println!("box {}", parsed);
}

#[test]
fn test_read_box_header_long() {
    use std::io::Cursor;
    let mut test: Vec<u8> = vec![0, 0, 0, 1]; // long box extension code
    test.extend("long".to_string().into_bytes()); // box type
    test.extend(vec![0, 0, 0, 0, 0, 0, 16, 0]); // 64 bit size
    // Skip generating box content.
    let mut stream = Cursor::new(test);
    let parsed = read_box_header(&mut stream).unwrap();
    assert_eq!(parsed.name, 1819242087);
    assert_eq!(parsed.size, 4096);
    println!("box {}", parsed);
}

#[test]
fn test_read_ftyp() {
    use std::io::Cursor;
    use std::io::Write;
    let mut test: Vec<u8> = vec![0, 0, 0, 24]; // size
    write!(&mut test, "ftyp").unwrap(); // type
    write!(&mut test, "mp42").unwrap(); // major brand
    test.extend(vec![0, 0, 0, 0]);      // minor version
    write!(&mut test, "isom").unwrap(); // compatible brands...
    write!(&mut test, "mp42").unwrap();
    assert_eq!(test.len(), 24);

    let mut stream = Cursor::new(test);
    let header = read_box_header(&mut stream).unwrap();
    let parsed = read_ftyp(&mut stream, &header).unwrap();
    assert_eq!(parsed.name, 1718909296);
    assert_eq!(parsed.size, 24);
    assert_eq!(parsed.major_brand, 1836069938);
    assert_eq!(parsed.minor_version, 0);
    assert_eq!(parsed.compatible_brands.len(), 2);
    println!("box {}", parsed);
}
<|endoftext|>


<commit_before>#![feature(core)]

/// Generate a new iterable witn a list comprehension. This macro tries to follow the syntax of
/// Python's list comprehensions. This is a very flexable macro that allows the generation of any
/// iterable that implements `std::iter::FromIterator`. The resulting type will be determined by
/// the type of the variable that you are attempting to assign to. You can create a `Vec`:
///
/// ```
/// let x: Vec<i32> = gen![i*30 => i in [1, 2, 3, 4, 5]];
/// ```
///
/// You can generate a `HashSet`:
///
/// ```
/// let x: HashSet<i32> = gen![i*30 => i in [1, 2, 3, 4, 5]];
/// ```
///
/// You can even use conditionals to generate stuff:
///
/// ```
/// let x: HashSet<i32> = gen![i => i in [1, 2, 3, 4, 5], x % 2 == 0];
/// // outputs vec![2, 4]
/// ```
///
/// Comparisson to Python's list comprehension
/// ===
///
/// Python
/// ---
/// ```python
/// x = [i*4 for i in range(1, 5)]
/// ```
///
/// Rust with gen! macro
/// ---
/// ```
/// let x: Vec<i32> = gen!(x*4 => x in [1, 2, 3, 4]);
/// ```
#[macro_export]
macro_rules! gen {
    [$e:expr => $variable:ident in $iterable:expr] => (
        $iterable.iter().cloned().map(|$variable| $e).collect()
    );
    [$e:expr => $variable:ident in $iterable:expr, $condition:expr] => (
        $iterable.iter().cloned().filter(|$variable| $condition).map(|$variable| $e).collect()
    );
}

#[cfg(test)]
mod test;
<commit_msg>change test stuff<commit_after>#![feature(core)]

/// Generate a new iterable witn a list comprehension. This macro tries to follow the syntax of
/// Python's list comprehensions. This is a very flexable macro that allows the generation of any
/// iterable that implements `std::iter::FromIterator`. The resulting type will be determined by
/// the type of the variable that you are attempting to assign to. You can create a `Vec`:
///
/// ```ignore
/// let x: Vec<i32> = gen![i*30 => i in [1, 2, 3, 4, 5]];
/// ```
///
/// You can generate a `HashSet`:
///
/// ```ignore
/// let x: HashSet<i32> = gen![i*30 => i in [1, 2, 3, 4, 5]];
/// ```
///
/// You can even use conditionals to generate stuff:
///
/// ```ignore
/// let x: HashSet<i32> = gen![i => i in [1, 2, 3, 4, 5], x % 2 == 0];
/// assert_eq!(x, vec![2, 4]);
/// ```
///
/// Comparisson to Python's list comprehension
/// ===
///
/// Python
/// ---
/// ```python
/// x = [i*4 for i in range(1, 5)]
/// ```
///
/// Rust with gen! macro
/// ---
/// ```ignore
/// let x: Vec<i32> = gen!(x*4 => x in [1, 2, 3, 4]);
/// ```
#[macro_export]
#[macro_use]
macro_rules! gen {
    [$e:expr => $variable:ident in $iterable:expr] => (
        $iterable.iter().cloned().map(|$variable| $e).collect()
    );
    [$e:expr => $variable:ident in $iterable:expr, $condition:expr] => (
        $iterable.iter().cloned().filter(|$variable| $condition).map(|$variable| $e).collect()
    );
}
<|endoftext|>


<commit_before>extern crate flate;

mod commit;
mod meta;
mod object_id;
mod object_header;
mod object_type;
mod eobject;
mod unit_tests;
mod serialization;
mod reader;
mod conversion;
mod error;
mod file_status;
mod repository;
mod branch;
mod tag_type;
mod tag;
mod reference;
mod pack_file;
mod pack_index;
mod blob;
mod object_database;
<commit_msg>Ignore dead code and unused imports until we get more stable<commit_after>#![allow(dead_code, unused_imports)]

extern crate flate;

mod commit;
mod meta;
mod object_id;
mod object_header;
mod object_type;
mod eobject;
mod unit_tests;
mod serialization;
mod reader;
mod conversion;
mod error;
mod file_status;
mod repository;
mod branch;
mod tag_type;
mod tag;
mod reference;
mod pack_file;
mod pack_index;
mod blob;
mod object_database;
<|endoftext|>


<commit_before>//! A native Rust library for Mozilla's Public Suffix List

#![cfg_attr(not(any(feature = "punycode", feature = "std")), no_std)]
#![forbid(unsafe_code)]

extern crate alloc;

mod error;

use crate::alloc::borrow::ToOwned;
#[cfg(feature = "anycase")]
use alloc::string::String;
#[cfg(not(feature = "anycase"))]
use alloc::vec::Vec;
#[cfg(feature = "anycase")]
use core::str;
use core::str::{from_utf8, FromStr};
use indexmap::IndexMap;
#[cfg(feature = "anycase")]
use unicase::UniCase;

pub use error::Error;
pub use psl_types::{Domain, Info, List as Psl, Suffix, Type};

#[cfg(not(feature = "anycase"))]
type Children = IndexMap<Vec<u8>, Node>;

#[cfg(feature = "anycase")]
type Children = IndexMap<UniCase<String>, Node>;

const WILDCARD: &str = "*";

#[derive(Debug, Clone, Default, Eq, PartialEq)]
struct Node {
    children: Children,
    leaf: Option<Leaf>,
}

#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
struct Leaf {
    is_exception: bool,
    typ: Option<Type>,
}

/// A dynamic public suffix list
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct List(Node);

impl List {
    /// Creates a new list with default wildcard rule support
    pub fn new() -> Self {
        let mut children = Children::new();
        children.insert(
            #[cfg(not(feature = "anycase"))]
            WILDCARD.as_bytes().to_vec(),
            #[cfg(feature = "anycase")]
            UniCase::new(WILDCARD.to_owned()),
            Node {
                leaf: Some(Default::default()),
                ..Default::default()
            },
        );
        Self(Node {
            children,
            ..Default::default()
        })
    }

    /// Creates a new list from a byte slice
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
        from_utf8(bytes)
            .map_err(|_| Error::ListNotUtf8Encoded)?
            .parse()
    }

    /// Checks to see if the list is empty, ignoring the wildcard rule
    pub fn is_empty(&self) -> bool {
        self.0.children.len() < 2
    }

    fn append(&mut self, mut rule: &str, typ: Type) -> Result<(), Error> {
        let mut is_exception = false;
        if rule.starts_with('!') {
            if !rule.contains('.') {
                return Err(Error::ExceptionAtFirstLabel(rule.to_owned()));
            }
            is_exception = true;
            rule = &rule[1..];
        }

        let mut current = &mut self.0;
        for label in rule.rsplit('.') {
            if label.is_empty() {
                return Err(Error::EmptyLabel(rule.to_owned()));
            }

            let children = &mut current.children;

            #[cfg(not(feature = "anycase"))]
            let entry = children.entry(label.as_bytes().to_vec());

            #[cfg(feature = "anycase")]
            let entry = children.entry(UniCase::new(label.to_owned()));

            current = entry.or_insert_with(Default::default);
        }

        current.leaf = Some(Leaf {
            is_exception,
            typ: Some(typ),
        });

        Ok(())
    }
}

impl<'a> Psl<'a> for List {
    #[inline]
    fn find<T>(&self, labels: T) -> Info
    where
        T: Iterator<Item = &'a [u8]>,
    {
        let mut info = Info { len: 0, typ: None };
        let mut len = 0;

        let mut current = &self.0;
        for label in labels {
            let is_first_label = len == 0;
            #[cfg(not(feature = "anycase"))]
            let node_opt = current.children.get(label);
            #[cfg(feature = "anycase")]
            let node_opt = match str::from_utf8(label) {
                Ok(label) => current.children.get(&UniCase::new(label.to_owned())),
                Err(_) => return Info { len: 0, typ: None },
            };
            match node_opt {
                Some(node) => {
                    current = node;
                }
                None => {
                    #[cfg(not(feature = "anycase"))]
                    let node_opt = current.children.get(WILDCARD.as_bytes());
                    #[cfg(feature = "anycase")]
                    let node_opt = current.children.get(&UniCase::new(WILDCARD.to_owned()));
                    match node_opt {
                        Some(node) => {
                            current = node;
                        }
                        None => break,
                    }
                }
            }
            let label_len = label.len();
            len += if is_first_label {
                label_len
            } else {
                1 + label_len
            };
            if let Some(leaf) = current.leaf {
                if leaf.is_exception {
                    len -= 1 + label_len;
                }
                info.len = len;
                info.typ = leaf.typ;
            } else if is_first_label {
                info.len = len;
            }
        }

        info
    }
}

impl FromStr for List {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut typ = None;
        let mut list = List::new();
        for line in s.lines() {
            match line {
                line if line.contains("BEGIN ICANN DOMAINS") => {
                    typ = Some(Type::Icann);
                }
                line if line.contains("BEGIN PRIVATE DOMAINS") => {
                    typ = Some(Type::Private);
                }
                line if line.starts_with("//") => {
                    continue;
                }
                line => match typ {
                    Some(typ) => {
                        let rule = match line.split_whitespace().next() {
                            Some(rule) => rule,
                            None => continue,
                        };
                        list.append(rule, typ)?;
                        #[cfg(feature = "punycode")]
                        {
                            let ascii = idna::domain_to_ascii(rule)
                                .map_err(|_| Error::InvalidRule(rule.to_owned()))?;
                            list.append(&ascii, typ)?;
                        }
                    }
                    None => {
                        continue;
                    }
                },
            }
        }
        Ok(list)
    }
}

impl Default for List {
    fn default() -> Self {
        Self::new()
    }
}
<commit_msg>bring back LIST_URL<commit_after>//! A native Rust library for Mozilla's Public Suffix List

#![cfg_attr(not(any(feature = "punycode", feature = "std")), no_std)]
#![forbid(unsafe_code)]

extern crate alloc;

mod error;

use crate::alloc::borrow::ToOwned;
#[cfg(feature = "anycase")]
use alloc::string::String;
#[cfg(not(feature = "anycase"))]
use alloc::vec::Vec;
#[cfg(feature = "anycase")]
use core::str;
use core::str::{from_utf8, FromStr};
use indexmap::IndexMap;
#[cfg(feature = "anycase")]
use unicase::UniCase;

pub use error::Error;
pub use psl_types::{Domain, Info, List as Psl, Suffix, Type};

/// The official URL of the list
pub const LIST_URL: &str = "https://publicsuffix.org/list/public_suffix_list.dat";

#[cfg(not(feature = "anycase"))]
type Children = IndexMap<Vec<u8>, Node>;

#[cfg(feature = "anycase")]
type Children = IndexMap<UniCase<String>, Node>;

const WILDCARD: &str = "*";

#[derive(Debug, Clone, Default, Eq, PartialEq)]
struct Node {
    children: Children,
    leaf: Option<Leaf>,
}

#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
struct Leaf {
    is_exception: bool,
    typ: Option<Type>,
}

/// A dynamic public suffix list
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct List(Node);

impl List {
    /// Creates a new list with default wildcard rule support
    pub fn new() -> Self {
        let mut children = Children::new();
        children.insert(
            #[cfg(not(feature = "anycase"))]
            WILDCARD.as_bytes().to_vec(),
            #[cfg(feature = "anycase")]
            UniCase::new(WILDCARD.to_owned()),
            Node {
                leaf: Some(Default::default()),
                ..Default::default()
            },
        );
        Self(Node {
            children,
            ..Default::default()
        })
    }

    /// Creates a new list from a byte slice
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
        from_utf8(bytes)
            .map_err(|_| Error::ListNotUtf8Encoded)?
            .parse()
    }

    /// Checks to see if the list is empty, ignoring the wildcard rule
    pub fn is_empty(&self) -> bool {
        self.0.children.len() < 2
    }

    fn append(&mut self, mut rule: &str, typ: Type) -> Result<(), Error> {
        let mut is_exception = false;
        if rule.starts_with('!') {
            if !rule.contains('.') {
                return Err(Error::ExceptionAtFirstLabel(rule.to_owned()));
            }
            is_exception = true;
            rule = &rule[1..];
        }

        let mut current = &mut self.0;
        for label in rule.rsplit('.') {
            if label.is_empty() {
                return Err(Error::EmptyLabel(rule.to_owned()));
            }

            let children = &mut current.children;

            #[cfg(not(feature = "anycase"))]
            let entry = children.entry(label.as_bytes().to_vec());

            #[cfg(feature = "anycase")]
            let entry = children.entry(UniCase::new(label.to_owned()));

            current = entry.or_insert_with(Default::default);
        }

        current.leaf = Some(Leaf {
            is_exception,
            typ: Some(typ),
        });

        Ok(())
    }
}

impl<'a> Psl<'a> for List {
    #[inline]
    fn find<T>(&self, labels: T) -> Info
    where
        T: Iterator<Item = &'a [u8]>,
    {
        let mut info = Info { len: 0, typ: None };
        let mut len = 0;

        let mut current = &self.0;
        for label in labels {
            let is_first_label = len == 0;
            #[cfg(not(feature = "anycase"))]
            let node_opt = current.children.get(label);
            #[cfg(feature = "anycase")]
            let node_opt = match str::from_utf8(label) {
                Ok(label) => current.children.get(&UniCase::new(label.to_owned())),
                Err(_) => return Info { len: 0, typ: None },
            };
            match node_opt {
                Some(node) => {
                    current = node;
                }
                None => {
                    #[cfg(not(feature = "anycase"))]
                    let node_opt = current.children.get(WILDCARD.as_bytes());
                    #[cfg(feature = "anycase")]
                    let node_opt = current.children.get(&UniCase::new(WILDCARD.to_owned()));
                    match node_opt {
                        Some(node) => {
                            current = node;
                        }
                        None => break,
                    }
                }
            }
            let label_len = label.len();
            len += if is_first_label {
                label_len
            } else {
                1 + label_len
            };
            if let Some(leaf) = current.leaf {
                if leaf.is_exception {
                    len -= 1 + label_len;
                }
                info.len = len;
                info.typ = leaf.typ;
            } else if is_first_label {
                info.len = len;
            }
        }

        info
    }
}

impl FromStr for List {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut typ = None;
        let mut list = List::new();
        for line in s.lines() {
            match line {
                line if line.contains("BEGIN ICANN DOMAINS") => {
                    typ = Some(Type::Icann);
                }
                line if line.contains("BEGIN PRIVATE DOMAINS") => {
                    typ = Some(Type::Private);
                }
                line if line.starts_with("//") => {
                    continue;
                }
                line => match typ {
                    Some(typ) => {
                        let rule = match line.split_whitespace().next() {
                            Some(rule) => rule,
                            None => continue,
                        };
                        list.append(rule, typ)?;
                        #[cfg(feature = "punycode")]
                        {
                            let ascii = idna::domain_to_ascii(rule)
                                .map_err(|_| Error::InvalidRule(rule.to_owned()))?;
                            list.append(&ascii, typ)?;
                        }
                    }
                    None => {
                        continue;
                    }
                },
            }
        }
        Ok(list)
    }
}

impl Default for List {
    fn default() -> Self {
        Self::new()
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate nom;

use std::str;
use std::str::FromStr;
use std::fmt;
use std::fs::File;
use std::io::prelude::*;

// like Into trait but works from a ref avoiding consumption or expensive clone
pub trait IntoSexp {
    fn into_sexp(&self) -> Sexp;
}

#[derive(Debug, Clone)]
pub struct Sexp {
    pub element:Element,
    pub meta:Meta,
}

pub struct Compact<'a> {
    what:&'a Sexp,
}

pub fn compact<'a>(e:&'a Sexp) -> Compact<'a> {
    Compact { what:e }
}

#[derive(Debug, Clone)]
pub enum Element {
    String(String),
    List(Vec<Sexp>),
    Empty,
}

#[derive(Debug, Clone)]
pub struct Meta {
    indent:String,
    nl:usize,
    after_list:String,
}

pub type ERes<T> = Result<T, String>;

impl Sexp {

    pub fn new_empty() -> Sexp {
        Sexp { element:Element::Empty, meta:Meta { indent:String::from(""), nl:0, after_list:String::from(""), } }
    }

    pub fn new(element:Element, indent:String, nl:usize, after_list:String) -> Sexp {
        Sexp { element:element, meta:Meta { indent:indent, nl:nl, after_list:after_list, }, }
    }

    pub fn from<T:IntoSexp>(t:&T) -> Sexp {
        t.into_sexp()
    }

    pub fn list(&self) -> ERes<&Vec<Sexp> > {
        match self.element {
            Element::List(ref v) => Ok(v),
            _ => Err(format!("not a list: {}", self))
        }
    }

    pub fn string(&self) -> ERes<&String> {
        match self.element {
            Element::String(ref s) => Ok(s),
            _ => Err(format!("not a string: {}", self))
        }
    }

    pub fn f(&self) -> ERes<f64> {
        let s = try!(self.string());
        match f64::from_str(&s) {
            Ok(f) => Ok(f),
            _ => Err(format!("Error parsing float"))
        }
    }

    pub fn i(&self) -> ERes<i64> {
        let s = try!(self.string());
        match i64::from_str(&s) {
            Ok(f) => Ok(f),
            _ => Err(format!("Error parsing int"))
        }
    }

    pub fn list_name(&self) -> ERes<&String> {
        let l = try!(self.list());
        let l = &l[..];
        let a = try!(l[0].string());
        Ok(a)
    }

    pub fn slice_atom(&self, s:&str) -> ERes<&[Sexp]> {
        let v = try!(self.list());
        let v2 =&v[..];
        let st = try!(v2[0].string());
        if st != s {
            return Err(format!("list doesn't start with {}, but with {}", s, st))
        };
        Ok(&v[1..])
    }
}

impl fmt::Display for Sexp {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        try!(write!(f, "{}", self.meta.indent));
        try!(match self.element {
            Element::String(ref s) => {
                if s.contains("(") || s.contains(" ") {
                    write!(f,"\"{}\"", s)
                } else {
                    write!(f,"{}", s)
                }
            },
            Element::List(ref v) => {
                try!(write!(f, "("));
                let mut prev_nl = 0;
                for (i, x) in v.iter().enumerate() {
                    let s = if i == 0 || x.meta.indent.len() > 0 || prev_nl > 0 { "" } else { " " };
                    try!(write!(f, "{}{}", s, x));
                    prev_nl = x.meta.nl;
                }
                write!(f, "{})", self.meta.after_list)
            },
            Element::Empty => Ok(())
        });
        for _ in 0..self.meta.nl {
            try!(writeln!(f,""));
        }
        Ok(())
    }
}

impl<'a> fmt::Display for Compact<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match self.what.element {
            Element::String(ref s) => {
                if s.contains("(") || s.contains(" ") {
                    write!(f,"\"{}\"", s)
                } else {
                    write!(f,"{}", s)
                }
            },
            Element::List(ref v) => {
                try!(write!(f, "("));
                for (i, x) in v.iter().enumerate() {
                    let s = if i == 0 { "" } else { " " };
                    try!(write!(f, "{}{}", s, compact(x)));
                }
                write!(f, ")")
            },
            Element::Empty => Ok(())
        }
    }
}

pub fn display_string(s:&String) -> String {
    if s.contains("(") || s.contains(" ") || s.len() == 0 {
        format!("\"{}\"", s)
    } else {
        s.clone()
    }
}

pub fn parse_str(sexp: &str) -> Result<Sexp, String> {
    if sexp.len() == 0 {
        return Ok(Sexp::new_empty())
    }
    match parse_sexp(&sexp.as_bytes()[..]) {
        nom::IResult::Done(_, c) => Ok(c),
        nom::IResult::Error(err) => {
            match err {
                nom::Err::Position(kind,p) =>
                    Err(format!("parse error: {:?} |{}|", kind, str::from_utf8(p).unwrap())),
                _ => Err(format!("parse error"))
            }
        },
        nom::IResult::Incomplete(x) => Err(format!("incomplete: {:?}", x)),
    }
}

fn read_file(name: &str) -> Result<String, std::io::Error> {
    let mut f = try!(File::open(name));
    let mut s = String::new();
    try!(f.read_to_string(&mut s));
    Ok(s)
}

pub fn parse_file(name: &str) -> ERes<Sexp> {
    let s = try!(match read_file(name) {
        Ok(s) => Ok(s),
        Err(x) => Err(format!("{:?}", x))
    });
    parse_str(&s[..])
}

named!(parse_qstring<String>,
       map_res!(
           map_res!(
               delimited!(char!('\"'), is_not!("\""), char!('\"')),
               str::from_utf8),
           FromStr::from_str)
       );

named!(parse_bare_string<String>,
       map_res!(
           map_res!(
               is_not!(b")( \r\n"),
               str::from_utf8),
           FromStr::from_str)
       );

named!(parse_string<(Element,Option<&[u8]>) >,
       map!(alt!(parse_qstring | parse_bare_string), |x| (Element::String(x), None))
       );

named!(parse_list<(Element,Option<&[u8]>) >,
       chain!(
           char!('(') ~
               v: many0!(parse_sexp) ~
               after_list: opt!(nom::multispace) ~ // sometimes there is space after a closing bracket, this would not be caught by parse_sexp
               char!(')'),
           || (Element::List(v), after_list) )
       );

// TODO: consider lines with just spaces and a nl as also nl
named!(line_ending<usize>,
       chain!(
           opt!(nom::space) ~
               c: opt!(is_a!(b"\r\n"))
               , || match c { None => 0, Some(ref x) => x.len(), }
               )
       );

named!(parse_sexp<Sexp>,
           chain!(
               indent: opt!(nom::space) ~
                   s_after_list: alt!(parse_list | parse_string) ~
                   nl: line_ending
                   ,
               || {
                   let s = s_after_list.0.clone();
                   let after_list = s_after_list.1;
                   let indent = match indent {
                       None => String::new(),
                       Some(x) => String::from(str::from_utf8(x).unwrap()),
                   };
                   let after_list = match after_list {
                       None => String::new(),
                       Some(x) => String::from(str::from_utf8(x).unwrap()),
                   };
                   Sexp::new(s, indent, nl, after_list)
               })
       );

// internal tests
#[test]
fn test_qstring1() {
    let x = parse_string(&b"\"hello world\""[..]);
    match x {
        nom::IResult::Done(_,y) => {
            let (e, _) = y;
            match e {
                Element::String(f) => assert_eq!(String::from("hello world"), f),
                _ => panic!("not string"),
            }
        },
        _ => panic!("parser not done"),
    }
}

/*
#[test]
#[should_panic(expected="assertion failed: `(left == right)` (left: `Incomplete(Size(1))`, right: `Done([], \"hello\")`)")]
fn test_qstring2() {
    parse_string(&b"\"hello"[..]);
}
*/

#[cfg(test)]
mod tests {
    use super::*;

    #[allow(dead_code)]
    fn check_parse_res(s: &str, o:&str) {
        let e = parse_str(s).unwrap();
        let t = format!("{}", e);
        assert_eq!(o, t)
    }
    #[allow(dead_code)]
    fn check_parse(s: &str) {
        let e = parse_str(s).unwrap();
        let t = format!("{}", e);
        assert_eq!(s, t)
    }

    #[allow(dead_code)]
    fn parse_fail(s: &str) {
        parse_str(s).unwrap();
    }
    #[allow(dead_code)]
    fn check_compact(s: &str, o:&str) {
        let e = parse_str(s).unwrap();
        let t = format!("{}", compact(&e));
        assert_eq!(o, t)
    }

    #[test]
    fn test_empty() { check_parse("") }

    #[test]
    fn test_empty_qstring() { check_parse("(hello \"\")") }

    #[test]
    fn test_minimal() { check_parse("()") }

    #[test]
    fn test_string() { check_parse("hello") }

    #[test]
    fn test_qstring_a() { check_parse_res("\"hello\"", "hello") }

    #[test]
    fn test_qstring_a2() { check_parse("\"hello world\"") }

    #[test]
    fn test_qstring_a3() { check_parse("\"hello(world)\"") }

    #[test]
    fn test_number() { check_parse("1.3") }

    #[test]
    fn test_float_vs_int() { check_parse("2.0") }

    #[test]
    fn test_double() { check_parse("(())") }

    #[test]
    fn test_br_string() { check_parse("(world)") }

    #[test]
    fn test_br_qstring() { check_parse_res("(\"world\")", "(world)") }

    #[test]
    fn test_br_int() { check_parse("(42)") }

    #[test]
    fn test_br_float() { check_parse("(12.7)") }

    #[test]
    fn test_br_qbrstring() { check_parse("(\"(()\")") }

    #[test]
    fn test_number_string() { check_parse("567A_WZ") }

    #[test]
    #[should_panic(expected="called `Result::unwrap()` on an `Err` value: \"incomplete: Size(2)\"")]
    fn test_invalid1() { parse_fail("(") }

    #[test]
    #[should_panic(expected="called `Result::unwrap()` on an `Err` value: \"parse error: Alt |)|\"")]
    fn test_invalid2() { parse_fail(")") }

    #[test]
    #[should_panic(expected="incomplete: Size")]
    fn test_invalid3() { parse_fail("\"hello") }

    #[test]
    fn test_complex() { check_parse("(module SWITCH_3W_SIDE_MMP221-R (layer F.Cu) (descr \"\") (pad 1 thru_hole rect (size 1.2 1.2) (at -2.5 -1.6 0) (layers *.Cu *.Mask) (drill 0.8)) (pad 2 thru_hole rect (size 1.2 1.2) (at 0.0 -1.6 0) (layers *.Cu *.Mask) (drill 0.8)) (pad 3 thru_hole rect (size 1.2 1.2) (at 2.5 -1.6 0) (layers *.Cu *.Mask) (drill 0.8)) (pad 5 thru_hole rect (size 1.2 1.2) (at 0.0 1.6 0) (layers *.Cu *.Mask) (drill 0.8)) (pad 6 thru_hole rect (size 1.2 1.2) (at -2.5 1.6 0) (layers *.Cu *.Mask) (drill 0.8)) (pad 4 thru_hole rect (size 1.2 1.2) (at 2.5 1.6 0) (layers *.Cu *.Mask) (drill 0.8)) (fp_line (start -4.5 -1.75) (end 4.5 -1.75) (layer F.SilkS) (width 0.127)) (fp_line (start 4.5 -1.75) (end 4.5 1.75) (layer F.SilkS) (width 0.127)) (fp_line (start 4.5 1.75) (end -4.5 1.75) (layer F.SilkS) (width 0.127)) (fp_line (start -4.5 1.75) (end -4.5 -1.75) (layer F.SilkS) (width 0.127)))") }

    #[test]
    fn test_multiline() {
        check_parse("\
(hello \"test it\"
    (foo bar)
    (mars venus)
)
")
    }

    #[test]
    fn test_multiline2() {
        check_parse("\
(hello world
  mad
    (world)
  not)")
    }

    #[test]
    fn test_multiline_two_empty() {
        check_parse("\
(hello

world)")
    }

    #[test]
    fn test_compact1() {
        check_compact("( hello
world \"foo
  bar\"
     (baz)
)", "(hello world \"foo
  bar\" (baz))")
    }

    #[test]
    fn test_fail_pcb() {
        check_parse("\
(kicad_pcb (version 4) (host pcbnew \"(2015-05-31 BZR 5692)-product\")
  (general
  )
)")
    }
}

<commit_msg>add slice_atom_num<commit_after>#[macro_use]
extern crate nom;

use std::str;
use std::str::FromStr;
use std::fmt;
use std::fs::File;
use std::io::prelude::*;

// like Into trait but works from a ref avoiding consumption or expensive clone
pub trait IntoSexp {
    fn into_sexp(&self) -> Sexp;
}

#[derive(Debug, Clone)]
pub struct Sexp {
    pub element:Element,
    pub meta:Meta,
}

pub struct Compact<'a> {
    what:&'a Sexp,
}

pub fn compact<'a>(e:&'a Sexp) -> Compact<'a> {
    Compact { what:e }
}

#[derive(Debug, Clone)]
pub enum Element {
    String(String),
    List(Vec<Sexp>),
    Empty,
}

#[derive(Debug, Clone)]
pub struct Meta {
    indent:String,
    nl:usize,
    after_list:String,
}

pub type ERes<T> = Result<T, String>;

impl Sexp {

    pub fn new_empty() -> Sexp {
        Sexp { element:Element::Empty, meta:Meta { indent:String::from(""), nl:0, after_list:String::from(""), } }
    }

    pub fn new(element:Element, indent:String, nl:usize, after_list:String) -> Sexp {
        Sexp { element:element, meta:Meta { indent:indent, nl:nl, after_list:after_list, }, }
    }

    pub fn from<T:IntoSexp>(t:&T) -> Sexp {
        t.into_sexp()
    }

    pub fn list(&self) -> ERes<&Vec<Sexp> > {
        match self.element {
            Element::List(ref v) => Ok(v),
            _ => Err(format!("not a list: {}", self))
        }
    }

    pub fn string(&self) -> ERes<&String> {
        match self.element {
            Element::String(ref s) => Ok(s),
            _ => Err(format!("not a string: {}", self))
        }
    }

    pub fn f(&self) -> ERes<f64> {
        let s = try!(self.string());
        match f64::from_str(&s) {
            Ok(f) => Ok(f),
            _ => Err(format!("Error parsing float"))
        }
    }

    pub fn i(&self) -> ERes<i64> {
        let s = try!(self.string());
        match i64::from_str(&s) {
            Ok(f) => Ok(f),
            _ => Err(format!("Error parsing int"))
        }
    }

    pub fn list_name(&self) -> ERes<&String> {
        let l = try!(self.list());
        let l = &l[..];
        let a = try!(l[0].string());
        Ok(a)
    }

    pub fn slice_atom(&self, s:&str) -> ERes<&[Sexp]> {
        let v = try!(self.list());
        let v2 =&v[..];
        let st = try!(v2[0].string());
        if st != s {
            return Err(format!("list doesn't start with {}, but with {}", s, st))
        };
        Ok(&v[1..])
    }
    pub fn slice_atom_num(&self, s:&str, num:usize) -> ERes<&[Sexp]> {
        let v = try!(self.list());
        let v2 =&v[..];
        let st = try!(v2[0].string());
        if st != s {
            return Err(format!("list doesn't start with {}, but with {}", s, st))
        };
        let x = &v[1..];
        if x.len() != num {
            return Err(format!("list ({}) doesn't have {} elements", s, num))
        }
        Ok(x)
    }
}

impl fmt::Display for Sexp {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        try!(write!(f, "{}", self.meta.indent));
        try!(match self.element {
            Element::String(ref s) => {
                if s.contains("(") || s.contains(" ") {
                    write!(f,"\"{}\"", s)
                } else {
                    write!(f,"{}", s)
                }
            },
            Element::List(ref v) => {
                try!(write!(f, "("));
                let mut prev_nl = 0;
                for (i, x) in v.iter().enumerate() {
                    let s = if i == 0 || x.meta.indent.len() > 0 || prev_nl > 0 { "" } else { " " };
                    try!(write!(f, "{}{}", s, x));
                    prev_nl = x.meta.nl;
                }
                write!(f, "{})", self.meta.after_list)
            },
            Element::Empty => Ok(())
        });
        for _ in 0..self.meta.nl {
            try!(writeln!(f,""));
        }
        Ok(())
    }
}

impl<'a> fmt::Display for Compact<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match self.what.element {
            Element::String(ref s) => {
                if s.contains("(") || s.contains(" ") {
                    write!(f,"\"{}\"", s)
                } else {
                    write!(f,"{}", s)
                }
            },
            Element::List(ref v) => {
                try!(write!(f, "("));
                for (i, x) in v.iter().enumerate() {
                    let s = if i == 0 { "" } else { " " };
                    try!(write!(f, "{}{}", s, compact(x)));
                }
                write!(f, ")")
            },
            Element::Empty => Ok(())
        }
    }
}

pub fn display_string(s:&String) -> String {
    if s.contains("(") || s.contains(" ") || s.len() == 0 {
        format!("\"{}\"", s)
    } else {
        s.clone()
    }
}

pub fn parse_str(sexp: &str) -> Result<Sexp, String> {
    if sexp.len() == 0 {
        return Ok(Sexp::new_empty())
    }
    match parse_sexp(&sexp.as_bytes()[..]) {
        nom::IResult::Done(_, c) => Ok(c),
        nom::IResult::Error(err) => {
            match err {
                nom::Err::Position(kind,p) =>
                    Err(format!("parse error: {:?} |{}|", kind, str::from_utf8(p).unwrap())),
                _ => Err(format!("parse error"))
            }
        },
        nom::IResult::Incomplete(x) => Err(format!("incomplete: {:?}", x)),
    }
}

fn read_file(name: &str) -> Result<String, std::io::Error> {
    let mut f = try!(File::open(name));
    let mut s = String::new();
    try!(f.read_to_string(&mut s));
    Ok(s)
}

pub fn parse_file(name: &str) -> ERes<Sexp> {
    let s = try!(match read_file(name) {
        Ok(s) => Ok(s),
        Err(x) => Err(format!("{:?}", x))
    });
    parse_str(&s[..])
}

named!(parse_qstring<String>,
       map_res!(
           map_res!(
               delimited!(char!('\"'), is_not!("\""), char!('\"')),
               str::from_utf8),
           FromStr::from_str)
       );

named!(parse_bare_string<String>,
       map_res!(
           map_res!(
               is_not!(b")( \r\n"),
               str::from_utf8),
           FromStr::from_str)
       );

named!(parse_string<(Element,Option<&[u8]>) >,
       map!(alt!(parse_qstring | parse_bare_string), |x| (Element::String(x), None))
       );

named!(parse_list<(Element,Option<&[u8]>) >,
       chain!(
           char!('(') ~
               v: many0!(parse_sexp) ~
               after_list: opt!(nom::multispace) ~ // sometimes there is space after a closing bracket, this would not be caught by parse_sexp
               char!(')'),
           || (Element::List(v), after_list) )
       );

// TODO: consider lines with just spaces and a nl as also nl
named!(line_ending<usize>,
       chain!(
           opt!(nom::space) ~
               c: opt!(is_a!(b"\r\n"))
               , || match c { None => 0, Some(ref x) => x.len(), }
               )
       );

named!(parse_sexp<Sexp>,
           chain!(
               indent: opt!(nom::space) ~
                   s_after_list: alt!(parse_list | parse_string) ~
                   nl: line_ending
                   ,
               || {
                   let s = s_after_list.0.clone();
                   let after_list = s_after_list.1;
                   let indent = match indent {
                       None => String::new(),
                       Some(x) => String::from(str::from_utf8(x).unwrap()),
                   };
                   let after_list = match after_list {
                       None => String::new(),
                       Some(x) => String::from(str::from_utf8(x).unwrap()),
                   };
                   Sexp::new(s, indent, nl, after_list)
               })
       );

// internal tests
#[test]
fn test_qstring1() {
    let x = parse_string(&b"\"hello world\""[..]);
    match x {
        nom::IResult::Done(_,y) => {
            let (e, _) = y;
            match e {
                Element::String(f) => assert_eq!(String::from("hello world"), f),
                _ => panic!("not string"),
            }
        },
        _ => panic!("parser not done"),
    }
}

/*
#[test]
#[should_panic(expected="assertion failed: `(left == right)` (left: `Incomplete(Size(1))`, right: `Done([], \"hello\")`)")]
fn test_qstring2() {
    parse_string(&b"\"hello"[..]);
}
*/

#[cfg(test)]
mod tests {
    use super::*;

    #[allow(dead_code)]
    fn check_parse_res(s: &str, o:&str) {
        let e = parse_str(s).unwrap();
        let t = format!("{}", e);
        assert_eq!(o, t)
    }
    #[allow(dead_code)]
    fn check_parse(s: &str) {
        let e = parse_str(s).unwrap();
        let t = format!("{}", e);
        assert_eq!(s, t)
    }

    #[allow(dead_code)]
    fn parse_fail(s: &str) {
        parse_str(s).unwrap();
    }
    #[allow(dead_code)]
    fn check_compact(s: &str, o:&str) {
        let e = parse_str(s).unwrap();
        let t = format!("{}", compact(&e));
        assert_eq!(o, t)
    }

    #[test]
    fn test_empty() { check_parse("") }

    #[test]
    fn test_empty_qstring() { check_parse("(hello \"\")") }

    #[test]
    fn test_minimal() { check_parse("()") }

    #[test]
    fn test_string() { check_parse("hello") }

    #[test]
    fn test_qstring_a() { check_parse_res("\"hello\"", "hello") }

    #[test]
    fn test_qstring_a2() { check_parse("\"hello world\"") }

    #[test]
    fn test_qstring_a3() { check_parse("\"hello(world)\"") }

    #[test]
    fn test_number() { check_parse("1.3") }

    #[test]
    fn test_float_vs_int() { check_parse("2.0") }

    #[test]
    fn test_double() { check_parse("(())") }

    #[test]
    fn test_br_string() { check_parse("(world)") }

    #[test]
    fn test_br_qstring() { check_parse_res("(\"world\")", "(world)") }

    #[test]
    fn test_br_int() { check_parse("(42)") }

    #[test]
    fn test_br_float() { check_parse("(12.7)") }

    #[test]
    fn test_br_qbrstring() { check_parse("(\"(()\")") }

    #[test]
    fn test_number_string() { check_parse("567A_WZ") }

    #[test]
    #[should_panic(expected="called `Result::unwrap()` on an `Err` value: \"incomplete: Size(2)\"")]
    fn test_invalid1() { parse_fail("(") }

    #[test]
    #[should_panic(expected="called `Result::unwrap()` on an `Err` value: \"parse error: Alt |)|\"")]
    fn test_invalid2() { parse_fail(")") }

    #[test]
    #[should_panic(expected="incomplete: Size")]
    fn test_invalid3() { parse_fail("\"hello") }

    #[test]
    fn test_complex() { check_parse("(module SWITCH_3W_SIDE_MMP221-R (layer F.Cu) (descr \"\") (pad 1 thru_hole rect (size 1.2 1.2) (at -2.5 -1.6 0) (layers *.Cu *.Mask) (drill 0.8)) (pad 2 thru_hole rect (size 1.2 1.2) (at 0.0 -1.6 0) (layers *.Cu *.Mask) (drill 0.8)) (pad 3 thru_hole rect (size 1.2 1.2) (at 2.5 -1.6 0) (layers *.Cu *.Mask) (drill 0.8)) (pad 5 thru_hole rect (size 1.2 1.2) (at 0.0 1.6 0) (layers *.Cu *.Mask) (drill 0.8)) (pad 6 thru_hole rect (size 1.2 1.2) (at -2.5 1.6 0) (layers *.Cu *.Mask) (drill 0.8)) (pad 4 thru_hole rect (size 1.2 1.2) (at 2.5 1.6 0) (layers *.Cu *.Mask) (drill 0.8)) (fp_line (start -4.5 -1.75) (end 4.5 -1.75) (layer F.SilkS) (width 0.127)) (fp_line (start 4.5 -1.75) (end 4.5 1.75) (layer F.SilkS) (width 0.127)) (fp_line (start 4.5 1.75) (end -4.5 1.75) (layer F.SilkS) (width 0.127)) (fp_line (start -4.5 1.75) (end -4.5 -1.75) (layer F.SilkS) (width 0.127)))") }

    #[test]
    fn test_multiline() {
        check_parse("\
(hello \"test it\"
    (foo bar)
    (mars venus)
)
")
    }

    #[test]
    fn test_multiline2() {
        check_parse("\
(hello world
  mad
    (world)
  not)")
    }

    #[test]
    fn test_multiline_two_empty() {
        check_parse("\
(hello

world)")
    }

    #[test]
    fn test_compact1() {
        check_compact("( hello
world \"foo
  bar\"
     (baz)
)", "(hello world \"foo
  bar\" (baz))")
    }

    #[test]
    fn test_fail_pcb() {
        check_parse("\
(kicad_pcb (version 4) (host pcbnew \"(2015-05-31 BZR 5692)-product\")
  (general
  )
)")
    }
}

<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

pub struct DatasetName(String);
pub struct BlankNode(u64);
pub struct IRI(String);
pub struct DefaultGraph {}
pub struct LangTag(String);
pub struct LangLiteral {
    pub value: String,
    pub lang_tag: LangTag,
}
pub struct UnknownLiteral {
    pub value: String,
    pub r#type: IRI,
}

pub enum Literal {
    LangLiteral(LangLiteral),
    StringLiteral(String),
    BooleanLiteral(bool),
    LongLiteral(i64),
    DoubleLiteral(f64),
    UnknownLiteral(UnknownLiteral),
}

pub enum Range {
    LangLiteralRange {
        start: LangLiteral,
        stop: LangLiteral,
    },
    StringLiteralRange {
        start: String,
        stop: String,
    },
    LongLiteralRange {
        start: i64,
        stop: i64,
    },
    DoubleLiteralRange {
        start: f64,
        stop: f64,
    },
}

pub enum Subject {
    IRI(IRI),
    BlankNode(BlankNode),
    DefaultGraph(DefaultGraph),
}

pub enum Predicate {
    IRI(IRI),
}

pub enum Object {
    Subject(Subject),
    Literal(Literal),
}

pub enum Graph {
    IRI(IRI),
    BlankNode(BlankNode),
    DefaultGraph(DefaultGraph),
}

pub struct Statement {
    pub subject: Subject,
    pub predicate: Predicate,
    pub object: Object,
}
pub struct PersistedStatement {
    pub dataset: DatasetName,
    pub statement: Statement,
    pub graph: Graph,
}

//val a: IRI = IRI("http://www.w3.org/1999/02/22-rdf-syntax-ns#type").getOrElse(???)
//fn validLangTag(langTag: String) -> Boolean =
//  "[a-zA-Z]+(-[a-zA-Z0-9]+)*".r.matches(langTag)

pub struct LigatureError(String);

pub trait Ligature {
    fn all_datasets(&self) -> Box<dyn Iterator<Item = DatasetName>>;
    fn match_datasets(&self, prefix: String) -> Box<dyn Iterator<Item = DatasetName>>;
    fn match_datasets_range(
        &self,
        from: String,
        to: String,
    ) -> Box<dyn Iterator<Item = DatasetName>>;
    fn create_dataset(&self, dataset: DatasetName) -> Result<DatasetName, LigatureError>;
    fn delete_dataset(&self, dataset: DatasetName) -> Result<DatasetName, LigatureError>;
    fn query(&self, dataset: DatasetName) -> Result<Box<dyn QueryTx>, LigatureError>;
    fn write(&self, dataset: DatasetName) -> Result<Box<dyn WriteTx>, LigatureError>;
}

pub trait QueryTx {
    fn all_statements(&self) -> Box<dyn Iterator<Item = PersistedStatement>>;
    fn match_statements(
        &self,
        subject: Option<Subject>,
        predicate: Option<Predicate>,
        object: Option<Object>,
        graph: Option<Graph>,
    ) -> Box<dyn Iterator<Item = PersistedStatement>>;
    fn match_statements_range(
        &self,
        subject: Option<Subject>,
        predicate: Option<Predicate>,
        graph: Option<Graph>,
        range: Range,
    ) -> Box<dyn Iterator<Item = PersistedStatement>>;
}

pub trait WriteTx {
    fn new_blank_node(&self) -> Result<BlankNode, LigatureError>;
    fn add_statement(
        &self,
        statement: Statement,
        graph: Graph,
    ) -> Result<PersistedStatement, LigatureError>;
    fn remove_statement(
        &self,
        statement: Statement,
        graph: Graph,
    ) -> Result<Statement, LigatureError>;
    fn cancel(&self) -> Result<(), LigatureError>;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn valid_dataset_names() {
        //   assert(!validNamedNode(NamedNode("")))
        //   assert(validNamedNode(NamedNode("http://localhost/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost(/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost{/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost\\/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost</people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost>/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost[/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost]/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost\"/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost'/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost`/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost\t/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost\n/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost /people/7")))
        //   assert(validNamedNode(NamedNode("hello")))
        //   assert(validNamedNode(NamedNode("_:")))
        //   assert(validNamedNode(NamedNode("_:valid")))
        //   assert(validNamedNode(NamedNode("_:1")))
        //   assert(validNamedNode(NamedNode("_:1344")))
    }

    #[test]
    fn valid_lang_tags() {
        // assert(!validLangTag(""))
        // assert(validLangTag("en"))
        // assert(!validLangTag("en-"))
        // assert(validLangTag("en-fr"))
        // assert(!validLangTag("en-fr-"))
        // assert(validLangTag("en-fr-sp"))
        // assert(validLangTag("ennnenefnk-dkfjkjfl-dfakjelfkjalkf-fakjeflkajlkfj"))
        // assert(!validLangTag("en-fr-ef "))
    }
}
<commit_msg>first pass at adding rustdocs, closes #168<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#![deny(missing_docs)]
//#![deny(missing_doc_example)] <-- for later, when I'm swole

//! This module is the main module for the Ligature project.
//! It represents to common types and traits used by Ligature.

/// A string that represents a Dataset's Name.
/// Currently can only be ASCII text separated by /
/// TODO add validator and tests
#[derive(Debug)]
pub struct DatasetName(String);

/// A node that is only identified by a unique u64 id.
#[derive(Debug)]
pub struct BlankNode(u64);

/// An IRI is represented via https://www.ietf.org/rfc/rfc3987.txt
/// TODO add validator and tests
#[derive(Debug)]
pub struct IRI(String);

/// A unit struct used to represent the concept of a Default Graph in a quad store.
#[derive(Debug)]
pub struct DefaultGraph;

/// A wrapper type that represents a language tag.
/// Represented via `[a-zA-Z]+ ('-' [a-zA-Z0-9]+)*`
/// TODO add validator and tests
#[derive(Debug)]
pub struct LangTag(String);

/// A struct containing text and a language tag that denotes what language the text is expressed in.
/// TODO add validator and tests
#[derive(Debug)]
pub struct LangLiteral {
    /// The String literal that is represented by this LangLiteral
    pub value: String,
    /// The LangTag that represents the language used to express the value.
    pub lang_tag: LangTag,
}

/// A struct containing a value represented as a String and the type of the value represented by an IRI.
/// TODO add validator and tests
/// TODO probably need a function that double checks a given UnknownLiteral is actually unknown
#[derive(Debug)]
pub struct UnknownLiteral {
    /// The value of this literal represented as a String.
    pub value: String,
    /// The IRI that represents this type.
    pub r#type: IRI,
}

/// An enum that represents all the currently supported literal types.
#[derive(Debug)]
pub enum Literal {
    /// A tagged LangLiteral used for an RDF literal
    LangLiteral(LangLiteral),
    /// A tagged String used for an RDF literal
    StringLiteral(String),
    /// A tagged bool used for an RDF literal
    BooleanLiteral(bool),
    /// A tagged i64 used for an RDF literal
    LongLiteral(i64),
    /// A tagged f64 used for an RDF literal
    DoubleLiteral(f64),
    /// A tagged UnknownLiteral used for an RDF literal
    UnknownLiteral(UnknownLiteral),
}

/// A set of enums used to express range queries when it makes sense for that type (ie no support for BooleanLiteralRange or UnknownLiteralRange since they don't make sense).
#[derive(Debug)]
pub enum Range {
    /// Represents a range of LangLiterals
    /// Note: LangTag needs to match for any comparison to take place.
    LangLiteralRange {
        /// The starting LangLiteral (inclusive)
        start: LangLiteral,
        /// The end LangLiteral (exclusive)
        end: LangLiteral,
    },
    /// Represents a String range using basic String comparisons.
    StringLiteralRange {
        /// The starting String (inclusive)
        start: String,
        /// The end String (exclusive)
        end: String,
    },
    /// Represents a String range using basic i64 comparisons.
    LongLiteralRange {
        /// The starting i64 (inclusive)
        start: i64,
        /// The end i64 (exclusive)
        end: i64,
    },
    /// Represents a String range using basic f64 comparisons.
    DoubleLiteralRange {
        /// The starting f64 (inclusive)
        start: f64,
        /// The end f64 (exclusive)
        end: f64,
    },
}

/// The set of valid types that can be used as a Subject.
/// TODO add validator and tests
#[derive(Debug)]
pub enum Subject {
    /// A tagged IRI used as a Subject.
    IRI(IRI),
    /// A tagged BlankNode used as a Subject.
    BlankNode(BlankNode),
    /// A tagged DefaultGraph used as a Subject.
    DefaultGraph(DefaultGraph),
}

/// The set of valid types that can be used as a Predicate.
/// TODO add validator and tests
#[derive(Debug)]
pub enum Predicate {
    /// A tagged IRI used as a Predicate.
    IRI(IRI),
}

/// The set of valid types that can be used as an Object.
/// TODO add validator and tests
#[derive(Debug)]
pub enum Object {
    /// A tagged Subject used as an Object.
    Subject(Subject),
    /// A tagged Literal used as an Object.
    Literal(Literal),
}

/// The set of valid types that can be used as a Graph name.
/// TODO add validator and tests
#[derive(Debug)]
pub enum Graph {
    /// A tagged IRI used as a Graph.
    IRI(IRI),
    /// A tagged BlankNode used as a Graph.
    BlankNode(BlankNode),
    /// A tagged DefaultGraph used as a Graph.
    DefaultGraph(DefaultGraph),
}

/// A Statement is a grouping of Subject, Predicate, and Object.
/// TODO add validator and tests
#[derive(Debug)]
pub struct Statement {
    /// The Subject of a Statement
    pub subject: Subject,
    /// The Predicate of a Statement
    pub predicate: Predicate,
    /// The Object of a Statement
    pub object: Object,
}

/// A PersistedStatement is a Statement along with a DatasetName and Graph that that Statement belongs to.
/// TODO add validator and tests
#[derive(Debug)]
pub struct PersistedStatement {
    /// The Dataset this Statement is persisted in
    pub dataset: DatasetName,
    /// The Statement that is persisted
    pub statement: Statement,
    /// The Graph this Statement is persisted in
    pub graph: Graph,
}

//val a: IRI = IRI("http://www.w3.org/1999/02/22-rdf-syntax-ns#type").getOrElse(???)
//fn validLangTag(langTag: String) -> Boolean =
//  "[a-zA-Z]+(-[a-zA-Z0-9]+)*".r.matches(langTag)

/// A general struct for representing errors involving Ligature.
/// TODO should probably be an enum with a bunch of specific cases
#[derive(Debug)]
pub struct LigatureError(String);

/// A trait that all Ligature implementations implement.
pub trait Ligature {
    /// Returns all Datasets in a Ligature instance.
    fn all_datasets(&self) -> Box<dyn Iterator<Item = DatasetName>>;

    /// Returns all Datasets in a Ligature instance that start with the given prefix.
    fn match_datasets(&self, prefix: String) -> Box<dyn Iterator<Item = DatasetName>>;

    /// Returns all Datasets in a Ligature instance that are in a given range (inclusive, exclusive].
    fn match_datasets_range(
        &self,
        start: String,
        end: String,
    ) -> Box<dyn Iterator<Item = DatasetName>>;

    /// Creates a dataset with the given name.
    /// TODO should probably return its own error type { InvalidDatasetName, DatasetExists, CouldNotCreateDataset }
    fn create_dataset(&self, dataset: DatasetName) -> Result<(), LigatureError>;

    /// Deletes a dataset with the given name.
    /// TODO should probably return its own error type { InvalidDatasetName, CouldNotDeleteDataset }
    fn delete_dataset(&self, dataset: DatasetName) -> Result<DatasetName, LigatureError>;

    /// Initiazes a QueryTx
    /// TODO should probably return its own error type CouldNotInitializeQueryTx
    fn query(&self, dataset: DatasetName) -> Result<Box<dyn QueryTx>, LigatureError>;

    /// Initiazes a WriteTx
    /// TODO should probably return its own error type CouldNotInitializeWriteTx
    fn write(&self, dataset: DatasetName) -> Result<Box<dyn WriteTx>, LigatureError>;
}

/// Represents a QueryTx within the context of a Ligature instance and a single Dataset
pub trait QueryTx {
    /// Returns all Statements in this Dataset as PersistedStatements.
    /// TODO should probably return a Result
    fn all_statements(&self) -> Box<dyn Iterator<Item = PersistedStatement>>;

    /// Retuns all Statements that match the given criteria.
    /// If a parameter is None then it matches all, so passing all Nones is the same as calling all_statements.
    /// TODO should return a Result
    fn match_statements(
        &self,
        subject: Option<Subject>,
        predicate: Option<Predicate>,
        object: Option<Object>,
        graph: Option<Graph>,
    ) -> Box<dyn Iterator<Item = PersistedStatement>>;

    /// Retuns all Statements that match the given criteria.
    /// If a parameter is None then it matches all.
    /// TODO should return a Result
    fn match_statements_range(
        &self,
        subject: Option<Subject>,
        predicate: Option<Predicate>,
        graph: Option<Graph>,
        range: Range,
    ) -> Box<dyn Iterator<Item = PersistedStatement>>;
}

/// Represents a WriteTx within the context of a Ligature instance and a single Dataset
pub trait WriteTx {
    /// Creates a new, unique BlankNode within this Dataset.
    /// Note: BlankNodes are shared across named graphs in a given Dataset.
    fn new_blank_node(&self) -> Result<BlankNode, LigatureError>;

    /// Adds a given Statement to this Dataset.
    /// If the Statement already exists nothing happens.
    /// Note: Potentally could trigger a ValidationError
    fn add_statement(
        &self,
        statement: Statement,
        graph: Graph,
    ) -> Result<PersistedStatement, LigatureError>;

    /// Removes a given Statement from this Dataset.
    /// If the Statement doesn't exist nothing happens.
    /// Note: Potentally could trigger a ValidationError.
    fn remove_statement(
        &self,
        statement: Statement,
        graph: Graph,
    ) -> Result<Statement, LigatureError>;

    /// Cancels this transaction so that none of the changes made so far will be stored.
    /// This also closes this transaction so no other methods can be called.
    fn cancel(&self) -> Result<(), LigatureError>;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn valid_dataset_names() {
        //   assert(!validNamedNode(NamedNode("")))
        //   assert(validNamedNode(NamedNode("http://localhost/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost(/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost{/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost\\/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost</people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost>/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost[/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost]/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost\"/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost'/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost`/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost\t/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost\n/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost /people/7")))
        //   assert(validNamedNode(NamedNode("hello")))
        //   assert(validNamedNode(NamedNode("_:")))
        //   assert(validNamedNode(NamedNode("_:valid")))
        //   assert(validNamedNode(NamedNode("_:1")))
        //   assert(validNamedNode(NamedNode("_:1344")))
    }

    #[test]
    fn valid_lang_tags() {
        // assert(!validLangTag(""))
        // assert(validLangTag("en"))
        // assert(!validLangTag("en-"))
        // assert(validLangTag("en-fr"))
        // assert(!validLangTag("en-fr-"))
        // assert(validLangTag("en-fr-sp"))
        // assert(validLangTag("ennnenefnk-dkfjkjfl-dfakjelfkjalkf-fakjeflkajlkfj"))
        // assert(!validLangTag("en-fr-ef "))
    }
}
<|endoftext|>