<commit_before>#![feature(test)]
#![feature(integer_atomics)]
#![allow(match_ref_pats)]
#![allow(too_many_arguments)]
#![allow(unknown_lints)]

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

extern crate ignore;
extern crate regex;
extern crate colored;

#[macro_use] mod macros;
pub mod test;
pub mod types;
pub mod error;
pub mod cli_helpers;
pub mod gitignore;

pub mod prelude {

    //use std::os::unix::fs::{MetadataExt};
    use std::sync::Arc;
    use std::fs;
    use std::path::{PathBuf, Path};
    use regex::{Regex, RegexSet};
    use types::*;
    use gitignore::*;
    use colored::*;
    use std::io::prelude::*;
    use std::process::exit;
    use ignore::{WalkBuilder, WalkState};
    use std::fs::{Metadata, File};
    use std::sync::atomic::{AtomicU64, Ordering};

    #[cfg(not(target_os = "windows"))]
    use std::os::unix::fs::PermissionsExt;

    pub use cli_helpers::*;
    pub use error::*;

    /// Helper function to determine whether a path points
    ///
    /// Rules:
    /// - if the file extension of that is that of an artifact, return true
    /// - if the file is executable and included in the .gitignore, return true
    /// - return false otherwise
    ///
    /// Explanation of extensions:
    /// - `.a`, `.la`, `.o`, `.lo`, `.so.*`:
    /// - `.S`: assembly
    /// - .ll, `.bc`: llvm
    /// - `.keter`: keter
    /// - `.d`: make
    /// - `.rlib`, `.crate`: rust
    /// - `.hi`, `.dyn_hi`, `.dyn_o`, `.p_hi`, `.p_o`, `.prof`, `.dump-.*`, `.tix`: GHC
    /// - `.webapp`: Web app manifest
    /// - `.js.externs`, `.jsexe`, `.min.js`:
    /// - `.ibc`: Idris
    /// - `.toc`, `.aux`, `.fdb_latexmk`, `.fls`: TeX
    /// - `.egg-info`, `.whl`, `.pyc`: python
    /// - `.js_a`, `.js_hi`, `.js_o`: GHCJS
    /// - `.vmb`: Vim
    /// - `.crx`: chrome
    /// - `.elmo`, `.elmi`: Elm
    /// - `.mod`: FORTRAN
    /// - `.ji`, `.jld`: julia
    #[cfg(not(target_os = "windows"))]
    pub fn is_artifact(path_str: &str, full_path: &str, re: Option<&Regex>, metadata: &Metadata, gitignore: &Option<RegexSet>) -> bool {

        // match on the user's expression if it exists
        if let Some(r) = re {
            r.is_match(path_str) // FIXME include .gitignore as well?
        }

        // otherwise, use builtin expressions
        else {
            lazy_static! {
                static ref REGEX: Regex =
                    Regex::new(r".*?\.(a|la|lo|o|ll|keter|bc|dyn_o|out|d|rlib|crate|min\.js|hi|dyn_hi|S|jsexe|webapp|js\.externs|ibc|toc|aux|fdb_latexmk|fls|egg-info|whl|js_a|js_hi|jld|ji|js_o|so.*|dump-.*|vmb|crx|orig|elmo|elmi|pyc|mod|p_hi|p_o|prof|tix)$") // TODO reorder regex
                    .unwrap();
            }
            lazy_static! {
                static ref REGEX_GITIGNORE: Regex =
                    Regex::new(r".*?\.(stats|conf|h|cache.*|dat|pc|info)$")
                    .unwrap();
            }
            if REGEX.is_match(path_str) { true }
            else if let &Some(ref x) = gitignore {
                if metadata.permissions().mode() == 0o755 || REGEX_GITIGNORE.is_match(path_str)
                    { x.is_match(full_path) }
                else { false }
            }
            else { false }
        }
    }

    #[cfg(target_os = "windows")]
    pub fn is_artifact(path_str: &str, full_path: &str, re: Option<&Regex>, _: &Metadata, gitignore:&Option<RegexSet>) -> bool {
        if let Some(r) = re {
            r.is_match(path_str)
        }
        else {
            lazy_static! {
                static ref REGEX: Regex =
                    Regex::new(r".*?\.(exe|a|la|o|ll|keter|bc|dyn_o|out|d|rlib|crate|min\.js|hi|dyn_hi|jsexe|webapp|js\.externs|ibc|toc|aux|fdb_latexmk|fls|egg-info|whl|js_a|js_hi|jld|ji|js_o|so.*|dump-.*|vmb|crx|orig|elmo|elmi|pyc|mod|p_hi|p_o|prof|tix)$")
                    .unwrap();
            }
            lazy_static! {
                static ref REGEX_GITIGNORE: Regex =
                    Regex::new(r".*?\.(stats|conf|h|cache.*)$")
                    .unwrap();
            }
            if REGEX.is_match(&path_str) { true }

            else if let &Some(ref x) = gitignore {
                if REGEX_GITIGNORE.is_match(path_str) {
                    x.is_match(full_path)
                } else { false }
            }
            else { false }
        }
    }

    pub fn read_parallel(in_paths: &Path,
                         max_depth: Option<usize>, // optionally include a max depth to which to recurse
                         _: Option<&Regex>,
                         show_hidden: bool,
                         silent: bool,
                         artifacts_only: bool,
                         follow_symlinks: bool) -> FileSize {

        // create new walk + set file size to zero
        let mut builder = WalkBuilder::new(in_paths);
        //let mut dir_size = AtomicU64::new(0);
        let dir_size = Arc::new(AtomicU64::new(0));

        // set options for our walk
        builder.max_depth(max_depth);
        builder.hidden(show_hidden);
        builder.follow_links(follow_symlinks);
        builder.ignore(false);
        builder.git_ignore(false);
        builder.git_exclude(false);
        builder.git_global(false);

        // runs loop
        builder.build_parallel().run(|| { let filesize_dir = dir_size.clone() ; Box::new(move |path| {

            if let Ok(p) = path {
                if p.path().is_file() {
                    if let Ok (f) = p.file_name().to_owned().into_string() {
                        if let Ok(metadata) = p.metadata() {
                            if !artifacts_only || is_artifact(&f, "", None, &metadata, &None) {
                                let file_size = FileSize::new(metadata.len());
                                filesize_dir.fetch_add(metadata.len(), Ordering::SeqCst);
                                if !silent {
                                    let formatted = format!("{}", file_size);
                                    if let Ok(s) = p.path().as_os_str().to_owned().into_string() {
                                        println!("{}\t {}", formatted.green(), s);
                                    }
                                    else {
                                        eprintln!("{}: ignoring invalid unicode {:?}", "Warning".yellow(), p.path())
                                    }
                                }
                            }
                        }
                        else if follow_symlinks {
                            let s = p.path().as_os_str().to_owned().into_string().expect("OS String failed to resolve");
                            eprintln!("{}: ignoring broken symlink at {}", "Warning".yellow(), s);
                        }
                    }
                    else {
                        eprintln!("{}: ignoring invalid unicode {:?}", "Warning".yellow(), p.path())
                    }
                }
            }
            else if let Err(e) = path {
                eprintln!("{}: failed to get path data from:\n{:?}", "Warning".yellow(), e);
            }
            ; WalkState::Continue }) } );

        FileSize::new(Arc::try_unwrap(dir_size).unwrap().into_inner())

    }

    /// Function to process directory contents and return a `FileTree` struct.
    pub fn read_all(in_paths: &PathBuf,
                          depth: u8,
                          max_depth: Option<u8>,
                          artifact_regex: Option<&Regex>,
                          excludes: Option<&Regex>,
                          silent: bool,
                          maybe_gitignore: &Option<RegexSet>,
                          with_gitignore: bool,
                          artifacts_only: bool) -> FileTree {

        // make this an input later
        let force_parallel = false;

        // attempt to read the .gitignore
        let mut tree = FileTree::new();
        let gitignore = if with_gitignore {
            if let Some(ref gitignore) = *maybe_gitignore { Some(gitignore.to_owned()) } // TODO get rid of this
            else {
                let mut gitignore_path = in_paths.clone();
                gitignore_path.push(".gitignore");
                if let Ok(mut file) = File::open(gitignore_path.clone()) {
                    let mut contents = String::new();
                    file.read_to_string(&mut contents)
                        .expect("File read failed.");
                    Some(file_contents_to_regex(&contents, &gitignore_path))
                } else { None }
            }
        } else { None };

        // try to read directory contents
        if let Ok(paths) = fs::read_dir(in_paths) {

            // iterate over all the entries in the directory
            for p in paths {
                let path = p.unwrap().path(); // TODO no unwraps; idk what this error would be though.
                let (path_string, bool_loop): (String, bool) = if let Ok(x) = path.clone().into_os_string().into_string() {
                    let bool_loop = match excludes {
                        Some(ex) => !ex.is_match(&x),
                        _ => true,
                    };

                    (x, bool_loop)
                }
                else {
                    eprintln!("{}: skipping invalid unicode filepath at {:?}", "Warning".yellow(), path); // TODO consider byte matching here?
                    ("".to_string(), false)
                };

                // only consider path if we're not using regex excludes or if they don't match the
                // exclusion regex
                if bool_loop {

                    // if this fails, it's probably because `path` is a broken symlink
                    if let Ok(metadata) = fs::symlink_metadata(&path) {

                        // append file size/name for a file
                        if metadata.is_file() {
                            let file = path.file_name().unwrap().to_owned().into_string().unwrap(); // ok because we already checked // TODO
                            if !artifacts_only || is_artifact(&file, &path_string, artifact_regex, &metadata, &gitignore) { // should check size before whether it's an artifact?
                                let file_size = FileSize::new(metadata.len());//blocks() * 512);
                                tree.push(path_string, file_size, None, depth + 1, false);
                            }
                        }

                        // otherwise, go deeper
                        else if metadata.is_dir() { // TODO iterate in parallel if we've hit max depth.
                            if let Some(d) = max_depth {
                                if depth > d - 1 {
                                    let dir_size = if !artifacts_only && force_parallel {
                                        read_parallel(&path, None, None, true, true, artifacts_only, false)
                                    }
                                    else {
                                        let subtree = read_all(&path, depth + 1, max_depth, artifact_regex, excludes, silent, &gitignore, with_gitignore, artifacts_only);
                                        subtree.file_size
                                    };
                                    tree.push(path_string, dir_size, None, depth + 1, true);
                                }
                                else {
                                    let mut subtree = read_all(&path, depth + 1, max_depth, artifact_regex, excludes, silent, &gitignore, with_gitignore, artifacts_only);
                                    let dir_size = subtree.file_size;
                                    tree.push(path_string, dir_size, Some(&mut subtree), depth + 1, true);
                                }
                            }
                            else {
                                let mut subtree = read_all(&path, depth + 1, max_depth, artifact_regex, excludes, silent, &gitignore, with_gitignore, artifacts_only);
                                let dir_size = subtree.file_size;
                                tree.push(path_string, dir_size, Some(&mut subtree), depth + 1, true);
                            }
                        }
                    }
                    else if !silent { eprintln!("{}: ignoring symlink at {}", "Warning".yellow(), path.display()); }
                }
            }
        }

        // if we can't read the directory contents, figure out why
        // 1: check the path exists
        else if !in_paths.exists() {
            eprintln!("{}: path '{}' does not exist.", "Error".red(), &in_paths.display());
            exit(0x0001);
        }
        // 2: check the path is actually a directory
        else if !in_paths.is_dir() {
            eprintln!("{}: {} is not a directory.", "Error".red(), &in_paths.display());
            exit(0x0001);
        }
        // 3: otherwise, give a warning about permissions
        else if !silent {
            eprintln!("{}: permission denied for directory: {}", "Warning".yellow(), &in_paths.display());
        }

        tree
    }
}
<commit_msg>works faster?<commit_after>#![feature(test)]
#![feature(integer_atomics)]
#![allow(match_ref_pats)]
#![allow(too_many_arguments)]
#![allow(unknown_lints)]

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

extern crate ignore;
extern crate regex;
extern crate colored;

#[macro_use] mod macros;
pub mod test;
pub mod types;
pub mod error;
pub mod cli_helpers;
pub mod gitignore;

pub mod prelude {

    //use std::os::unix::fs::{MetadataExt};
    use std::sync::Arc;
    use std::fs;
    use std::path::{PathBuf, Path};
    use regex::{Regex, RegexSet};
    use types::*;
    use gitignore::*;
    use colored::*;
    use std::io::prelude::*;
    use std::process::exit;
    use ignore::{WalkBuilder, WalkState};
    use std::fs::{Metadata, File};
    use std::cmp::min;
    use std::sync::atomic::{AtomicU64, Ordering};

    #[cfg(not(target_os = "windows"))]
    use std::os::unix::fs::PermissionsExt;

    pub use cli_helpers::*;
    pub use error::*;

    /// Helper function to determine whether a path points
    ///
    /// Rules:
    /// - if the file extension of that is that of an artifact, return true
    /// - if the file is executable and included in the .gitignore, return true
    /// - return false otherwise
    ///
    /// Explanation of extensions:
    /// - `.a`, `.la`, `.o`, `.lo`, `.so.*`:
    /// - `.S`: assembly
    /// - .ll, `.bc`: llvm
    /// - `.keter`: keter
    /// - `.d`: make
    /// - `.rlib`, `.crate`: rust
    /// - `.hi`, `.dyn_hi`, `.dyn_o`, `.p_hi`, `.p_o`, `.prof`, `.dump-.*`, `.tix`: GHC
    /// - `.webapp`: Web app manifest
    /// - `.js.externs`, `.jsexe`, `.min.js`:
    /// - `.ibc`: Idris
    /// - `.toc`, `.aux`, `.fdb_latexmk`, `.fls`: TeX
    /// - `.egg-info`, `.whl`, `.pyc`: python
    /// - `.js_a`, `.js_hi`, `.js_o`: GHCJS
    /// - `.vmb`: Vim
    /// - `.crx`: chrome
    /// - `.elmo`, `.elmi`: Elm
    /// - `.mod`: FORTRAN
    /// - `.ji`, `.jld`: julia
    #[cfg(not(target_os = "windows"))]
    pub fn is_artifact(path_str: &str, full_path: &str, re: Option<&Regex>, metadata: &Metadata, gitignore: &Option<RegexSet>) -> bool {

        // match on the user's expression if it exists
        if let Some(r) = re {
            r.is_match(path_str) // FIXME include .gitignore as well?
        }

        // otherwise, use builtin expressions
        else {
            lazy_static! {
                static ref REGEX: Regex =
                    Regex::new(r".*?\.(a|la|lo|o|ll|keter|bc|dyn_o|out|d|rlib|crate|min\.js|hi|dyn_hi|S|jsexe|webapp|js\.externs|ibc|toc|aux|fdb_latexmk|fls|egg-info|whl|js_a|js_hi|jld|ji|js_o|so.*|dump-.*|vmb|crx|orig|elmo|elmi|pyc|mod|p_hi|p_o|prof|tix)$") // TODO reorder regex
                    .unwrap();
            }
            lazy_static! {
                static ref REGEX_GITIGNORE: Regex =
                    Regex::new(r".*?\.(stats|conf|h|cache.*|dat|pc|info)$")
                    .unwrap();
            }
            if REGEX.is_match(path_str) { true }
            else if let &Some(ref x) = gitignore {
                if metadata.permissions().mode() == 0o755 || REGEX_GITIGNORE.is_match(path_str)
                    { x.is_match(full_path) }
                else { false }
            }
            else { false }
        }
    }

    #[cfg(target_os = "windows")]
    pub fn is_artifact(path_str: &str, full_path: &str, re: Option<&Regex>, _: &Metadata, gitignore:&Option<RegexSet>) -> bool {
        if let Some(r) = re {
            r.is_match(path_str)
        }
        else {
            lazy_static! {
                static ref REGEX: Regex =
                    Regex::new(r".*?\.(exe|a|la|o|ll|keter|bc|dyn_o|out|d|rlib|crate|min\.js|hi|dyn_hi|jsexe|webapp|js\.externs|ibc|toc|aux|fdb_latexmk|fls|egg-info|whl|js_a|js_hi|jld|ji|js_o|so.*|dump-.*|vmb|crx|orig|elmo|elmi|pyc|mod|p_hi|p_o|prof|tix)$")
                    .unwrap();
            }
            lazy_static! {
                static ref REGEX_GITIGNORE: Regex =
                    Regex::new(r".*?\.(stats|conf|h|cache.*)$")
                    .unwrap();
            }
            if REGEX.is_match(&path_str) { true }

            else if let &Some(ref x) = gitignore {
                if REGEX_GITIGNORE.is_match(path_str) {
                    x.is_match(full_path)
                } else { false }
            }
            else { false }
        }
    }

    pub fn read_parallel(in_paths: &Path,
                         max_depth: Option<usize>, // optionally include a max depth to which to recurse
                         _: Option<&Regex>,
                         show_hidden: bool,
                         silent: bool,
                         artifacts_only: bool,
                         follow_symlinks: bool) -> FileSize {

        // create new walk + set file size to zero
        let mut builder = WalkBuilder::new(in_paths);
        //let mut dir_size = AtomicU64::new(0);
        let dir_size = Arc::new(AtomicU64::new(0));

        // set options for our walk
        builder.max_depth(max_depth);
        builder.hidden(show_hidden);
        builder.follow_links(follow_symlinks);
        builder.ignore(false);
        builder.git_ignore(false);
        builder.git_exclude(false);
        builder.git_global(false);

        // runs loop
        builder.build_parallel().run(|| { let filesize_dir = dir_size.clone() ; Box::new(move |path| {

            if let Ok(p) = path {
                if p.path().is_file() {
                    if let Ok (f) = p.file_name().to_owned().into_string() {
                        if let Ok(metadata) = p.metadata() {
                            if !artifacts_only || is_artifact(&f, "", None, &metadata, &None) {
                                let file_size = FileSize::new(metadata.len());
                                filesize_dir.fetch_add(metadata.len(), Ordering::SeqCst);
                                if !silent {
                                    let formatted = format!("{}", file_size);
                                    if let Ok(s) = p.path().as_os_str().to_owned().into_string() {
                                        println!("{}\t {}", formatted.green(), s);
                                    }
                                    else {
                                        eprintln!("{}: ignoring invalid unicode {:?}", "Warning".yellow(), p.path())
                                    }
                                }
                            }
                        }
                        else if follow_symlinks {
                            let s = p.path().as_os_str().to_owned().into_string().expect("OS String failed to resolve");
                            eprintln!("{}: ignoring broken symlink at {}", "Warning".yellow(), s);
                        }
                    }
                    else {
                        eprintln!("{}: ignoring invalid unicode {:?}", "Warning".yellow(), p.path())
                    }
                }
            }
            else if let Err(e) = path {
                eprintln!("{}: failed to get path data from:\n{:?}", "Warning".yellow(), e);
            }
            ; WalkState::Continue }) } );

        FileSize::new(Arc::try_unwrap(dir_size).unwrap().into_inner())

    }

    /// Function to process directory contents and return a `FileTree` struct.
    pub fn read_all(in_paths: &PathBuf,
                          depth: u8,
                          max_depth: Option<u8>,
                          artifact_regex: Option<&Regex>,
                          excludes: Option<&Regex>,
                          silent: bool,
                          maybe_gitignore: &Option<RegexSet>,
                          with_gitignore: bool,
                          artifacts_only: bool) -> FileTree {

        // make this an input later
        let force_parallel = false;

        // attempt to read the .gitignore
        let mut tree = FileTree::new();
        let gitignore = if with_gitignore {
            if let Some(ref gitignore) = *maybe_gitignore { Some(gitignore.to_owned()) } // TODO get rid of this
            else {
                let mut gitignore_path = in_paths.clone();
                gitignore_path.push(".gitignore");
                if let Ok(mut file) = File::open(gitignore_path.clone()) {
                    let mut contents = String::new();
                    file.read_to_string(&mut contents)
                        .expect("File read failed.");
                    Some(file_contents_to_regex(&contents, &gitignore_path))
                } else { None }
            }
        } else { None };

        // try to read directory contents
        if let Ok(paths) = fs::read_dir(in_paths) {

            // iterate over all the entries in the directory
            for p in paths {
                let path = p.unwrap().path(); // TODO no unwraps; idk what this error would be though.
                let (path_string, bool_loop): (String, bool) = if let Ok(x) = path.clone().into_os_string().into_string() {
                    let bool_loop = match excludes {
                        Some(ex) => !ex.is_match(&x),
                        _ => true,
                    };

                    (x, bool_loop)
                }
                else {
                    eprintln!("{}: skipping invalid unicode filepath at {:?}", "Warning".yellow(), path); // TODO consider byte matching here?
                    ("".to_string(), false)
                };

                // only consider path if we're not using regex excludes or if they don't match the
                // exclusion regex
                if bool_loop {

                    // if this fails, it's probably because `path` is a broken symlink
                    if let Ok(metadata) = fs::symlink_metadata(&path) {

                        // append file size/name for a file
                        if metadata.is_file() {
                            let file = path.file_name().unwrap().to_owned().into_string().unwrap(); // ok because we already checked // TODO
                            if !artifacts_only || is_artifact(&file, &path_string, artifact_regex, &metadata, &gitignore) { // should check size before whether it's an artifact?
                                let file_size = FileSize::new(metadata.len());//blocks() * 512);
                                tree.push(path_string, file_size, None, depth + 1, false);
                            }
                        }

                        // otherwise, go deeper
                        else if metadata.is_dir() { // TODO iterate in parallel if we've hit max depth.
                            if let Some(d) = max_depth {
                                if depth > d - min(depth, d) {
                                    let dir_size = if !artifacts_only && force_parallel {
                                        read_parallel(&path, None, None, true, true, artifacts_only, false)
                                    }
                                    else {
                                        let subtree = read_all(&path, depth + 1, max_depth, artifact_regex, excludes, silent, &gitignore, with_gitignore, artifacts_only);
                                        subtree.file_size
                                    };
                                    tree.push(path_string, dir_size, None, depth + 1, true);
                                }
                                else {
                                    let mut subtree = read_all(&path, depth + 1, max_depth, artifact_regex, excludes, silent, &gitignore, with_gitignore, artifacts_only);
                                    let dir_size = subtree.file_size;
                                    tree.push(path_string, dir_size, Some(&mut subtree), depth + 1, true);
                                }
                            }
                            else {
                                let mut subtree = read_all(&path, depth + 1, max_depth, artifact_regex, excludes, silent, &gitignore, with_gitignore, artifacts_only);
                                let dir_size = subtree.file_size;
                                tree.push(path_string, dir_size, Some(&mut subtree), depth + 1, true);
                            }
                        }
                    }
                    else if !silent { eprintln!("{}: ignoring symlink at {}", "Warning".yellow(), path.display()); }
                }
            }
        }

        // if we can't read the directory contents, figure out why
        // 1: check the path exists
        else if !in_paths.exists() {
            eprintln!("{}: path '{}' does not exist.", "Error".red(), &in_paths.display());
            exit(0x0001);
        }
        // 2: check the path is actually a directory
        else if !in_paths.is_dir() {
            eprintln!("{}: {} is not a directory.", "Error".red(), &in_paths.display());
            exit(0x0001);
        }
        // 3: otherwise, give a warning about permissions
        else if !silent {
            eprintln!("{}: permission denied for directory: {}", "Warning".yellow(), &in_paths.display());
        }

        tree
    }
}
<|endoftext|>


<commit_before>// Copyright 2015 Nicholas Bishop
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Vec2D is a very simple 2D container for storing rectangular data

#![deny(missing_docs)]

/// 2D coordinate
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Coord {
    /// X component
    pub x: usize,

    /// Y component
    pub y: usize
}

/// Rectangle defined by inclusive minimum and maximum coordinates
#[derive(Clone, Copy, Eq, Debug, PartialEq)]
pub struct Rect {
    /// Minimum coordinate (inclusive)
    min_coord: Coord,

    /// Maximum coordinate (inclusive)
    max_coord: Coord
}

/// Rectangle dimensions
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Size {
    /// Width of rectangle
    pub width: usize,
    /// Height of rectangle
    pub height: usize
}

/// Container for 2D data
#[derive(Clone, Debug)]
pub struct Vec2D<T> {
    elems: Vec<T>,
    size: Size
}

/// Iterator over a rectangle within a Vec2D
pub struct RectIter<'a, Elem: 'a> {
    grid: std::marker::PhantomData<&'a Vec2D<Elem>>,

    rect: Rect,
    cur_elem: *const Elem,
    cur_coord: Coord,
    stride: isize
}

/// Mutable iterator over a rectangle within a Vec2D
pub struct RectIterMut<'a, Elem: 'a> {
    grid: std::marker::PhantomData<&'a mut Vec2D<Elem>>,

    rect: Rect,
    cur_elem: *mut Elem,
    cur_coord: Coord,
    stride: isize
}

impl Coord {
    /// Create a coordinate at (x, y)
    pub fn new(x: usize, y: usize) -> Coord {
        Coord {
            x: x,
            y: y
        }
    }
}

impl Rect {
    /// Calculate rectangle width
    pub fn width(&self) -> usize {
        return self.max_coord.x - self.min_coord.x + 1;
    }

    /// Calculate rectangle height
    pub fn height(&self) -> usize {
        return self.max_coord.y - self.min_coord.y + 1;
    }

    /// Calculate rectangle size
    pub fn size(&self) -> Size {
        Size::new(self.width(), self.height())
    }
}

impl Size {
    /// Create a 2D size of (width, height)
    pub fn new(width: usize, height: usize) -> Size {
        Size {
            width: width,
            height: height
        }
    }

    /// width * height
    pub fn area(&self) -> usize {
        self.width * self.height
    }

    /// Return true if the coordinate fits within self's width and
    /// height, false otherwise.
    pub fn contains_coord(&self, coord: Coord) -> bool {
        coord.x < self.width && coord.y < self.height
    }

    /// Create a rectangle starting at (0, 0) with `self`'s size.
    pub fn rect(&self) -> Rect {
        Rect {
            min_coord: Coord::new(0, 0),
            max_coord: Coord::new(self.width - 1, self.height - 1)
        }
    }
}

impl<Elem: Copy> Vec2D<Elem> {
    /// Create a Vec2D with the given `size`. All elements are
    /// initialized as copies of the `example` element.
    pub fn from_example(size: Size, example: &Elem) -> Vec2D<Elem> {
        Vec2D {
            elems: vec![*example; size.area()],
            size: size
        }
    }

    /// Create a Vec2D with the given `size`. The contents are set to
    /// `src`. None is returned if the `size` does not match the
    /// length of `src`.
    pub fn from_vec(size: Size, src: Vec<Elem>) -> Option<Vec2D<Elem>> {
        if size.area() == src.len() {
            Some(Vec2D {
                elems: src,
                size: size
            })
        }
        else {
            None
        }
    }

    fn stride(&self, rect: &Rect) -> isize {
        (self.size.width + 1 - rect.width()) as isize
    }

    /// Create an iterator over a rectangular region of the
    /// Vec2D. None is returned if the given `rect` does not fit
    /// entirely within the Vec2D.
    pub fn rect_iter<'a>(&'a self, rect: Rect) -> Option<RectIter<'a, Elem>> {
        if self.size.contains_coord(rect.max_coord) {
            Some(RectIter {
                grid: std::marker::PhantomData,
                stride: self.stride(&rect),
                cur_elem: self.elems.as_ptr(),
                rect: rect,
                cur_coord: rect.min_coord
            })
        }
        else {
            None
        }
    }

    /// Create a mutable iterator over a rectangular region of the
    /// Vec2D. None is returned if the given `rect` does not fit
    /// entirely within the Vec2D.
    pub fn rect_iter_mut<'a>(&'a mut self, rect: Rect) -> Option<RectIterMut<'a, Elem>> {
        if self.size.contains_coord(rect.max_coord) {
            Some(RectIterMut {
                grid: std::marker::PhantomData,
                stride: self.stride(&rect),
                cur_elem: self.elems.as_mut_ptr(),
                rect: rect,
                cur_coord: rect.min_coord
            })
        }
        else {
            None
        }
    }
}

impl<'a, Elem> Iterator for RectIter<'a, Elem> {
    type Item = (Coord, &'a Elem);

    fn next(&mut self) -> Option<Self::Item> {
        if self.cur_coord.y <= self.rect.max_coord.y {
            let result = (self.cur_coord, unsafe { & *self.cur_elem });

            self.cur_coord.x += 1;
            if self.cur_coord.x <= self.rect.max_coord.x {
                unsafe { self.cur_elem = self.cur_elem.offset(1); }
            }
            else {
                self.cur_coord.x = self.rect.min_coord.x;
                self.cur_coord.y += 1;
                unsafe { self.cur_elem = self.cur_elem.offset(self.stride); }
            }
            Some(result)
        }
        else {
            None
        }
    }
}

impl<'a, Elem> Iterator for RectIterMut<'a, Elem> {
    type Item = (Coord, &'a mut Elem);

    fn next(&mut self) -> Option<Self::Item> {
        if self.cur_coord.y <= self.rect.max_coord.y {
            let result = (self.cur_coord, unsafe { &mut *self.cur_elem });

            self.cur_coord.x += 1;
            if self.cur_coord.x <= self.rect.max_coord.x {
                unsafe { self.cur_elem = self.cur_elem.offset(1); }
            }
            else {
                self.cur_coord.x = self.rect.min_coord.x;
                self.cur_coord.y += 1;
                unsafe { self.cur_elem = self.cur_elem.offset(self.stride); }
            }
            Some(result)
        }
        else {
            None
        }
    }
}

impl Rect {
    /// Create a new Rect defined by inclusive minimum and maximum
    /// coordinates. If min_coord is greater than max_coord on either
    /// axis then None is returned.
    pub fn new(min_coord: Coord, max_coord: Coord) -> Option<Rect> {
        if min_coord.x <= max_coord.x && min_coord.y <= max_coord.y {
            Some(Rect {
                min_coord: min_coord,
                max_coord: max_coord
            })
        }
        else {
            None
        }
    }

}

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

    #[test]
    fn test_coord() {
        let coord = Coord::new(1, 2);
        assert_eq!(coord.x, 1);
        assert_eq!(coord.y, 2);
    }

    #[test]
    fn test_rect() {
        let rect = Rect::new(Coord::new(1, 2), Coord::new(5, 3)).unwrap();
        assert_eq!(rect.width(), 5);
        assert_eq!(rect.height(), 2);

        assert_eq!(rect.width(), rect.size().width);
        assert_eq!(rect.height(), rect.size().height);
    }

    #[test]
    fn test_bad_rect() {
        assert_eq!(Rect::new(Coord::new(2, 1), Coord::new(1, 1)).is_none(),
                   true);
        assert_eq!(Rect::new(Coord::new(1, 2), Coord::new(1, 1)).is_none(),
                   true);
    }

    #[test]
    fn test_size() {
        let size = Size::new(3, 2);
        assert_eq!(size.width, 3);
        assert_eq!(size.height, 2);

        assert_eq!(size.area(), 6);

        assert_eq!(size.contains_coord(Coord::new(1, 1)), true);
        assert_eq!(size.contains_coord(Coord::new(4, 1)), false);
        assert_eq!(size.contains_coord(Coord::new(1, 3)), false);

        let rect = size.rect();
        assert_eq!(rect.min_coord, Coord::new(0, 0));
        assert_eq!(rect.max_coord, Coord::new(2, 1));
    }

    #[test]
    fn test_rect_iter_mut() {
        let elems = vec![1, 2, 3, 4];
        let mut grid = Vec2D::from_vec(Size::new(2, 2), elems).unwrap();
        let rect = Rect::new(Coord::new(0, 0), Coord::new(1, 1)).unwrap();

        let mut actual_coords = Vec::new();
        for (coord, elem) in grid.rect_iter_mut(rect).unwrap() {
            *elem = -(*elem);
            actual_coords.push((coord.x, coord.y));
        }
        assert_eq!(actual_coords, [(0, 0), (1, 0), (0, 1), (1, 1)]);
        assert_eq!(grid.elems, [-1, -2, -3, -4]);
    }

    #[test]
    fn test_two_iterators() {
        let size = Size::new(2, 1);
        let v = Vec2D::from_vec(size, vec![0, 1]).unwrap();

        let iter1 = v.rect_iter(size.rect()).unwrap();
        let iter2 = v.rect_iter(size.rect()).unwrap();

        for ((coord1, elem1), (coord2, elem2)) in iter1.zip(iter2) {
            assert_eq!(coord1, coord2);
            assert_eq!(elem1, elem2);
        }
    }
}
<commit_msg>Add Rect::contains_coord() and test<commit_after>// Copyright 2015 Nicholas Bishop
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Vec2D is a very simple 2D container for storing rectangular data

#![deny(missing_docs)]

/// 2D coordinate
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Coord {
    /// X component
    pub x: usize,

    /// Y component
    pub y: usize
}

/// Rectangle defined by inclusive minimum and maximum coordinates
#[derive(Clone, Copy, Eq, Debug, PartialEq)]
pub struct Rect {
    /// Minimum coordinate (inclusive)
    min_coord: Coord,

    /// Maximum coordinate (inclusive)
    max_coord: Coord
}

/// Rectangle dimensions
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Size {
    /// Width of rectangle
    pub width: usize,
    /// Height of rectangle
    pub height: usize
}

/// Container for 2D data
#[derive(Clone, Debug)]
pub struct Vec2D<T> {
    elems: Vec<T>,
    size: Size
}

/// Iterator over a rectangle within a Vec2D
pub struct RectIter<'a, Elem: 'a> {
    grid: std::marker::PhantomData<&'a Vec2D<Elem>>,

    rect: Rect,
    cur_elem: *const Elem,
    cur_coord: Coord,
    stride: isize
}

/// Mutable iterator over a rectangle within a Vec2D
pub struct RectIterMut<'a, Elem: 'a> {
    grid: std::marker::PhantomData<&'a mut Vec2D<Elem>>,

    rect: Rect,
    cur_elem: *mut Elem,
    cur_coord: Coord,
    stride: isize
}

impl Coord {
    /// Create a coordinate at (x, y)
    pub fn new(x: usize, y: usize) -> Coord {
        Coord {
            x: x,
            y: y
        }
    }
}

impl Rect {
    /// Calculate rectangle width
    pub fn width(&self) -> usize {
        return self.max_coord.x - self.min_coord.x + 1;
    }

    /// Calculate rectangle height
    pub fn height(&self) -> usize {
        return self.max_coord.y - self.min_coord.y + 1;
    }

    /// Calculate rectangle size
    pub fn size(&self) -> Size {
        Size::new(self.width(), self.height())
    }

    /// Return true if the coordinate is between `min_coord` and
    /// `max_coord` (inclusive).
    pub fn contains_coord(&self, coord: Coord) -> bool {
        (coord.x >= self.min_coord.x && coord.x <= self.max_coord.x &&
         coord.y >= self.min_coord.y && coord.y <= self.max_coord.y)
    }
}

impl Size {
    /// Create a 2D size of (width, height)
    pub fn new(width: usize, height: usize) -> Size {
        Size {
            width: width,
            height: height
        }
    }

    /// width * height
    pub fn area(&self) -> usize {
        self.width * self.height
    }

    /// Return true if the coordinate fits within self's width and
    /// height, false otherwise.
    pub fn contains_coord(&self, coord: Coord) -> bool {
        coord.x < self.width && coord.y < self.height
    }

    /// Create a rectangle starting at (0, 0) with `self`'s size.
    pub fn rect(&self) -> Rect {
        Rect {
            min_coord: Coord::new(0, 0),
            max_coord: Coord::new(self.width - 1, self.height - 1)
        }
    }
}

impl<Elem: Copy> Vec2D<Elem> {
    /// Create a Vec2D with the given `size`. All elements are
    /// initialized as copies of the `example` element.
    pub fn from_example(size: Size, example: &Elem) -> Vec2D<Elem> {
        Vec2D {
            elems: vec![*example; size.area()],
            size: size
        }
    }

    /// Create a Vec2D with the given `size`. The contents are set to
    /// `src`. None is returned if the `size` does not match the
    /// length of `src`.
    pub fn from_vec(size: Size, src: Vec<Elem>) -> Option<Vec2D<Elem>> {
        if size.area() == src.len() {
            Some(Vec2D {
                elems: src,
                size: size
            })
        }
        else {
            None
        }
    }

    fn stride(&self, rect: &Rect) -> isize {
        (self.size.width + 1 - rect.width()) as isize
    }

    /// Create an iterator over a rectangular region of the
    /// Vec2D. None is returned if the given `rect` does not fit
    /// entirely within the Vec2D.
    pub fn rect_iter<'a>(&'a self, rect: Rect) -> Option<RectIter<'a, Elem>> {
        if self.size.contains_coord(rect.max_coord) {
            Some(RectIter {
                grid: std::marker::PhantomData,
                stride: self.stride(&rect),
                cur_elem: self.elems.as_ptr(),
                rect: rect,
                cur_coord: rect.min_coord
            })
        }
        else {
            None
        }
    }

    /// Create a mutable iterator over a rectangular region of the
    /// Vec2D. None is returned if the given `rect` does not fit
    /// entirely within the Vec2D.
    pub fn rect_iter_mut<'a>(&'a mut self, rect: Rect) -> Option<RectIterMut<'a, Elem>> {
        if self.size.contains_coord(rect.max_coord) {
            Some(RectIterMut {
                grid: std::marker::PhantomData,
                stride: self.stride(&rect),
                cur_elem: self.elems.as_mut_ptr(),
                rect: rect,
                cur_coord: rect.min_coord
            })
        }
        else {
            None
        }
    }
}

impl<'a, Elem> Iterator for RectIter<'a, Elem> {
    type Item = (Coord, &'a Elem);

    fn next(&mut self) -> Option<Self::Item> {
        if self.cur_coord.y <= self.rect.max_coord.y {
            let result = (self.cur_coord, unsafe { & *self.cur_elem });

            self.cur_coord.x += 1;
            if self.cur_coord.x <= self.rect.max_coord.x {
                unsafe { self.cur_elem = self.cur_elem.offset(1); }
            }
            else {
                self.cur_coord.x = self.rect.min_coord.x;
                self.cur_coord.y += 1;
                unsafe { self.cur_elem = self.cur_elem.offset(self.stride); }
            }
            Some(result)
        }
        else {
            None
        }
    }
}

impl<'a, Elem> Iterator for RectIterMut<'a, Elem> {
    type Item = (Coord, &'a mut Elem);

    fn next(&mut self) -> Option<Self::Item> {
        if self.cur_coord.y <= self.rect.max_coord.y {
            let result = (self.cur_coord, unsafe { &mut *self.cur_elem });

            self.cur_coord.x += 1;
            if self.cur_coord.x <= self.rect.max_coord.x {
                unsafe { self.cur_elem = self.cur_elem.offset(1); }
            }
            else {
                self.cur_coord.x = self.rect.min_coord.x;
                self.cur_coord.y += 1;
                unsafe { self.cur_elem = self.cur_elem.offset(self.stride); }
            }
            Some(result)
        }
        else {
            None
        }
    }
}

impl Rect {
    /// Create a new Rect defined by inclusive minimum and maximum
    /// coordinates. If min_coord is greater than max_coord on either
    /// axis then None is returned.
    pub fn new(min_coord: Coord, max_coord: Coord) -> Option<Rect> {
        if min_coord.x <= max_coord.x && min_coord.y <= max_coord.y {
            Some(Rect {
                min_coord: min_coord,
                max_coord: max_coord
            })
        }
        else {
            None
        }
    }

}

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

    #[test]
    fn test_coord() {
        let coord = Coord::new(1, 2);
        assert_eq!(coord.x, 1);
        assert_eq!(coord.y, 2);
    }

    #[test]
    fn test_rect() {
        let rect = Rect::new(Coord::new(1, 2), Coord::new(5, 3)).unwrap();
        assert_eq!(rect.width(), 5);
        assert_eq!(rect.height(), 2);

        assert_eq!(rect.width(), rect.size().width);
        assert_eq!(rect.height(), rect.size().height);

        assert_eq!(rect.contains_coord(Coord::new(0, 0)), false);
        assert_eq!(rect.contains_coord(Coord::new(4, 3)), true);
    }

    #[test]
    fn test_bad_rect() {
        assert_eq!(Rect::new(Coord::new(2, 1), Coord::new(1, 1)).is_none(),
                   true);
        assert_eq!(Rect::new(Coord::new(1, 2), Coord::new(1, 1)).is_none(),
                   true);
    }

    #[test]
    fn test_size() {
        let size = Size::new(3, 2);
        assert_eq!(size.width, 3);
        assert_eq!(size.height, 2);

        assert_eq!(size.area(), 6);

        assert_eq!(size.contains_coord(Coord::new(1, 1)), true);
        assert_eq!(size.contains_coord(Coord::new(4, 1)), false);
        assert_eq!(size.contains_coord(Coord::new(1, 3)), false);

        let rect = size.rect();
        assert_eq!(rect.min_coord, Coord::new(0, 0));
        assert_eq!(rect.max_coord, Coord::new(2, 1));
    }

    #[test]
    fn test_rect_iter_mut() {
        let elems = vec![1, 2, 3, 4];
        let mut grid = Vec2D::from_vec(Size::new(2, 2), elems).unwrap();
        let rect = Rect::new(Coord::new(0, 0), Coord::new(1, 1)).unwrap();

        let mut actual_coords = Vec::new();
        for (coord, elem) in grid.rect_iter_mut(rect).unwrap() {
            *elem = -(*elem);
            actual_coords.push((coord.x, coord.y));
        }
        assert_eq!(actual_coords, [(0, 0), (1, 0), (0, 1), (1, 1)]);
        assert_eq!(grid.elems, [-1, -2, -3, -4]);
    }

    #[test]
    fn test_two_iterators() {
        let size = Size::new(2, 1);
        let v = Vec2D::from_vec(size, vec![0, 1]).unwrap();

        let iter1 = v.rect_iter(size.rect()).unwrap();
        let iter2 = v.rect_iter(size.rect()).unwrap();

        for ((coord1, elem1), (coord2, elem2)) in iter1.zip(iter2) {
            assert_eq!(coord1, coord2);
            assert_eq!(elem1, elem2);
        }
    }
}
<|endoftext|>


<commit_before>//! Lorem ipsum generator.
//!
//! This crate contains functions for generating pseudo-Latin lorem
//! ipsum placeholder text. The traditional lorem ipsum text start
//! like this:
//!
//! > Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
//! > eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
//! > enim ad minim veniam, quis nostrud exercitation ullamco laboris
//! > nisi ut aliquip ex ea commodo consequat. [...]
//!
//! This text is in the [`LOREM_IPSUM`] constant. Random text looking
//! like the above can be generated using the [`lipsum`] function.
//! This function allows you to generate as much text as desired and
//! each invocation will generate different text. This is done using a
//! [Markov chain] based on both the [`LOREM_IPSUM`] and
//! [`LIBER_PRIMUS`] texts. The latter constant holds the full text of
//! the first book of a work by Cicero, of which the lorem ipsum text
//! is a scrambled subset.
//!
//! The random looking text is generatd using a Markov chain of order
//! two, which simply means that the next word is based on the
//! previous two words in the input texts. The Markov chain can be
//! used with other input texts by creating an instance of
//! [`MarkovChain`] and calling its [`learn`] method.
//!
//! [`LOREM_IPSUM`]: constant.LOREM_IPSUM.html
//! [`LIBER_PRIMUS`]: constant.LIBER_PRIMUS.html
//! [`lipsum`]: fn.lipsum.html
//! [`MarkovChain`]: struct.MarkovChain.html
//! [`learn`]: struct.MarkovChain.html#method.learn
//! [Markov chain]: https://en.wikipedia.org/wiki/Markov_chain

extern crate rand;

use std::cell::RefCell;
use std::collections::HashMap;
use rand::Rng;

/// A bigram is simply two consecutive words.
pub type Bigram<'a> = (&'a str, &'a str);

/// Simple order two Markov chain implementation.
///
/// The [Markov chain] is a chain of order two, which means that it
/// will use the previous two words (a bigram) when predicting the
/// next word. This is normally enough to generate random text that
/// looks somewhat plausible. The implementation is based on
/// [Generating arbitrary text with Markov chains in Rust][blog post].
///
/// [Markov chain]: https://en.wikipedia.org/wiki/Markov_chain
/// [blog post]: https://blakewilliams.me/posts/generating-arbitrary-text-with-markov-chains-in-rust
pub struct MarkovChain<'a, R: Rng> {
    map: HashMap<Bigram<'a>, Vec<&'a str>>,
    keys: Vec<Bigram<'a>>,
    rng: R,
}

impl<'a> MarkovChain<'a, rand::ThreadRng> {
    /// Create a new Markov chain. It will use a default thread-local
    /// random number generator.
    pub fn new() -> MarkovChain<'a, rand::ThreadRng> {
        MarkovChain::new_with_rng(rand::thread_rng())
    }
}

impl<'a, R: Rng> MarkovChain<'a, R> {
    /// Create a new Markov chain that uses the given random number
    /// generator.
    pub fn new_with_rng(rng: R) -> MarkovChain<'a, R> {
        MarkovChain {
            map: HashMap::new(),
            keys: Vec::new(),
            rng: rng,
        }
    }

    /// Add new text to the Markov chain. This can be called several
    /// times to build up the chain.
    ///
    /// # Examples
    ///
    /// ```
    /// use lipsum::MarkovChain;
    ///
    /// let mut chain = MarkovChain::new();
    /// chain.learn("red green blue");
    /// assert_eq!(chain.words(("red", "green")), Some(&vec!["blue"]));
    ///
    /// chain.learn("red green yellow");
    /// assert_eq!(chain.words(("red", "green")), Some(&vec!["blue", "yellow"]));
    /// ```
    pub fn learn(&mut self, sentence: &'a str) {
        let words = sentence.split_whitespace().collect::<Vec<&str>>();
        for window in words.windows(3) {
            let (a, b, c) = (window[0], window[1], window[2]);
            self.map.entry((a, b)).or_insert_with(Vec::new).push(c);
        }
        // Sync the keys with the current map.
        self.keys = self.map.keys().cloned().collect();
        self.keys.sort();
    }

    /// Returs the number of states in the Markov chain.
    ///
    /// # Examples
    ///
    /// ```
    /// use lipsum::MarkovChain;
    ///
    /// let mut chain = MarkovChain::new();
    /// assert_eq!(chain.len(), 0);
    ///
    /// chain.learn("red orange yellow green blue indigo");
    /// assert_eq!(chain.len(), 4);
    /// ```
    #[inline]
    pub fn len(&self) -> usize {
        self.map.len()
    }

    /// Get the possible words following the given bigram, or `None`
    /// if the state is invalid.
    ///
    /// # Examples
    ///
    /// ```
    /// use lipsum::MarkovChain;
    ///
    /// let mut chain = MarkovChain::new();
    /// chain.learn("red green blue");
    /// assert_eq!(chain.words(("red", "green")), Some(&vec!["blue"]));
    /// assert_eq!(chain.words(("foo", "bar")), None);
    /// ```
    pub fn words(&self, state: Bigram<'a>) -> Option<&Vec<&str>> {
        self.map.get(&state)
    }

    /// Generate `n` words worth of lorem ipsum text. The text will
    /// start from a random point in the Markov chain.
    ///
    /// See [`generate_from`] if you want to control the starting
    /// point for the generated text.
    ///
    /// # Examples
    ///
    /// Generating the sounds of a grandfather clock:
    ///
    /// ```
    /// use lipsum::MarkovChain;
    ///
    /// let mut chain = MarkovChain::new();
    /// chain.learn("Tick, Tock, Tick, Tock, Ding! Tick, Tock, Ding! Ding!");
    /// println!("{}", chain.generate(15));
    /// ```
    ///
    /// The output looks like this:
    ///
    /// > Ding! Tick, Tock, Tick, Tock, Ding! Ding! Tock, Ding! Tick,
    /// > Tock, Tick, Tock, Tick, Tock
    ///
    /// [`generate_from`]: struct.MarkovChain.html#method.generate_from
    pub fn generate(&mut self, n: usize) -> String {
        join_words(self.iter().take(n))
    }

    /// Generate `n` words worth of lorem ipsum text. The text will
    /// start from the given bigram.
    ///
    /// Use [`generate`] if the starting point is not important.
    ///
    /// [`generate`]: struct.MarkovChain.html#method.generate
    pub fn generate_from(&mut self, n: usize, from: Bigram<'a>) -> String {
        join_words(self.iter_from(from).take(n))
    }

    /// Make a never-ending iterator over the words in the Markov
    /// chain. The iterator starts at a random point in the chain.
    pub fn iter(&mut self) -> Words {
        let state = if self.keys.is_empty() {
            ("", "")
        } else {
            *choose(&mut self.rng, &self.keys).unwrap()
        };
        Words {
            map: &self.map,
            rng: &mut self.rng,
            keys: &self.keys,
            state: state,
        }
    }

    /// Make a never-ending iterator over the words in the Markov
    /// chain. The iterator starts at the given bigram.
    pub fn iter_from(&mut self, from: Bigram<'a>) -> Words {
        Words {
            map: &self.map,
            rng: &mut self.rng,
            keys: &self.keys,
            state: from,
        }
    }
}

pub struct Words<'a> {
    map: &'a HashMap<Bigram<'a>, Vec<&'a str>>,
    rng: &'a mut rand::Rng,
    keys: &'a Vec<Bigram<'a>>,
    state: Bigram<'a>,
}

impl<'a> Iterator for Words<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<&'a str> {
        if self.map.is_empty() {
            return None;
        }

        let result = Some(self.state.0);

        while !self.map.contains_key(&self.state) {
            self.state = *choose(self.rng, self.keys).unwrap();
        }
        let next_words = &self.map[&self.state];
        let next = choose(self.rng, next_words).unwrap();
        self.state = (self.state.1, next);
        result
    }
}

/// Choose a random element from a slice.
///
/// Unlike `Rng::choose`, this function does not require the RNG to be
/// Sized and thus works on any random number generator.
fn choose<'a, T>(rng: &mut Rng, values: &'a [T]) -> Option<&'a T> {
    if values.is_empty() {
        None
    } else {
        let idx = (values.len() as f32 * rng.next_f32()) as usize;
        Some(&values[idx])
    }
}

fn join_words<'a, I: Iterator<Item = &'a str>>(mut words: I) -> String {
    match words.next() {
        None => String::new(),
        Some(word) => {
            let mut sentence = String::from(word);
            for word in words {
                sentence.push(' ');
                sentence.push_str(word);
            }
            sentence
        }
    }
}

/// The traditional lorem ipsum text as given in [Wikipedia]. Using
/// this text alone for a Markov chain of order two doesn't work very
/// well since each bigram (two consequtive words) is followed by just
/// one other word. In other words, the Markov chain will always
/// produce the same output and recreate the lorem ipsum text
/// precisely. However, combining it with the full text in
/// [`LIBER_PRIMUS`] works well.
///
/// [Wikipedia]: https://en.wikipedia.org/wiki/Lorem_ipsum
/// [`LIBER_PRIMUS`]: constant.LIBER_PRIMUS.html
pub const LOREM_IPSUM: &'static str = include_str!("lorem-ipsum.txt");

/// The first book in Cicero's work De finibus bonorum et malorum ("On
/// the ends of good and evil"). The lorem ipsum text in
/// [`LOREM_IPSUM`] is derived from part of this text.
///
/// [`LOREM_IPSUM`]: constant.LOREM_IPSUM.html
pub const LIBER_PRIMUS: &'static str = include_str!("liber-primus.txt");

thread_local! {
    // Markov chain generating lorem ipsum text.
    static LOREM_IPSUM_CHAIN: RefCell<MarkovChain<'static, rand::ThreadRng>> = {
        let mut chain = MarkovChain::new();
        // The cost of learning increases as more and more text is
        // added, so we start with the smallest text.
        chain.learn(LOREM_IPSUM);
        chain.learn(LIBER_PRIMUS);
        RefCell::new(chain)
    }
}

/// Generate `n` words of lorem ipsum text. The output starts with
/// "Lorem ipsum" and continues with the standard lorem ipsum text
/// from [`LOREM_IPSUM`]. The text will become random if sufficiently
/// long output is requested.
///
/// # Examples
///
/// ```
/// use lipsum::lipsum;
///
/// assert_eq!(lipsum(7), "Lorem ipsum dolor sit amet, consectetur adipiscing");
/// ```
///
/// [`LOREM_IPSUM`]: constant.LOREM_IPSUM.html
pub fn lipsum(n: usize) -> String {
    LOREM_IPSUM_CHAIN.with(|cell| {
                               let mut chain = cell.borrow_mut();
                               chain.generate_from(n, ("Lorem", "ipsum"))
                           })
}

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

    #[test]
    fn starts_with_lorem_ipsum() {
        assert_eq!(&lipsum(10)[..11], "Lorem ipsum");
    }

    #[test]
    fn generate_zero_words() {
        assert_eq!(lipsum(0).split_whitespace().count(), 0);
    }

    #[test]
    fn generate_one_word() {
        assert_eq!(lipsum(1).split_whitespace().count(), 1);
    }

    #[test]
    fn generate_two_words() {
        assert_eq!(lipsum(2).split_whitespace().count(), 2);
    }

    #[test]
    fn empty_chain() {
        let mut chain = MarkovChain::new();
        assert_eq!(chain.generate(10), "");
    }

    #[test]
    fn generate_from() {
        let mut chain = MarkovChain::new();
        chain.learn("red orange yellow green blue indigo violet");
        assert_eq!(chain.generate_from(5, ("orange", "yellow")),
                   "orange yellow green blue indigo");
    }

    #[test]
    fn generate_last_bigram() {
        // The bigram "yyy zzz" will not be present in the Markov
        // chain's map, and so we will not generate "xxx yyy zzz" as
        // one would expect. The chain moves from state "xxx yyy" to
        // "yyy zzz", but sees that as invalid state and resets itsel
        // back to "xxx yyy".
        let mut chain = MarkovChain::new();
        chain.learn("xxx yyy zzz");
        // We use assert! instead of assert_ne! to support early
        // versions of Rust.
        assert!(chain.generate_from(3, ("xxx", "yyy")) != "xxx yyy zzz");
    }

    #[test]
    fn generate_from_no_panic() {
        // No panic when asked to generate a chain from a starting
        // point that doesn't exist in the chain.
        let mut chain = MarkovChain::new();
        chain.learn("foo bar baz");
        chain.generate_from(3, ("xxx", "yyy"));
    }

    #[test]
    fn chain_map() {
        let mut chain = MarkovChain::new();
        chain.learn("foo bar baz quuz");
        let map = &chain.map;

        assert_eq!(map.len(), 2);
        assert_eq!(map[&("foo", "bar")], vec!["baz"]);
        assert_eq!(map[&("bar", "baz")], vec!["quuz"]);
    }

    #[test]
    fn new_with_rng() {
        extern crate rand;
        use rand::SeedableRng;

        let rng = rand::XorShiftRng::from_seed([1, 2, 3, 4]);
        let mut chain = MarkovChain::new_with_rng(rng);
        chain.learn("foo bar x y z");
        chain.learn("foo bar a b c");

        assert_eq!(chain.generate(15), "a b b x y b x y x y x y bar x y");
    }
}
<commit_msg>MarkovChain: add is_empty method<commit_after>//! Lorem ipsum generator.
//!
//! This crate contains functions for generating pseudo-Latin lorem
//! ipsum placeholder text. The traditional lorem ipsum text start
//! like this:
//!
//! > Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
//! > eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
//! > enim ad minim veniam, quis nostrud exercitation ullamco laboris
//! > nisi ut aliquip ex ea commodo consequat. [...]
//!
//! This text is in the [`LOREM_IPSUM`] constant. Random text looking
//! like the above can be generated using the [`lipsum`] function.
//! This function allows you to generate as much text as desired and
//! each invocation will generate different text. This is done using a
//! [Markov chain] based on both the [`LOREM_IPSUM`] and
//! [`LIBER_PRIMUS`] texts. The latter constant holds the full text of
//! the first book of a work by Cicero, of which the lorem ipsum text
//! is a scrambled subset.
//!
//! The random looking text is generatd using a Markov chain of order
//! two, which simply means that the next word is based on the
//! previous two words in the input texts. The Markov chain can be
//! used with other input texts by creating an instance of
//! [`MarkovChain`] and calling its [`learn`] method.
//!
//! [`LOREM_IPSUM`]: constant.LOREM_IPSUM.html
//! [`LIBER_PRIMUS`]: constant.LIBER_PRIMUS.html
//! [`lipsum`]: fn.lipsum.html
//! [`MarkovChain`]: struct.MarkovChain.html
//! [`learn`]: struct.MarkovChain.html#method.learn
//! [Markov chain]: https://en.wikipedia.org/wiki/Markov_chain

extern crate rand;

use std::cell::RefCell;
use std::collections::HashMap;
use rand::Rng;

/// A bigram is simply two consecutive words.
pub type Bigram<'a> = (&'a str, &'a str);

/// Simple order two Markov chain implementation.
///
/// The [Markov chain] is a chain of order two, which means that it
/// will use the previous two words (a bigram) when predicting the
/// next word. This is normally enough to generate random text that
/// looks somewhat plausible. The implementation is based on
/// [Generating arbitrary text with Markov chains in Rust][blog post].
///
/// [Markov chain]: https://en.wikipedia.org/wiki/Markov_chain
/// [blog post]: https://blakewilliams.me/posts/generating-arbitrary-text-with-markov-chains-in-rust
pub struct MarkovChain<'a, R: Rng> {
    map: HashMap<Bigram<'a>, Vec<&'a str>>,
    keys: Vec<Bigram<'a>>,
    rng: R,
}

impl<'a> MarkovChain<'a, rand::ThreadRng> {
    /// Create a new Markov chain. It will use a default thread-local
    /// random number generator.
    pub fn new() -> MarkovChain<'a, rand::ThreadRng> {
        MarkovChain::new_with_rng(rand::thread_rng())
    }
}

impl<'a, R: Rng> MarkovChain<'a, R> {
    /// Create a new Markov chain that uses the given random number
    /// generator.
    pub fn new_with_rng(rng: R) -> MarkovChain<'a, R> {
        MarkovChain {
            map: HashMap::new(),
            keys: Vec::new(),
            rng: rng,
        }
    }

    /// Add new text to the Markov chain. This can be called several
    /// times to build up the chain.
    ///
    /// # Examples
    ///
    /// ```
    /// use lipsum::MarkovChain;
    ///
    /// let mut chain = MarkovChain::new();
    /// chain.learn("red green blue");
    /// assert_eq!(chain.words(("red", "green")), Some(&vec!["blue"]));
    ///
    /// chain.learn("red green yellow");
    /// assert_eq!(chain.words(("red", "green")), Some(&vec!["blue", "yellow"]));
    /// ```
    pub fn learn(&mut self, sentence: &'a str) {
        let words = sentence.split_whitespace().collect::<Vec<&str>>();
        for window in words.windows(3) {
            let (a, b, c) = (window[0], window[1], window[2]);
            self.map.entry((a, b)).or_insert_with(Vec::new).push(c);
        }
        // Sync the keys with the current map.
        self.keys = self.map.keys().cloned().collect();
        self.keys.sort();
    }

    /// Returs the number of states in the Markov chain.
    ///
    /// # Examples
    ///
    /// ```
    /// use lipsum::MarkovChain;
    ///
    /// let mut chain = MarkovChain::new();
    /// assert_eq!(chain.len(), 0);
    ///
    /// chain.learn("red orange yellow green blue indigo");
    /// assert_eq!(chain.len(), 4);
    /// ```
    #[inline]
    pub fn len(&self) -> usize {
        self.map.len()
    }

    /// Returns `true` if the Markov chain has no states.
    ///
    /// # Examples
    ///
    /// ```
    /// use lipsum::MarkovChain;
    ///
    /// let mut chain = MarkovChain::new();
    /// assert!(chain.is_empty());
    ///
    /// chain.learn("foo bar baz");
    /// assert!(!chain.is_empty());
    /// ```
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Get the possible words following the given bigram, or `None`
    /// if the state is invalid.
    ///
    /// # Examples
    ///
    /// ```
    /// use lipsum::MarkovChain;
    ///
    /// let mut chain = MarkovChain::new();
    /// chain.learn("red green blue");
    /// assert_eq!(chain.words(("red", "green")), Some(&vec!["blue"]));
    /// assert_eq!(chain.words(("foo", "bar")), None);
    /// ```
    pub fn words(&self, state: Bigram<'a>) -> Option<&Vec<&str>> {
        self.map.get(&state)
    }

    /// Generate `n` words worth of lorem ipsum text. The text will
    /// start from a random point in the Markov chain.
    ///
    /// See [`generate_from`] if you want to control the starting
    /// point for the generated text.
    ///
    /// # Examples
    ///
    /// Generating the sounds of a grandfather clock:
    ///
    /// ```
    /// use lipsum::MarkovChain;
    ///
    /// let mut chain = MarkovChain::new();
    /// chain.learn("Tick, Tock, Tick, Tock, Ding! Tick, Tock, Ding! Ding!");
    /// println!("{}", chain.generate(15));
    /// ```
    ///
    /// The output looks like this:
    ///
    /// > Ding! Tick, Tock, Tick, Tock, Ding! Ding! Tock, Ding! Tick,
    /// > Tock, Tick, Tock, Tick, Tock
    ///
    /// [`generate_from`]: struct.MarkovChain.html#method.generate_from
    pub fn generate(&mut self, n: usize) -> String {
        join_words(self.iter().take(n))
    }

    /// Generate `n` words worth of lorem ipsum text. The text will
    /// start from the given bigram.
    ///
    /// Use [`generate`] if the starting point is not important.
    ///
    /// [`generate`]: struct.MarkovChain.html#method.generate
    pub fn generate_from(&mut self, n: usize, from: Bigram<'a>) -> String {
        join_words(self.iter_from(from).take(n))
    }

    /// Make a never-ending iterator over the words in the Markov
    /// chain. The iterator starts at a random point in the chain.
    pub fn iter(&mut self) -> Words {
        let state = if self.is_empty() {
            ("", "")
        } else {
            *choose(&mut self.rng, &self.keys).unwrap()
        };
        Words {
            map: &self.map,
            rng: &mut self.rng,
            keys: &self.keys,
            state: state,
        }
    }

    /// Make a never-ending iterator over the words in the Markov
    /// chain. The iterator starts at the given bigram.
    pub fn iter_from(&mut self, from: Bigram<'a>) -> Words {
        Words {
            map: &self.map,
            rng: &mut self.rng,
            keys: &self.keys,
            state: from,
        }
    }
}

pub struct Words<'a> {
    map: &'a HashMap<Bigram<'a>, Vec<&'a str>>,
    rng: &'a mut rand::Rng,
    keys: &'a Vec<Bigram<'a>>,
    state: Bigram<'a>,
}

impl<'a> Iterator for Words<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<&'a str> {
        if self.map.is_empty() {
            return None;
        }

        let result = Some(self.state.0);

        while !self.map.contains_key(&self.state) {
            self.state = *choose(self.rng, self.keys).unwrap();
        }
        let next_words = &self.map[&self.state];
        let next = choose(self.rng, next_words).unwrap();
        self.state = (self.state.1, next);
        result
    }
}

/// Choose a random element from a slice.
///
/// Unlike `Rng::choose`, this function does not require the RNG to be
/// Sized and thus works on any random number generator.
fn choose<'a, T>(rng: &mut Rng, values: &'a [T]) -> Option<&'a T> {
    if values.is_empty() {
        None
    } else {
        let idx = (values.len() as f32 * rng.next_f32()) as usize;
        Some(&values[idx])
    }
}

fn join_words<'a, I: Iterator<Item = &'a str>>(mut words: I) -> String {
    match words.next() {
        None => String::new(),
        Some(word) => {
            let mut sentence = String::from(word);
            for word in words {
                sentence.push(' ');
                sentence.push_str(word);
            }
            sentence
        }
    }
}

/// The traditional lorem ipsum text as given in [Wikipedia]. Using
/// this text alone for a Markov chain of order two doesn't work very
/// well since each bigram (two consequtive words) is followed by just
/// one other word. In other words, the Markov chain will always
/// produce the same output and recreate the lorem ipsum text
/// precisely. However, combining it with the full text in
/// [`LIBER_PRIMUS`] works well.
///
/// [Wikipedia]: https://en.wikipedia.org/wiki/Lorem_ipsum
/// [`LIBER_PRIMUS`]: constant.LIBER_PRIMUS.html
pub const LOREM_IPSUM: &'static str = include_str!("lorem-ipsum.txt");

/// The first book in Cicero's work De finibus bonorum et malorum ("On
/// the ends of good and evil"). The lorem ipsum text in
/// [`LOREM_IPSUM`] is derived from part of this text.
///
/// [`LOREM_IPSUM`]: constant.LOREM_IPSUM.html
pub const LIBER_PRIMUS: &'static str = include_str!("liber-primus.txt");

thread_local! {
    // Markov chain generating lorem ipsum text.
    static LOREM_IPSUM_CHAIN: RefCell<MarkovChain<'static, rand::ThreadRng>> = {
        let mut chain = MarkovChain::new();
        // The cost of learning increases as more and more text is
        // added, so we start with the smallest text.
        chain.learn(LOREM_IPSUM);
        chain.learn(LIBER_PRIMUS);
        RefCell::new(chain)
    }
}

/// Generate `n` words of lorem ipsum text. The output starts with
/// "Lorem ipsum" and continues with the standard lorem ipsum text
/// from [`LOREM_IPSUM`]. The text will become random if sufficiently
/// long output is requested.
///
/// # Examples
///
/// ```
/// use lipsum::lipsum;
///
/// assert_eq!(lipsum(7), "Lorem ipsum dolor sit amet, consectetur adipiscing");
/// ```
///
/// [`LOREM_IPSUM`]: constant.LOREM_IPSUM.html
pub fn lipsum(n: usize) -> String {
    LOREM_IPSUM_CHAIN.with(|cell| {
                               let mut chain = cell.borrow_mut();
                               chain.generate_from(n, ("Lorem", "ipsum"))
                           })
}

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

    #[test]
    fn starts_with_lorem_ipsum() {
        assert_eq!(&lipsum(10)[..11], "Lorem ipsum");
    }

    #[test]
    fn generate_zero_words() {
        assert_eq!(lipsum(0).split_whitespace().count(), 0);
    }

    #[test]
    fn generate_one_word() {
        assert_eq!(lipsum(1).split_whitespace().count(), 1);
    }

    #[test]
    fn generate_two_words() {
        assert_eq!(lipsum(2).split_whitespace().count(), 2);
    }

    #[test]
    fn empty_chain() {
        let mut chain = MarkovChain::new();
        assert_eq!(chain.generate(10), "");
    }

    #[test]
    fn generate_from() {
        let mut chain = MarkovChain::new();
        chain.learn("red orange yellow green blue indigo violet");
        assert_eq!(chain.generate_from(5, ("orange", "yellow")),
                   "orange yellow green blue indigo");
    }

    #[test]
    fn generate_last_bigram() {
        // The bigram "yyy zzz" will not be present in the Markov
        // chain's map, and so we will not generate "xxx yyy zzz" as
        // one would expect. The chain moves from state "xxx yyy" to
        // "yyy zzz", but sees that as invalid state and resets itsel
        // back to "xxx yyy".
        let mut chain = MarkovChain::new();
        chain.learn("xxx yyy zzz");
        // We use assert! instead of assert_ne! to support early
        // versions of Rust.
        assert!(chain.generate_from(3, ("xxx", "yyy")) != "xxx yyy zzz");
    }

    #[test]
    fn generate_from_no_panic() {
        // No panic when asked to generate a chain from a starting
        // point that doesn't exist in the chain.
        let mut chain = MarkovChain::new();
        chain.learn("foo bar baz");
        chain.generate_from(3, ("xxx", "yyy"));
    }

    #[test]
    fn chain_map() {
        let mut chain = MarkovChain::new();
        chain.learn("foo bar baz quuz");
        let map = &chain.map;

        assert_eq!(map.len(), 2);
        assert_eq!(map[&("foo", "bar")], vec!["baz"]);
        assert_eq!(map[&("bar", "baz")], vec!["quuz"]);
    }

    #[test]
    fn new_with_rng() {
        extern crate rand;
        use rand::SeedableRng;

        let rng = rand::XorShiftRng::from_seed([1, 2, 3, 4]);
        let mut chain = MarkovChain::new_with_rng(rng);
        chain.learn("foo bar x y z");
        chain.learn("foo bar a b c");

        assert_eq!(chain.generate(15), "a b b x y b x y x y x y bar x y");
    }
}
<|endoftext|>


<commit_before>extern crate libc;
use std::ffi::{CStr, CString};
use std::fmt;
use std::mem::size_of;

extern crate rpgffi as pg;

macro_rules! log {
    ($msg:expr) => {
        elog(file!(), line!(), "log()", $msg)
    }
}

// Implementation of initialization and callbacks.

pub unsafe extern "C" fn init(cb: *mut pg::OutputPluginCallbacks) {
    (*cb).startup_cb = Some(startup);
    (*cb).begin_cb = Some(begin);
    (*cb).change_cb = Some(change);
    (*cb).commit_cb = Some(commit);
    (*cb).shutdown_cb = Some(shutdown);
    (*cb).message_cb = Some(message);
}

unsafe extern "C" fn startup(
    ctx: *mut pg::Struct_LogicalDecodingContext,
    options: *mut pg::OutputPluginOptions,
    _is_init: pg::_bool,
) {
    use pg::Enum_OutputPluginOutputType::*;
    let last_relid = pg::palloc0(size_of::<pg::Oid>() as u64);
    (*ctx).output_plugin_private = last_relid;
    (*options).output_type = OUTPUT_PLUGIN_TEXTUAL_OUTPUT;
}

unsafe extern "C" fn begin(
    ctx: *mut pg::Struct_LogicalDecodingContext,
    txn: *mut pg::ReorderBufferTXN,
) {
    let s = CString::new("{ \"begin\": %u }").unwrap();
    pg::OutputPluginPrepareWrite(ctx, CTRUE);
    pg::appendStringInfo((*ctx).out, s.as_ptr(), (*txn).xid);
    pg::OutputPluginWrite(ctx, CTRUE);
}

unsafe extern "C" fn change(
    ctx: *mut pg::Struct_LogicalDecodingContext,
    _txn: *mut pg::ReorderBufferTXN,
    relation: pg::Relation,
    change: *mut pg::ReorderBufferChange,
) {
    let relid = (*relation).rd_id;
    let last_relid: *mut pg::Oid = (*ctx).output_plugin_private as
        *mut pg::Oid;
    if *last_relid != relid {
        pg::OutputPluginPrepareWrite(ctx, CFALSE);
        append_schema(relation, (*ctx).out);
        pg::OutputPluginWrite(ctx, CFALSE);
        *last_relid = relid;
    }
    pg::OutputPluginPrepareWrite(ctx, CTRUE);
    append_change(relation, change, (*ctx).out);
    pg::OutputPluginWrite(ctx, CTRUE);
}

unsafe extern "C" fn commit(
    ctx: *mut pg::Struct_LogicalDecodingContext,
    txn: *mut pg::ReorderBufferTXN,
    _lsn: pg::XLogRecPtr,
) {
    let s = CString::new("{ \"commit\": %u, \"t\": \"%s\" }").unwrap();
    let t = pg::timestamptz_to_str((*txn).commit_time);
    pg::OutputPluginPrepareWrite(ctx, CTRUE);
    pg::appendStringInfo((*ctx).out, s.as_ptr(), (*txn).xid, t);
    pg::OutputPluginWrite(ctx, CTRUE);
    let last_relid: *mut pg::Oid = (*ctx).output_plugin_private as
        *mut pg::Oid;
    *last_relid = 0;
}

unsafe extern "C" fn shutdown(ctx: *mut pg::Struct_LogicalDecodingContext) {
    pg::pfree((*ctx).output_plugin_private);
}

#[allow(unused_variables)]
unsafe extern "C" fn message(ctx: *mut pg::Struct_LogicalDecodingContext,
                             txn: *mut pg::ReorderBufferTXN,
                             lsn: pg::XLogRecPtr,
                             transactional: pg::_bool,
                             prefix: *const std::os::raw::c_char,
                             message_size: pg::Size,
                             message: *const std::os::raw::c_char
) {
    pg::OutputPluginPrepareWrite(ctx, CTRUE);
    append_message(transactional, prefix, message, (*ctx).out);
    pg::OutputPluginWrite(ctx, CTRUE);
}

trait PGAppend<T> {
    unsafe fn add_str(self, T);
    unsafe fn add_json(self, T);
}

impl<'a> PGAppend<&'a str> for pg::StringInfo {
    unsafe fn add_str(self, t: &'a str) {
        pg::appendStringInfoString(self, CString::new(t).unwrap().as_ptr());
    }
    unsafe fn add_json(self, t: &'a str) {
        pg::escape_json(self, CString::new(t).unwrap().as_ptr());
    }
}

impl PGAppend<*mut i8> for pg::StringInfo {
    unsafe fn add_str(self, t: *mut i8) {
        pg::appendStringInfoString(self, t);
    }
    unsafe fn add_json(self, t: *mut i8) { pg::escape_json(self, t); }
}

impl PGAppend<*const i8> for pg::StringInfo {
    unsafe fn add_str(self, t: *const i8) {
        pg::appendStringInfoString(self, t);
    }
    unsafe fn add_json(self, t: *const i8) { pg::escape_json(self, t); }
}

struct Wrapped(pg::Enum_ReorderBufferChangeType);

impl fmt::Display for Wrapped {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use pg::Enum_ReorderBufferChangeType::*;
        #[allow(unreachable_patterns)]
        let formatted_token = match self.0 {
            REORDER_BUFFER_CHANGE_INSERT => "insert",
            REORDER_BUFFER_CHANGE_UPDATE => "update",
            REORDER_BUFFER_CHANGE_DELETE => "delete",
            REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT => "internal_snapshot",
            REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID => "internal_command_id",
            REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID => "internal_tuplecid",
            #[cfg_attr(rustfmt, rustfmt_skip)]
            REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT =>
                "internal_spec_insert",
            _ => "unknown_change_type",   // NB: Unreachable after Postgres 9.4
        };
        write!(f, "{}", formatted_token)
    }
}

unsafe fn append_change(
    relation: pg::Relation,
    change: *mut pg::ReorderBufferChange,
    out: pg::StringInfo,
) {
    use pg::Enum_ReorderBufferChangeType::*;
    let relid = (*relation).rd_id;
    let name = pg::get_rel_name(relid);
    let ns = pg::get_namespace_name(pg::get_rel_namespace(relid));
    let qualified_name = pg::quote_qualified_identifier(ns, name);
    let tuple_desc = (*relation).rd_att;
    let tuples = (*change).data.tp();
    let tuple_new = (*tuples).newtuple;
    let tuple_old = (*tuples).oldtuple;
    let token = match (*change).action {
        REORDER_BUFFER_CHANGE_INSERT => "insert",
        REORDER_BUFFER_CHANGE_UPDATE => "update",
        REORDER_BUFFER_CHANGE_DELETE => "delete",
        _ => "unrecognized",
    };
    if token == "unrecognized" {
        log!(
            format!(
                "Unrecognized Change Action: [ {} ]",
                Wrapped((*change).action)
            )
            .as_str()
        );
        return;
    }
    out.add_str("{ ");
    out.add_json(token);
    out.add_str(": ");
    append_tuple_buf_as_json(tuple_new, tuple_desc, out);
    if !tuple_old.is_null() {
        out.add_str(", \"@\": ");
        append_tuple_buf_as_json(tuple_old, tuple_desc, out);
    }
    out.add_str(", ");
    out.add_json("table");
    out.add_str(": ");
    out.add_json(qualified_name);
    out.add_str(" }");
}

unsafe fn append_tuple_buf_as_json(
    data: *mut pg::ReorderBufferTupleBuf,
    desc: pg::TupleDesc,
    out: pg::StringInfo,
) {
    if !data.is_null() {
        let heap_tuple = &mut (*data).tuple;
        let n = (*desc).natts as usize;
        let attrs = (*desc).attrs;

        // Pull out every single field to check for stale TOAST.
        let mut datums: Vec<pg::Datum> = Vec::new();
        let mut nulls: Vec<pg::_bool> = Vec::new();
        datums.resize(n, 0);
        nulls.resize(n, CFALSE);
        pg::heap_deform_tuple(
            heap_tuple,
            desc,
            datums.as_mut_ptr(),
            nulls.as_mut_ptr(),
        );

        let mut skip: Vec<pg::Form_pg_attribute> = Vec::with_capacity(n);

        for i in 0..n {
            let datum: pg::Datum = datums[i];
            let attr = *attrs.offset(i as isize);
            if datum == 0 || (*attr).attnum <= 0 {
                continue;
            }
            if (*attr).attisdropped == CFALSE && is_stale_toast(datum, attr) {
                skip.push(attr);
                // Mark as NULL to trick heap_form_tuple().
                nulls[i] = CTRUE;
            }
        }

        // Mark as dropped to trick row_to_json().
        for attr in &mut skip {
            (**attr).attisdropped = CTRUE;
        }

        let new =
            pg::heap_form_tuple(desc, datums.as_mut_ptr(), nulls.as_mut_ptr());

        let datum = pg::heap_copy_tuple_as_datum(new, desc);
        let empty_oid: pg::Oid = 0;
        let json =
            pg::DirectFunctionCall1Coll(Some(row_to_json), empty_oid, datum);

        // Set back to true because who knows how else these attrs, which are
        // part of the passed in tuple description, are being used.
        for attr in &mut skip {
            (**attr).attisdropped = CFALSE;
        }

        let ptr = json as *const pg::Struct_varlena;
        let text = pg::text_to_cstring(ptr);
        pg::appendStringInfoString(out, text);

        if skip.len() > 0 {
            out.add_str(", ");
            out.add_json("skipped");
            out.add_str(": [");
            for (i, attr) in skip.into_iter().enumerate() {
                if i > 0 {
                    out.add_str(", ");
                }
                out.add_json((*attr).attname.data.as_mut_ptr());
            }
            out.add_str("]");
        }
    } else {
        out.add_str("{}");
    }
}

unsafe fn append_schema(relation: pg::Relation, out: pg::StringInfo) {
    let relid = (*relation).rd_id;
    let tupdesc = (*relation).rd_att;
    let name = pg::get_rel_name(relid);
    let ns = pg::get_namespace_name(pg::get_rel_namespace(relid));
    let qualified_name = pg::quote_qualified_identifier(ns, name);
    out.add_str("{ ");
    out.add_json("schema");
    out.add_str(": ");
    out.add_str("[");
    let mut first: bool = true;
    for i in 0..(*tupdesc).natts {
        let attr = *(*tupdesc).attrs.offset(i as isize);
        let num = (*attr).attnum;
        if (*attr).attisdropped == CTRUE || num <= 0 {
            continue;
        }
        let col = pg::get_attname(relid, num);
        let typ = pg::format_type_be(pg::get_atttype(relid, num));
        if !first {
            out.add_str(",");
        } else {
            first = false;
        }
        out.add_str("{");
        out.add_json(col);
        out.add_str(":");
        out.add_json(typ);
        out.add_str("}");
    }
    out.add_str("]");
    out.add_str(", ");
    out.add_json("table");
    out.add_str(": ");
    out.add_json(qualified_name);
    out.add_str(" }");
}

unsafe fn append_message(transactional: pg::_bool,
                         prefix: *const std::os::raw::c_char,
                         message: *const std::os::raw::c_char,
                         out: pg::StringInfo) {

    // { "message": %s,
    out.add_str("{ ");
    out.add_json("message");
    out.add_str(": ");
    out.add_json(message);
    out.add_str(", ");

    // "prefix": %s,
    out.add_json("prefix");
    out.add_str(": ");
    out.add_json(prefix);
    out.add_str(", ");

    // "transactional": %s }
    out.add_json("transactional");
    out.add_str(": ");

    if transactional == CTRUE {
        out.add_str("true");
    } else {
        out.add_str("false");
    }

    out.add_str(" }");
}

extern "C" fn row_to_json(fcinfo: pg::FunctionCallInfo) -> pg::Datum {
    // We wrap the unsafe call to make it safe, so that it can be passed as
    // a function pointer to DirectFunctionCall1Coll(). This is a spurious
    // artifact of the generated binding.
    unsafe { pg::row_to_json(fcinfo) }
}

/* This is a simulation of `VARATT_IS_EXTERNAL_ONDISK`.

```c
#define VARATT_IS_EXTERNAL_ONDISK(PTR) \
  (VARATT_IS_EXTERNAL(PTR) && VARTAG_EXTERNAL(PTR) == VARTAG_ONDISK)

    #define VARATT_IS_EXTERNAL(PTR)             VARATT_IS_1B_E(PTR)

        #define VARATT_IS_1B_E(PTR) \
            ((((varattrib_1b *) (PTR))->va_header) == 0x01)

    #define VARTAG_EXTERNAL(PTR)                VARTAG_1B_E(PTR)

        #define VARTAG_1B_E(PTR) \
            (((varattrib_1b_e *) (PTR))->va_tag)
```
*/
unsafe fn is_stale_toast(
    datum: pg::Datum,
    attr: pg::Form_pg_attribute,
) -> bool {
    use pg::Enum_vartag_external::VARTAG_ONDISK;
    let mut o: pg::Oid = 0; // Output function; not used
    let mut is_variable_length: pg::_bool = CFALSE;
    pg::getTypeOutputInfo((*attr).atttypid, &mut o, &mut is_variable_length);
    if is_variable_length == CTRUE {
        // Cast to varlena metadata type.
        let v = datum as *const pg::varattrib_1b_e;
        if (*v).va_header != 0x01 {
            return false;
        }
        return (*v).va_tag == (VARTAG_ONDISK as u8);
    }
    return false;
}

// Symbols Postgres needs to find.

#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn _PG_init() {}

#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn _PG_output_plugin_init(
    cb: *mut pg::OutputPluginCallbacks,
) {
    init(cb);
}

// Miscellaneous.

const CTRUE: pg::_bool = 1;
const CFALSE: pg::_bool = 0;

pub unsafe fn elog(file: &str, line: u32, function: &str, msg: &str) {
    let level = 15; // The LOG level of logging is normally server-only
    pg::elog_start(
        CString::new(file).unwrap().as_ptr(),
        line as ::std::os::raw::c_int,
        CString::new(function).unwrap().as_ptr(),
    );
    pg::elog_finish(
        level,
        CString::new("%s").unwrap().as_ptr(),
        CString::new(msg).unwrap().as_ptr(),
    );
}

pub unsafe fn fmt_name(name: pg::NameData) -> String {
    let cstr = CStr::from_ptr(name.data.as_ptr());
    format!("{:?}", cstr.to_owned())
}
<commit_msg>Use appendBinaryStringInfo to naively output the message<commit_after>extern crate libc;
use std::ffi::{CStr, CString};
use std::fmt;
use std::mem::size_of;

extern crate rpgffi as pg;

macro_rules! log {
    ($msg:expr) => {
        elog(file!(), line!(), "log()", $msg)
    }
}

// Implementation of initialization and callbacks.

pub unsafe extern "C" fn init(cb: *mut pg::OutputPluginCallbacks) {
    (*cb).startup_cb = Some(startup);
    (*cb).begin_cb = Some(begin);
    (*cb).change_cb = Some(change);
    (*cb).commit_cb = Some(commit);
    (*cb).shutdown_cb = Some(shutdown);
    (*cb).message_cb = Some(message);
}

unsafe extern "C" fn startup(
    ctx: *mut pg::Struct_LogicalDecodingContext,
    options: *mut pg::OutputPluginOptions,
    _is_init: pg::_bool,
) {
    use pg::Enum_OutputPluginOutputType::*;
    let last_relid = pg::palloc0(size_of::<pg::Oid>() as u64);
    (*ctx).output_plugin_private = last_relid;
    (*options).output_type = OUTPUT_PLUGIN_TEXTUAL_OUTPUT;
}

unsafe extern "C" fn begin(
    ctx: *mut pg::Struct_LogicalDecodingContext,
    txn: *mut pg::ReorderBufferTXN,
) {
    let s = CString::new("{ \"begin\": %u }").unwrap();
    pg::OutputPluginPrepareWrite(ctx, CTRUE);
    pg::appendStringInfo((*ctx).out, s.as_ptr(), (*txn).xid);
    pg::OutputPluginWrite(ctx, CTRUE);
}

unsafe extern "C" fn change(
    ctx: *mut pg::Struct_LogicalDecodingContext,
    _txn: *mut pg::ReorderBufferTXN,
    relation: pg::Relation,
    change: *mut pg::ReorderBufferChange,
) {
    let relid = (*relation).rd_id;
    let last_relid: *mut pg::Oid = (*ctx).output_plugin_private as
        *mut pg::Oid;
    if *last_relid != relid {
        pg::OutputPluginPrepareWrite(ctx, CFALSE);
        append_schema(relation, (*ctx).out);
        pg::OutputPluginWrite(ctx, CFALSE);
        *last_relid = relid;
    }
    pg::OutputPluginPrepareWrite(ctx, CTRUE);
    append_change(relation, change, (*ctx).out);
    pg::OutputPluginWrite(ctx, CTRUE);
}

unsafe extern "C" fn commit(
    ctx: *mut pg::Struct_LogicalDecodingContext,
    txn: *mut pg::ReorderBufferTXN,
    _lsn: pg::XLogRecPtr,
) {
    let s = CString::new("{ \"commit\": %u, \"t\": \"%s\" }").unwrap();
    let t = pg::timestamptz_to_str((*txn).commit_time);
    pg::OutputPluginPrepareWrite(ctx, CTRUE);
    pg::appendStringInfo((*ctx).out, s.as_ptr(), (*txn).xid, t);
    pg::OutputPluginWrite(ctx, CTRUE);
    let last_relid: *mut pg::Oid = (*ctx).output_plugin_private as
        *mut pg::Oid;
    *last_relid = 0;
}

unsafe extern "C" fn shutdown(ctx: *mut pg::Struct_LogicalDecodingContext) {
    pg::pfree((*ctx).output_plugin_private);
}

unsafe extern "C" fn message(
    ctx: *mut pg::Struct_LogicalDecodingContext,
    txn: *mut pg::ReorderBufferTXN,
    lsn: pg::XLogRecPtr,
    transactional: pg::_bool,
    prefix: *const std::os::raw::c_char,
    message_size: pg::Size,
    message: *const std::os::raw::c_char,
) {
    pg::OutputPluginPrepareWrite(ctx, CTRUE);
    append_message(transactional, prefix, message_size, message, (*ctx).out);
    pg::OutputPluginWrite(ctx, CTRUE);
}

trait PGAppend<T> {
    unsafe fn add_str(self, T);
    unsafe fn add_json(self, T);
}

impl<'a> PGAppend<&'a str> for pg::StringInfo {
    unsafe fn add_str(self, t: &'a str) {
        pg::appendStringInfoString(self, CString::new(t).unwrap().as_ptr());
    }
    unsafe fn add_json(self, t: &'a str) {
        pg::escape_json(self, CString::new(t).unwrap().as_ptr());
    }
}

impl PGAppend<*mut i8> for pg::StringInfo {
    unsafe fn add_str(self, t: *mut i8) {
        pg::appendStringInfoString(self, t);
    }
    unsafe fn add_json(self, t: *mut i8) { pg::escape_json(self, t); }
}

impl PGAppend<*const i8> for pg::StringInfo {
    unsafe fn add_str(self, t: *const i8) {
        pg::appendStringInfoString(self, t);
    }
    unsafe fn add_json(self, t: *const i8) { pg::escape_json(self, t); }
}

struct Wrapped(pg::Enum_ReorderBufferChangeType);

impl fmt::Display for Wrapped {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use pg::Enum_ReorderBufferChangeType::*;
        #[allow(unreachable_patterns)]
        let formatted_token = match self.0 {
            REORDER_BUFFER_CHANGE_INSERT => "insert",
            REORDER_BUFFER_CHANGE_UPDATE => "update",
            REORDER_BUFFER_CHANGE_DELETE => "delete",
            REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT => "internal_snapshot",
            REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID => "internal_command_id",
            REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID => "internal_tuplecid",
            #[cfg_attr(rustfmt, rustfmt_skip)]
            REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT =>
                "internal_spec_insert",
            _ => "unknown_change_type",   // NB: Unreachable after Postgres 9.4
        };
        write!(f, "{}", formatted_token)
    }
}

unsafe fn append_change(
    relation: pg::Relation,
    change: *mut pg::ReorderBufferChange,
    out: pg::StringInfo,
) {
    use pg::Enum_ReorderBufferChangeType::*;
    let relid = (*relation).rd_id;
    let name = pg::get_rel_name(relid);
    let ns = pg::get_namespace_name(pg::get_rel_namespace(relid));
    let qualified_name = pg::quote_qualified_identifier(ns, name);
    let tuple_desc = (*relation).rd_att;
    let tuples = (*change).data.tp();
    let tuple_new = (*tuples).newtuple;
    let tuple_old = (*tuples).oldtuple;
    let token = match (*change).action {
        REORDER_BUFFER_CHANGE_INSERT => "insert",
        REORDER_BUFFER_CHANGE_UPDATE => "update",
        REORDER_BUFFER_CHANGE_DELETE => "delete",
        _ => "unrecognized",
    };
    if token == "unrecognized" {
        log!(
            format!(
                "Unrecognized Change Action: [ {} ]",
                Wrapped((*change).action)
            )
            .as_str()
        );
        return;
    }
    out.add_str("{ ");
    out.add_json(token);
    out.add_str(": ");
    append_tuple_buf_as_json(tuple_new, tuple_desc, out);
    if !tuple_old.is_null() {
        out.add_str(", \"@\": ");
        append_tuple_buf_as_json(tuple_old, tuple_desc, out);
    }
    out.add_str(", ");
    out.add_json("table");
    out.add_str(": ");
    out.add_json(qualified_name);
    out.add_str(" }");
}

unsafe fn append_tuple_buf_as_json(
    data: *mut pg::ReorderBufferTupleBuf,
    desc: pg::TupleDesc,
    out: pg::StringInfo,
) {
    if !data.is_null() {
        let heap_tuple = &mut (*data).tuple;
        let n = (*desc).natts as usize;
        let attrs = (*desc).attrs;

        // Pull out every single field to check for stale TOAST.
        let mut datums: Vec<pg::Datum> = Vec::new();
        let mut nulls: Vec<pg::_bool> = Vec::new();
        datums.resize(n, 0);
        nulls.resize(n, CFALSE);
        pg::heap_deform_tuple(
            heap_tuple,
            desc,
            datums.as_mut_ptr(),
            nulls.as_mut_ptr(),
        );

        let mut skip: Vec<pg::Form_pg_attribute> = Vec::with_capacity(n);

        for i in 0..n {
            let datum: pg::Datum = datums[i];
            let attr = *attrs.offset(i as isize);
            if datum == 0 || (*attr).attnum <= 0 {
                continue;
            }
            if (*attr).attisdropped == CFALSE && is_stale_toast(datum, attr) {
                skip.push(attr);
                // Mark as NULL to trick heap_form_tuple().
                nulls[i] = CTRUE;
            }
        }

        // Mark as dropped to trick row_to_json().
        for attr in &mut skip {
            (**attr).attisdropped = CTRUE;
        }

        let new =
            pg::heap_form_tuple(desc, datums.as_mut_ptr(), nulls.as_mut_ptr());

        let datum = pg::heap_copy_tuple_as_datum(new, desc);
        let empty_oid: pg::Oid = 0;
        let json =
            pg::DirectFunctionCall1Coll(Some(row_to_json), empty_oid, datum);

        // Set back to true because who knows how else these attrs, which are
        // part of the passed in tuple description, are being used.
        for attr in &mut skip {
            (**attr).attisdropped = CFALSE;
        }

        let ptr = json as *const pg::Struct_varlena;
        let text = pg::text_to_cstring(ptr);
        pg::appendStringInfoString(out, text);

        if skip.len() > 0 {
            out.add_str(", ");
            out.add_json("skipped");
            out.add_str(": [");
            for (i, attr) in skip.into_iter().enumerate() {
                if i > 0 {
                    out.add_str(", ");
                }
                out.add_json((*attr).attname.data.as_mut_ptr());
            }
            out.add_str("]");
        }
    } else {
        out.add_str("{}");
    }
}

unsafe fn append_schema(relation: pg::Relation, out: pg::StringInfo) {
    let relid = (*relation).rd_id;
    let tupdesc = (*relation).rd_att;
    let name = pg::get_rel_name(relid);
    let ns = pg::get_namespace_name(pg::get_rel_namespace(relid));
    let qualified_name = pg::quote_qualified_identifier(ns, name);
    out.add_str("{ ");
    out.add_json("schema");
    out.add_str(": ");
    out.add_str("[");
    let mut first: bool = true;
    for i in 0..(*tupdesc).natts {
        let attr = *(*tupdesc).attrs.offset(i as isize);
        let num = (*attr).attnum;
        if (*attr).attisdropped == CTRUE || num <= 0 {
            continue;
        }
        let col = pg::get_attname(relid, num);
        let typ = pg::format_type_be(pg::get_atttype(relid, num));
        if !first {
            out.add_str(",");
        } else {
            first = false;
        }
        out.add_str("{");
        out.add_json(col);
        out.add_str(":");
        out.add_json(typ);
        out.add_str("}");
    }
    out.add_str("]");
    out.add_str(", ");
    out.add_json("table");
    out.add_str(": ");
    out.add_json(qualified_name);
    out.add_str(" }");
}

unsafe fn append_message(
    transactional: pg::_bool,
    prefix: *const std::os::raw::c_char,
    message_size: pg::Size,
    message: *const std::os::raw::c_char,
    out: pg::StringInfo,
) {
    // { "message": %s,
    out.add_str("{ ");
    out.add_json("message");
    out.add_str(": \"");
    // TODO: Can we escape this like add_json?
    pg::appendBinaryStringInfo(out, message, message_size as i32);
    out.add_str("\", ");

    // "prefix": %s,
    out.add_json("prefix");
    out.add_str(": ");
    out.add_json(prefix);
    out.add_str(", ");

    // "transactional": %s }
    out.add_json("transactional");
    out.add_str(": ");

    if transactional == CTRUE {
        out.add_str("true");
    } else {
        out.add_str("false");
    }

    out.add_str(" }");
}

extern "C" fn row_to_json(fcinfo: pg::FunctionCallInfo) -> pg::Datum {
    // We wrap the unsafe call to make it safe, so that it can be passed as
    // a function pointer to DirectFunctionCall1Coll(). This is a spurious
    // artifact of the generated binding.
    unsafe { pg::row_to_json(fcinfo) }
}

/* This is a simulation of `VARATT_IS_EXTERNAL_ONDISK`.

```c
#define VARATT_IS_EXTERNAL_ONDISK(PTR) \
  (VARATT_IS_EXTERNAL(PTR) && VARTAG_EXTERNAL(PTR) == VARTAG_ONDISK)

    #define VARATT_IS_EXTERNAL(PTR)             VARATT_IS_1B_E(PTR)

        #define VARATT_IS_1B_E(PTR) \
            ((((varattrib_1b *) (PTR))->va_header) == 0x01)

    #define VARTAG_EXTERNAL(PTR)                VARTAG_1B_E(PTR)

        #define VARTAG_1B_E(PTR) \
            (((varattrib_1b_e *) (PTR))->va_tag)
```
*/
unsafe fn is_stale_toast(
    datum: pg::Datum,
    attr: pg::Form_pg_attribute,
) -> bool {
    use pg::Enum_vartag_external::VARTAG_ONDISK;
    let mut o: pg::Oid = 0; // Output function; not used
    let mut is_variable_length: pg::_bool = CFALSE;
    pg::getTypeOutputInfo((*attr).atttypid, &mut o, &mut is_variable_length);
    if is_variable_length == CTRUE {
        // Cast to varlena metadata type.
        let v = datum as *const pg::varattrib_1b_e;
        if (*v).va_header != 0x01 {
            return false;
        }
        return (*v).va_tag == (VARTAG_ONDISK as u8);
    }
    return false;
}

// Symbols Postgres needs to find.

#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn _PG_init() {}

#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn _PG_output_plugin_init(
    cb: *mut pg::OutputPluginCallbacks,
) {
    init(cb);
}

// Miscellaneous.

const CTRUE: pg::_bool = 1;
const CFALSE: pg::_bool = 0;

pub unsafe fn elog(file: &str, line: u32, function: &str, msg: &str) {
    let level = 15; // The LOG level of logging is normally server-only
    pg::elog_start(
        CString::new(file).unwrap().as_ptr(),
        line as ::std::os::raw::c_int,
        CString::new(function).unwrap().as_ptr(),
    );
    pg::elog_finish(
        level,
        CString::new("%s").unwrap().as_ptr(),
        CString::new(msg).unwrap().as_ptr(),
    );
}

pub unsafe fn fmt_name(name: pg::NameData) -> String {
    let cstr = CStr::from_ptr(name.data.as_ptr());
    format!("{:?}", cstr.to_owned())
}
<|endoftext|>