<commit_before>//! HTML encoding for node types in the "data" category
//!
//! ## Validators
//!
//! Currently validators are used for the `validator` property of two node
//! types: `DatatableColumn` and `Parameter`. This modules provides to functions
//! for each of these use cases.
//!
//! For use in a `DatatableColumn`, the usual `ToHtml` trait is implemented which
//! generates a custom element e.g. `<stencila-number-validator>` to indicate the
//! type of the column and it's restrictions.
//!
//! For use in a `Parameter`, the `ToAttrs` trait is implemented so that the validator
//! can be represented as standard HTML form input attributes e.g. `type="number" minimum="0"`

use std::string::ToString;

use codec::common::tracing;
use codec_txt::ToTxt;
use node_dispatch::dispatch_validator;
use stencila_schema::*;

use super::{
    attr, attr_bool, attr_id, attr_itemprop, attr_itemtype, attr_itemtype_str, attr_prop,
    attr_slot, concat, concat_html, elem, elem_empty, elem_meta, elem_placeholder, nothing,
    EncodeContext, ToHtml,
};

/// Encode a `Datatable`
impl ToHtml for Datatable {
    fn to_html(&self, context: &EncodeContext) -> String {
        let columns = elem(
            "tr",
            &[attr_prop("columns")],
            &concat_html(&self.columns, context),
        );
        let rows = elem_meta("rows", "");
        let values = elem_meta("values", "");

        let head = elem("thead", &[], &[columns, rows, values].concat());

        let rows = self.columns.iter().fold(0, |mut rows, column| {
            let len = column.values.len();
            if len > rows {
                rows = len
            }
            rows
        });
        let rows = (0..rows)
            .into_iter()
            .map(|row| {
                let data = concat(&self.columns, |column| {
                    let data = if let Some(data) = column.values.get(row) {
                        data.to_html(context)
                    } else {
                        nothing()
                    };
                    elem("td", &[], &data)
                });
                elem("tr", &[], &data)
            })
            .collect::<Vec<String>>()
            .concat();
        let body = elem("tbody", &[], &rows);

        elem(
            "stencila-datatable",
            &[attr_itemtype::<Self>()],
            &elem("table", &[], &[head, body].concat()),
        )
    }
}

/// Encode a `DatatableColumn`
impl ToHtml for DatatableColumn {
    fn to_html(&self, context: &EncodeContext) -> String {
        let name = elem("span", &[attr_prop("name")], &self.name.to_html(context));
        elem("th", &[attr_itemtype::<Self>()], &[name].concat())
    }
}

/// Encode a `Parameter`
impl ToHtml for Parameter {
    fn to_html(&self, context: &EncodeContext) -> String {
        // Generate a unique id for the <input> to be able to associate the
        // <label> with it. We avoid using `self.name` or `self.name` which could
        // get updated via patches (and thus would need changing in two places).
        // But for determinism in tests, create a static id.
        let input_id = match cfg!(test) {
            true => "input-id".to_string(),
            false => uuids::generate("in").to_string(),
        };

        let name = elem(
            "label",
            &[attr_prop("name"), attr_slot("name"), attr("for", &input_id)],
            &self.name,
        );

        // Meta elements for `validator`, `default`, and `value` that add HTML Microdata and
        // are used as "proxies" to the attributes added to the <input> element when patching the DOM

        let validator = elem_empty(
            "meta",
            &[
                attr_itemprop("validator"),
                self.validator
                    .as_deref()
                    .map_or_else(nothing, |node| attr_itemtype_str(node.as_ref())),
            ],
        );

        let default = elem_empty(
            "meta",
            &[
                attr_itemprop("default"),
                self.default
                    .as_deref()
                    .map_or_else(nothing, |node| attr_itemtype_str(node.as_ref())),
                self.default
                    .as_deref()
                    .map_or_else(nothing, |node| attr("content", &node.to_txt())),
            ],
        );

        let value = elem_empty(
            "meta",
            &[
                attr_itemprop("value"),
                self.value
                    .as_deref()
                    .map_or_else(nothing, |node| attr_itemtype_str(node.as_ref())),
                self.value
                    .as_deref()
                    .map_or_else(nothing, |node| attr("content", &node.to_txt())),
            ],
        );

        let input = if let Some(ValidatorTypes::EnumValidator(validator)) =
            self.validator.as_deref()
        {
            // Select the `value`, or secondarily, the `default` <option>
            let value = self
                .value
                .as_deref()
                .or(self.default.as_deref())
                .map(|node| node.to_txt())
                .unwrap_or_default();

            let options = concat(&validator.values, |node| {
                let txt = node.to_txt();
                let selected = if txt == value { "selected" } else { "" };
                elem("option", &[attr("value", &txt), selected.to_string()], &txt)
            });

            elem(
                "select",
                &[attr("id", &input_id), attr_slot("value")],
                &[options].concat(),
            )
        } else {
            // Get the attrs corresponding to the validator so that we
            // can add them to the <input> element
            let validator_attrs = match &self.validator {
                Some(validator) => validator.to_attrs(context),
                None => vec![attr("type", "text")],
            };

            // If the parameter's `default` property is set then set a `placeholder` attribute
            let placeholder_attr = match &self.default {
                Some(node) => attr("placeholder", &node.to_txt()),
                None => "".to_string(),
            };

            let value_attr = match &self.value {
                Some(node) => attr("value", &node.to_txt()),
                None => "".to_string(),
            };

            // Add a size attribute which will expand the horizontal with of the input to match the content.
            // This is useful when generating RPNGs to avoid extra whitespace. There is not an easy way to do this
            // using CSS, see https://css-tricks.com/auto-growing-inputs-textareas/
            let size_attr = self
                .value
                .as_ref()
                .or(self.default.as_ref())
                .map(|node| attr("size", &(node.to_txt().len() + 1).to_string()))
                .unwrap_or_default();

            // If a `BooleanValidator` then need to set the `checked` attribute if true
            let checked_attr =
                if let (Some(ValidatorTypes::BooleanValidator(..)), Some(Node::Boolean(true))) =
                    (self.validator.as_deref(), self.value.as_deref())
                {
                    attr("checked", "")
                } else {
                    nothing()
                };

            elem_empty(
                "input",
                &[
                    attr("id", &input_id),
                    attr_slot("value"),
                    validator_attrs.join(" "),
                    placeholder_attr,
                    value_attr,
                    size_attr,
                    checked_attr,
                ],
            )
        };

        elem(
            "stencila-parameter",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &[name, validator, default, value, input].concat(),
        )
    }
}

/// Encode a `ValidatorTypes` variant
impl ToHtml for ValidatorTypes {
    fn to_html(&self, context: &EncodeContext) -> String {
        dispatch_validator!(self, to_html, context)
    }

    fn to_attrs(&self, context: &EncodeContext) -> Vec<String> {
        dispatch_validator!(self, to_attrs, context)
    }
}

/// Encode a `Validator`
///
/// Note that this is just an empty base for all other validators and should not
/// really be part of the `ValidatorTypes` enum and never be instantiated.
/// So this just logs a warning returns an empty string.
impl ToHtml for Validator {
    fn to_html(&self, _context: &EncodeContext) -> String {
        tracing::warn!("Unexpected instantiation of `Validator` type");
        String::new()
    }
}

/// Encode a `ArrayValidator`
///
/// No properties, so just an empty element used to indicate the type
impl ToHtml for ArrayValidator {
    fn to_html(&self, _context: &EncodeContext) -> String {
        todo!()
    }
}

/// Encode a `BooleanValidator`
///
/// No properties, so just an empty element used to indicate the type
impl ToHtml for BooleanValidator {
    fn to_html(&self, _context: &EncodeContext) -> String {
        elem_empty(
            "stencila-boolean-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        vec![attr("type", "checkbox")]
    }
}

/// Encode a `ConstantValidator`
///
/// Encodes the constant `value`.
impl ToHtml for ConstantValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        let value = elem(
            "span",
            &[attr_prop("value"), attr_slot("value")],
            &self.value.to_html(context),
        );
        elem(
            "stencila-constant-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &value,
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        // The `type=text` could be changed to depend on the
        // `Node` type of the `value`.
        vec![attr("type", "text"), attr_bool("readonly")]
    }
}

/// Encode a `EnumValidator`
///
/// Encodes the possible `values`. Each of these will be an element
/// indicating the type of the value.
impl ToHtml for EnumValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        let values = elem(
            "div",
            &[attr_prop("values"), attr_slot("values")],
            &self.values.to_html(context),
        );
        elem(
            "stencila-enum-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &values,
        )
    }
}

/// Encode a `IntegerValidator`
///
/// No properties, so just an empty element used to indicate the type.
/// It is probable that in the future, this will have properties such as
/// `minimum` and `maximum` like `NumberValidator`.
impl ToHtml for IntegerValidator {
    fn to_html(&self, _context: &EncodeContext) -> String {
        elem_empty(
            "stencila-integer-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        vec![attr("type", "number")]
    }
}

/// Encode a `NumberValidator`
///
/// Encodes all properties
impl ToHtml for NumberValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        // We use `.map(|value| value.to_string())` for properties so they get
        // rendered as text, not wrapped as a `<span itemtype="https://schema.org/Number"...`

        let minimum = elem_placeholder(
            "span",
            &[attr_prop("minimum"), attr_slot("minimum")],
            &self.minimum.as_ref().map(|value| value.to_string()),
            context,
        );

        let exclusive_minimum = elem_placeholder(
            "span",
            &[
                attr_prop("exclusive_minimum"),
                attr_slot("exclusive-minimum"),
            ],
            &self
                .exclusive_minimum
                .as_ref()
                .map(|value| value.to_string()),
            context,
        );

        let maximum = elem_placeholder(
            "span",
            &[attr_prop("maximum"), attr_slot("maximum")],
            &self.maximum.as_ref().map(|value| value.to_string()),
            context,
        );

        let exclusive_maximum = elem_placeholder(
            "span",
            &[
                attr_prop("exclusive_maximum"),
                attr_slot("exclusive-maximum"),
            ],
            &self
                .exclusive_maximum
                .as_ref()
                .map(|value| value.to_string()),
            context,
        );

        let multiple_of = elem_placeholder(
            "span",
            &[attr_prop("multiple_of"), attr_slot("multiple-of")],
            &self.multiple_of.as_ref().map(|value| value.to_string()),
            context,
        );

        elem(
            "stencila-number-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &[
                minimum,
                exclusive_minimum,
                maximum,
                exclusive_maximum,
                multiple_of,
            ]
            .concat(),
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number for
        // attributes supported here.
        let mut attrs = Vec::with_capacity(4);
        attrs.push(attr("type", "number"));
        if let Some(min) = &self.minimum {
            attrs.push(attr("min", &min.to_string()))
        }
        if let Some(max) = &self.maximum {
            attrs.push(attr("max", &max.to_string()))
        }
        if let Some(step) = &self.multiple_of {
            attrs.push(attr("step", &step.to_string()))
        }
        attrs
    }
}

/// Encode a `StringValidator`
///
/// Encodes all properties
impl ToHtml for StringValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        let min_length = elem_placeholder(
            "span",
            &[attr_prop("min_length"), attr_slot("min-length")],
            &self.min_length.map(|value| value.to_string()),
            context,
        );

        let max_length = elem_placeholder(
            "span",
            &[attr_prop("max_length"), attr_slot("max-length")],
            &self.max_length.map(|value| value.to_string()),
            context,
        );

        let pattern = elem_placeholder(
            "span",
            &[attr_prop("pattern"), attr_slot("pattern")],
            &self.pattern,
            context,
        );

        elem(
            "stencila-string-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &[min_length, max_length, pattern].concat(),
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/text for
        // attributes supported here.
        let mut attrs = Vec::with_capacity(4);
        attrs.push(attr("type", "text"));
        if let Some(min_length) = self.min_length {
            attrs.push(attr("minlength", &min_length.to_string()))
        }
        if let Some(max_length) = self.max_length {
            attrs.push(attr("maxlength", &max_length.to_string()))
        }
        if let Some(pattern) = &self.pattern {
            attrs.push(attr("pattern", pattern))
        }
        attrs
    }
}

/// Encode a `TupleValidator`
///
/// Encodes each of the validators in `items`
impl ToHtml for TupleValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        elem(
            "stencila-tuple-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &self.items.to_html(context),
        )
    }
}
<commit_msg>fix(Integer parameters): Encode all properties to HTML<commit_after>//! HTML encoding for node types in the "data" category
//!
//! ## Validators
//!
//! Currently validators are used for the `validator` property of two node
//! types: `DatatableColumn` and `Parameter`. This modules provides to functions
//! for each of these use cases.
//!
//! For use in a `DatatableColumn`, the usual `ToHtml` trait is implemented which
//! generates a custom element e.g. `<stencila-number-validator>` to indicate the
//! type of the column and it's restrictions.
//!
//! For use in a `Parameter`, the `ToAttrs` trait is implemented so that the validator
//! can be represented as standard HTML form input attributes e.g. `type="number" minimum="0"`

use std::string::ToString;

use codec::common::tracing;
use codec_txt::ToTxt;
use node_dispatch::dispatch_validator;
use stencila_schema::*;

use super::{
    attr, attr_bool, attr_id, attr_itemprop, attr_itemtype, attr_itemtype_str, attr_prop,
    attr_slot, concat, concat_html, elem, elem_empty, elem_meta, elem_placeholder, nothing,
    EncodeContext, ToHtml,
};

/// Encode a `Datatable`
impl ToHtml for Datatable {
    fn to_html(&self, context: &EncodeContext) -> String {
        let columns = elem(
            "tr",
            &[attr_prop("columns")],
            &concat_html(&self.columns, context),
        );
        let rows = elem_meta("rows", "");
        let values = elem_meta("values", "");

        let head = elem("thead", &[], &[columns, rows, values].concat());

        let rows = self.columns.iter().fold(0, |mut rows, column| {
            let len = column.values.len();
            if len > rows {
                rows = len
            }
            rows
        });
        let rows = (0..rows)
            .into_iter()
            .map(|row| {
                let data = concat(&self.columns, |column| {
                    let data = if let Some(data) = column.values.get(row) {
                        data.to_html(context)
                    } else {
                        nothing()
                    };
                    elem("td", &[], &data)
                });
                elem("tr", &[], &data)
            })
            .collect::<Vec<String>>()
            .concat();
        let body = elem("tbody", &[], &rows);

        elem(
            "stencila-datatable",
            &[attr_itemtype::<Self>()],
            &elem("table", &[], &[head, body].concat()),
        )
    }
}

/// Encode a `DatatableColumn`
impl ToHtml for DatatableColumn {
    fn to_html(&self, context: &EncodeContext) -> String {
        let name = elem("span", &[attr_prop("name")], &self.name.to_html(context));
        elem("th", &[attr_itemtype::<Self>()], &[name].concat())
    }
}

/// Encode a `Parameter`
impl ToHtml for Parameter {
    fn to_html(&self, context: &EncodeContext) -> String {
        // Generate a unique id for the <input> to be able to associate the
        // <label> with it. We avoid using `self.name` or `self.name` which could
        // get updated via patches (and thus would need changing in two places).
        // But for determinism in tests, create a static id.
        let input_id = match cfg!(test) {
            true => "input-id".to_string(),
            false => uuids::generate("in").to_string(),
        };

        let name = elem(
            "label",
            &[attr_prop("name"), attr_slot("name"), attr("for", &input_id)],
            &self.name,
        );

        // Meta elements for `validator`, `default`, and `value` that add HTML Microdata and
        // are used as "proxies" to the attributes added to the <input> element when patching the DOM

        let validator = elem_empty(
            "meta",
            &[
                attr_itemprop("validator"),
                self.validator
                    .as_deref()
                    .map_or_else(nothing, |node| attr_itemtype_str(node.as_ref())),
            ],
        );

        let default = elem_empty(
            "meta",
            &[
                attr_itemprop("default"),
                self.default
                    .as_deref()
                    .map_or_else(nothing, |node| attr_itemtype_str(node.as_ref())),
                self.default
                    .as_deref()
                    .map_or_else(nothing, |node| attr("content", &node.to_txt())),
            ],
        );

        let value = elem_empty(
            "meta",
            &[
                attr_itemprop("value"),
                self.value
                    .as_deref()
                    .map_or_else(nothing, |node| attr_itemtype_str(node.as_ref())),
                self.value
                    .as_deref()
                    .map_or_else(nothing, |node| attr("content", &node.to_txt())),
            ],
        );

        let input = if let Some(ValidatorTypes::EnumValidator(validator)) =
            self.validator.as_deref()
        {
            // Select the `value`, or secondarily, the `default` <option>
            let value = self
                .value
                .as_deref()
                .or(self.default.as_deref())
                .map(|node| node.to_txt())
                .unwrap_or_default();

            let options = concat(&validator.values, |node| {
                let txt = node.to_txt();
                let selected = if txt == value { "selected" } else { "" };
                elem("option", &[attr("value", &txt), selected.to_string()], &txt)
            });

            elem(
                "select",
                &[attr("id", &input_id), attr_slot("value")],
                &[options].concat(),
            )
        } else {
            // Get the attrs corresponding to the validator so that we
            // can add them to the <input> element
            let validator_attrs = match &self.validator {
                Some(validator) => validator.to_attrs(context),
                None => vec![attr("type", "text")],
            };

            // If the parameter's `default` property is set then set a `placeholder` attribute
            let placeholder_attr = match &self.default {
                Some(node) => attr("placeholder", &node.to_txt()),
                None => "".to_string(),
            };

            let value_attr = match &self.value {
                Some(node) => attr("value", &node.to_txt()),
                None => "".to_string(),
            };

            // Add a size attribute which will expand the horizontal with of the input to match the content.
            // This is useful when generating RPNGs to avoid extra whitespace. There is not an easy way to do this
            // using CSS, see https://css-tricks.com/auto-growing-inputs-textareas/
            let size_attr = self
                .value
                .as_ref()
                .or(self.default.as_ref())
                .map(|node| attr("size", &(node.to_txt().len() + 1).to_string()))
                .unwrap_or_default();

            // If a `BooleanValidator` then need to set the `checked` attribute if true
            let checked_attr =
                if let (Some(ValidatorTypes::BooleanValidator(..)), Some(Node::Boolean(true))) =
                    (self.validator.as_deref(), self.value.as_deref())
                {
                    attr("checked", "")
                } else {
                    nothing()
                };

            elem_empty(
                "input",
                &[
                    attr("id", &input_id),
                    attr_slot("value"),
                    validator_attrs.join(" "),
                    placeholder_attr,
                    value_attr,
                    size_attr,
                    checked_attr,
                ],
            )
        };

        elem(
            "stencila-parameter",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &[name, validator, default, value, input].concat(),
        )
    }
}

/// Encode a `ValidatorTypes` variant
impl ToHtml for ValidatorTypes {
    fn to_html(&self, context: &EncodeContext) -> String {
        dispatch_validator!(self, to_html, context)
    }

    fn to_attrs(&self, context: &EncodeContext) -> Vec<String> {
        dispatch_validator!(self, to_attrs, context)
    }
}

/// Encode a `Validator`
///
/// Note that this is just an empty base for all other validators and should not
/// really be part of the `ValidatorTypes` enum and never be instantiated.
/// So this just logs a warning returns an empty string.
impl ToHtml for Validator {
    fn to_html(&self, _context: &EncodeContext) -> String {
        tracing::warn!("Unexpected instantiation of `Validator` type");
        String::new()
    }
}

/// Encode a `ArrayValidator`
///
/// No properties, so just an empty element used to indicate the type
impl ToHtml for ArrayValidator {
    fn to_html(&self, _context: &EncodeContext) -> String {
        todo!()
    }
}

/// Encode a `BooleanValidator`
///
/// No properties, so just an empty element used to indicate the type
impl ToHtml for BooleanValidator {
    fn to_html(&self, _context: &EncodeContext) -> String {
        elem_empty(
            "stencila-boolean-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        vec![attr("type", "checkbox")]
    }
}

/// Encode a `ConstantValidator`
///
/// Encodes the constant `value`.
impl ToHtml for ConstantValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        let value = elem(
            "span",
            &[attr_prop("value"), attr_slot("value")],
            &self.value.to_html(context),
        );
        elem(
            "stencila-constant-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &value,
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        // The `type=text` could be changed to depend on the
        // `Node` type of the `value`.
        vec![attr("type", "text"), attr_bool("readonly")]
    }
}

/// Encode a `EnumValidator`
///
/// Encodes the possible `values`. Each of these will be an element
/// indicating the type of the value.
impl ToHtml for EnumValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        let values = elem(
            "div",
            &[attr_prop("values"), attr_slot("values")],
            &self.values.to_html(context),
        );
        elem(
            "stencila-enum-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &values,
        )
    }
}

fn numeric_validator_content(
    context: &EncodeContext,
    minimum: &Option<Number>,
    exclusive_minimum: &Option<Number>,
    maximum: &Option<Number>,
    exclusive_maximum: &Option<Number>,
    multiple_of: &Option<Number>,
) -> String {
    // We use `.map(|value| value.to_string())` for properties so they get
    // rendered as text, not wrapped as a `<span itemtype="https://schema.org/Number"...`

    let minimum = elem_placeholder(
        "span",
        &[attr_prop("minimum"), attr_slot("minimum")],
        &minimum.as_ref().map(|value| value.to_string()),
        context,
    );

    let exclusive_minimum = elem_placeholder(
        "span",
        &[
            attr_prop("exclusive_minimum"),
            attr_slot("exclusive-minimum"),
        ],
        &exclusive_minimum.as_ref().map(|value| value.to_string()),
        context,
    );

    let maximum = elem_placeholder(
        "span",
        &[attr_prop("maximum"), attr_slot("maximum")],
        &maximum.as_ref().map(|value| value.to_string()),
        context,
    );

    let exclusive_maximum = elem_placeholder(
        "span",
        &[
            attr_prop("exclusive_maximum"),
            attr_slot("exclusive-maximum"),
        ],
        &exclusive_maximum.as_ref().map(|value| value.to_string()),
        context,
    );

    let multiple_of = elem_placeholder(
        "span",
        &[attr_prop("multiple_of"), attr_slot("multiple-of")],
        &multiple_of.as_ref().map(|value| value.to_string()),
        context,
    );

    [
        minimum,
        exclusive_minimum,
        maximum,
        exclusive_maximum,
        multiple_of,
    ]
    .concat()
}

fn numeric_validator_attrs(
    min: &Option<Number>,
    max: &Option<Number>,
    step: &Option<Number>,
) -> Vec<String> {
    // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number for
    // attributes supported here.
    let mut attrs = Vec::with_capacity(4);
    attrs.push(attr("type", "number"));
    if let Some(min) = &min {
        attrs.push(attr("min", &min.to_string()))
    }
    if let Some(max) = &max {
        attrs.push(attr("max", &max.to_string()))
    }
    if let Some(step) = &step {
        attrs.push(attr("step", &step.to_string()))
    }
    attrs
}

/// Encode a `IntegerValidator`
impl ToHtml for IntegerValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        elem(
            "stencila-integer-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &numeric_validator_content(
                context,
                &self.minimum,
                &self.exclusive_minimum,
                &self.maximum,
                &self.exclusive_maximum,
                &self.multiple_of,
            ),
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        numeric_validator_attrs(
            &self.minimum.or(self.exclusive_minimum),
            &self.maximum.or(self.exclusive_maximum),
            &self.multiple_of.or(Some(Number(1f64))),
        )
    }
}

/// Encode a `NumberValidator`
impl ToHtml for NumberValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        elem(
            "stencila-number-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &numeric_validator_content(
                context,
                &self.minimum,
                &self.exclusive_minimum,
                &self.maximum,
                &self.exclusive_maximum,
                &self.multiple_of,
            ),
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        numeric_validator_attrs(
            &self.minimum.or(self.exclusive_minimum),
            &self.maximum.or(self.exclusive_maximum),
            &self.multiple_of,
        )
    }
}

/// Encode a `StringValidator`
///
/// Encodes all properties
impl ToHtml for StringValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        let min_length = elem_placeholder(
            "span",
            &[attr_prop("min_length"), attr_slot("min-length")],
            &self.min_length.map(|value| value.to_string()),
            context,
        );

        let max_length = elem_placeholder(
            "span",
            &[attr_prop("max_length"), attr_slot("max-length")],
            &self.max_length.map(|value| value.to_string()),
            context,
        );

        let pattern = elem_placeholder(
            "span",
            &[attr_prop("pattern"), attr_slot("pattern")],
            &self.pattern,
            context,
        );

        elem(
            "stencila-string-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &[min_length, max_length, pattern].concat(),
        )
    }

    fn to_attrs(&self, _context: &EncodeContext) -> Vec<String> {
        // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/text for
        // attributes supported here.
        let mut attrs = Vec::with_capacity(4);
        attrs.push(attr("type", "text"));
        if let Some(min_length) = self.min_length {
            attrs.push(attr("minlength", &min_length.to_string()))
        }
        if let Some(max_length) = self.max_length {
            attrs.push(attr("maxlength", &max_length.to_string()))
        }
        if let Some(pattern) = &self.pattern {
            attrs.push(attr("pattern", pattern))
        }
        attrs
    }
}

/// Encode a `TupleValidator`
///
/// Encodes each of the validators in `items`
impl ToHtml for TupleValidator {
    fn to_html(&self, context: &EncodeContext) -> String {
        elem(
            "stencila-tuple-validator",
            &[attr_itemtype::<Self>(), attr_id(&self.id)],
            &self.items.to_html(context),
        )
    }
}
<|endoftext|>


<commit_before>pub fn to_camel_case(ident: &str) -> Option<String> {
    let mut output = String::new();

    if is_camel_case(ident) {
        return None;
    }

    let mut capital_added = false;
    for chr in ident.chars() {
        if chr.is_alphabetic() {
            if !capital_added {
                output.push(chr.to_ascii_uppercase());
                capital_added = true;
            } else {
                output.push(chr.to_ascii_lowercase());
            }
        } else if chr == '_' {
            // Skip this character and make the next one capital.
            capital_added = false;
        } else {
            // Put the characted as-is.
            output.push(chr);
        }
    }

    if output == ident {
        None
    } else {
        Some(output)
    }
}

pub fn to_lower_snake_case(ident: &str) -> Option<String> {
    // First, assume that it's UPPER_SNAKE_CASE.
    if let Some(normalized) = to_lower_snake_case_from_upper_snake_case(ident) {
        return Some(normalized);
    }

    // Otherwise, assume that it's CamelCase.
    let lower_snake_case = stdx::to_lower_snake_case(ident);

    if lower_snake_case == ident {
        None
    } else {
        Some(lower_snake_case)
    }
}

fn to_lower_snake_case_from_upper_snake_case(ident: &str) -> Option<String> {
    if is_upper_snake_case(ident) {
        let string = ident.chars().map(|c| c.to_ascii_lowercase()).collect();
        Some(string)
    } else {
        None
    }
}

fn is_upper_snake_case(ident: &str) -> bool {
    ident.chars().all(|c| c.is_ascii_uppercase() || c == '_')
}

fn is_camel_case(ident: &str) -> bool {
    // We assume that the string is either snake case or camel case.
    // `_` is allowed only at the beginning or in the end of identifier, not between characters.
    ident.trim_matches('_').chars().all(|c| c != '_')
        && ident.chars().find(|c| c.is_alphabetic()).map(|c| c.is_ascii_uppercase()).unwrap_or(true)
}

#[cfg(test)]
mod tests {
    use super::*;
    use expect_test::{expect, Expect};

    fn check<F: Fn(&str) -> Option<String>>(fun: F, input: &str, expect: Expect) {
        // `None` is translated to empty string, meaning that there is nothing to fix.
        let output = fun(input).unwrap_or_default();

        expect.assert_eq(&output);
    }

    #[test]
    fn test_to_lower_snake_case() {
        check(to_lower_snake_case, "lower_snake_case", expect![[""]]);
        check(to_lower_snake_case, "UPPER_SNAKE_CASE", expect![["upper_snake_case"]]);
        check(to_lower_snake_case, "Weird_Case", expect![["weird_case"]]);
        check(to_lower_snake_case, "CamelCase", expect![["camel_case"]]);
    }

    #[test]
    fn test_to_camel_case() {
        check(to_camel_case, "CamelCase", expect![[""]]);
        check(to_camel_case, "CamelCase_", expect![[""]]);
        check(to_camel_case, "_CamelCase", expect![[""]]);
        check(to_camel_case, "lower_snake_case", expect![["LowerSnakeCase"]]);
        check(to_camel_case, "UPPER_SNAKE_CASE", expect![["UpperSnakeCase"]]);
        check(to_camel_case, "Weird_Case", expect![["WeirdCase"]]);
        check(to_camel_case, "name", expect![["Name"]]);
    }
}
<commit_msg>Refactor string helpers for decl_check module<commit_after>#[derive(Debug)]
enum DetectedCase {
    LowerCamelCase,
    UpperCamelCase,
    LowerSnakeCase,
    UpperSnakeCase,
    Unknown,
}

fn detect_case(ident: &str) -> DetectedCase {
    let trimmed_ident = ident.trim_matches('_');
    let first_lowercase =
        trimmed_ident.chars().next().map(|chr| chr.is_ascii_lowercase()).unwrap_or(false);
    let mut has_lowercase = first_lowercase;
    let mut has_uppercase = false;
    let mut has_underscore = false;

    for chr in trimmed_ident.chars() {
        if chr == '_' {
            has_underscore = true;
        } else if chr.is_ascii_uppercase() {
            has_uppercase = true;
        } else if chr.is_ascii_lowercase() {
            has_lowercase = true;
        }
    }

    if has_uppercase {
        if !has_lowercase {
            DetectedCase::UpperSnakeCase
        } else if !has_underscore {
            if first_lowercase {
                DetectedCase::LowerCamelCase
            } else {
                DetectedCase::UpperCamelCase
            }
        } else {
            // It has uppercase, it has lowercase, it has underscore.
            // No assumptions here
            DetectedCase::Unknown
        }
    } else {
        DetectedCase::LowerSnakeCase
    }
}

pub fn to_camel_case(ident: &str) -> Option<String> {
    let detected_case = detect_case(ident);

    match detected_case {
        DetectedCase::UpperCamelCase => return None,
        DetectedCase::LowerCamelCase => {
            let mut first_capitalized = false;
            let output = ident
                .chars()
                .map(|chr| {
                    if !first_capitalized && chr.is_ascii_lowercase() {
                        first_capitalized = true;
                        chr.to_ascii_uppercase()
                    } else {
                        chr
                    }
                })
                .collect();
            return Some(output);
        }
        _ => {}
    }

    let mut output = String::with_capacity(ident.len());

    let mut capital_added = false;
    for chr in ident.chars() {
        if chr.is_alphabetic() {
            if !capital_added {
                output.push(chr.to_ascii_uppercase());
                capital_added = true;
            } else {
                output.push(chr.to_ascii_lowercase());
            }
        } else if chr == '_' {
            // Skip this character and make the next one capital.
            capital_added = false;
        } else {
            // Put the characted as-is.
            output.push(chr);
        }
    }

    Some(output)
}

pub fn to_lower_snake_case(ident: &str) -> Option<String> {
    // First, assume that it's UPPER_SNAKE_CASE.
    match detect_case(ident) {
        DetectedCase::LowerSnakeCase => return None,
        DetectedCase::UpperSnakeCase => {
            return Some(ident.chars().map(|chr| chr.to_ascii_lowercase()).collect())
        }
        _ => {}
    }

    // Otherwise, assume that it's CamelCase.
    let lower_snake_case = stdx::to_lower_snake_case(ident);
    Some(lower_snake_case)
}

pub fn to_upper_snake_case(ident: &str) -> Option<String> {
    match detect_case(ident) {
        DetectedCase::UpperSnakeCase => return None,
        DetectedCase::LowerSnakeCase => {
            return Some(ident.chars().map(|chr| chr.to_ascii_uppercase()).collect())
        }
        _ => {}
    }

    // Normalize the string from whatever form it's in currently, and then just make it uppercase.
    let upper_snake_case =
        stdx::to_lower_snake_case(ident).chars().map(|c| c.to_ascii_uppercase()).collect();
    Some(upper_snake_case)
}

#[cfg(test)]
mod tests {
    use super::*;
    use expect_test::{expect, Expect};

    fn check<F: Fn(&str) -> Option<String>>(fun: F, input: &str, expect: Expect) {
        // `None` is translated to empty string, meaning that there is nothing to fix.
        let output = fun(input).unwrap_or_default();

        expect.assert_eq(&output);
    }

    #[test]
    fn test_to_lower_snake_case() {
        check(to_lower_snake_case, "lower_snake_case", expect![[""]]);
        check(to_lower_snake_case, "UPPER_SNAKE_CASE", expect![["upper_snake_case"]]);
        check(to_lower_snake_case, "Weird_Case", expect![["weird_case"]]);
        check(to_lower_snake_case, "CamelCase", expect![["camel_case"]]);
        check(to_lower_snake_case, "lowerCamelCase", expect![["lower_camel_case"]]);
    }

    #[test]
    fn test_to_camel_case() {
        check(to_camel_case, "CamelCase", expect![[""]]);
        check(to_camel_case, "CamelCase_", expect![[""]]);
        check(to_camel_case, "_CamelCase", expect![[""]]);
        check(to_camel_case, "lowerCamelCase", expect![["LowerCamelCase"]]);
        check(to_camel_case, "lower_snake_case", expect![["LowerSnakeCase"]]);
        check(to_camel_case, "UPPER_SNAKE_CASE", expect![["UpperSnakeCase"]]);
        check(to_camel_case, "Weird_Case", expect![["WeirdCase"]]);
        check(to_camel_case, "name", expect![["Name"]]);
    }

    #[test]
    fn test_to_upper_snake_case() {
        check(to_upper_snake_case, "UPPER_SNAKE_CASE", expect![[""]]);
        check(to_upper_snake_case, "lower_snake_case", expect![["LOWER_SNAKE_CASE"]]);
        check(to_upper_snake_case, "Weird_Case", expect![["WEIRD_CASE"]]);
        check(to_upper_snake_case, "CamelCase", expect![["CAMEL_CASE"]]);
        check(to_upper_snake_case, "lowerCamelCase", expect![["LOWER_CAMEL_CASE"]]);
    }
}
<|endoftext|>


<commit_before>use std::cmp::Ordering;
use std::collections::VecDeque;
use std::fs::{File, OpenOptions};
use std::io::SeekFrom;
use std::io::{BufRead, BufReader, BufWriter, Seek, Write};
use std::path::Path;

use tempdir::TempDir;

use super::{GlobalSettings, Line};

/// Iterator that provides sorted `T`s
pub struct ExtSortedIterator {
    buffers: Vec<VecDeque<Line>>,
    chunk_offsets: Vec<u64>,
    max_per_chunk: usize,
    chunks: usize,
    tmp_dir: TempDir,
    settings: GlobalSettings,
    failed: bool,
}

impl Iterator for ExtSortedIterator {
    type Item = Line;

    /// # Errors
    ///
    /// This method can fail due to issues reading intermediate sorted chunks
    /// from disk
    fn next(&mut self) -> Option<Self::Item> {
        if self.failed {
            return None;
        }
        // fill up any empty buffers
        let mut empty = true;
        for chunk_num in 0..self.chunks {
            if self.buffers[chunk_num as usize].is_empty() {
                let mut f = crash_if_err!(
                    1,
                    File::open(self.tmp_dir.path().join(chunk_num.to_string()))
                );
                crash_if_err!(1, f.seek(SeekFrom::Start(self.chunk_offsets[chunk_num])));
                let bytes_read = fill_buff(
                    &mut self.buffers[chunk_num as usize],
                    f,
                    self.max_per_chunk,
                    &self.settings,
                );
                self.chunk_offsets[chunk_num as usize] += bytes_read as u64;
                if !self.buffers[chunk_num as usize].is_empty() {
                    empty = false;
                }
            } else {
                empty = false;
            }
        }
        if empty {
            return None;
        }

        // find the next record to write
        // check is_empty() before unwrap()ing
        let mut idx = 0;
        for chunk_num in 0..self.chunks as usize {
            if !self.buffers[chunk_num].is_empty()
                && (self.buffers[idx].is_empty()
                    || super::compare_by(
                        self.buffers[chunk_num].front().unwrap(),
                        self.buffers[idx].front().unwrap(),
                        &self.settings,
                    ) == Ordering::Less)
            {
                idx = chunk_num;
            }
        }

        // unwrap due to checks above
        let r = self.buffers[idx].pop_front().unwrap();
        Some(r)
    }
}

/// Sort (based on `compare`) the `T`s provided by `unsorted` and return an
/// iterator
///
/// # Errors
///
/// This method can fail due to issues writing intermediate sorted chunks
/// to disk.
pub fn ext_sort(
    unsorted: impl Iterator<Item = Line>,
    settings: &GlobalSettings,
) -> ExtSortedIterator {
    let tmp_dir = crash_if_err!(1, TempDir::new_in(&settings.tmp_dir, "uutils_sort"));

    let mut iter = ExtSortedIterator {
        buffers: Vec::new(),
        chunk_offsets: Vec::new(),
        max_per_chunk: 0,
        chunks: 0,
        tmp_dir,
        settings: settings.clone(),
        failed: false,
    };

    let mut total_read = 0;
    let mut chunk = Vec::new();

    // make the initial chunks on disk
    for seq in unsorted {
        let seq_size = seq.estimate_size();
        total_read += seq_size;

        chunk.push(seq);

        if total_read >= settings.buffer_size {
            super::sort_by(&mut chunk, &settings);
            write_chunk(
                settings,
                &iter.tmp_dir.path().join(iter.chunks.to_string()),
                &mut chunk,
            );
            chunk.clear();
            total_read = 0;
            iter.chunks += 1;
        }
    }
    // write the last chunk
    if !chunk.is_empty() {
        super::sort_by(&mut chunk, &settings);
        write_chunk(
            settings,
            &iter.tmp_dir.path().join(iter.chunks.to_string()),
            &mut chunk,
        );
        iter.chunks += 1;
    }

    // initialize buffers for each chunk
    //
    // Having a right sized buffer for each chunk for smallish values seems silly to me?
    //
    // We will have to have the entire iter in memory sometime right?
    // Set minimum to the size of the writer buffer, ~8K

    const MINIMUM_READBACK_BUFFER: usize = 8200;
    let right_sized_buffer = settings
        .buffer_size
        .checked_div(iter.chunks)
        .unwrap_or(settings.buffer_size);
    iter.max_per_chunk = if right_sized_buffer > MINIMUM_READBACK_BUFFER {
        right_sized_buffer
    } else {
        MINIMUM_READBACK_BUFFER
    };
    iter.buffers = vec![VecDeque::new(); iter.chunks];
    iter.chunk_offsets = vec![0; iter.chunks];
    for chunk_num in 0..iter.chunks {
        let offset = fill_buff(
            &mut iter.buffers[chunk_num],
            crash_if_err!(
                1,
                File::open(iter.tmp_dir.path().join(chunk_num.to_string()))
            ),
            iter.max_per_chunk,
            &settings,
        );
        iter.chunk_offsets[chunk_num] = offset as u64;
    }

    iter
}

fn write_chunk(settings: &GlobalSettings, file: &Path, chunk: &mut Vec<Line>) {
    let new_file = crash_if_err!(1, OpenOptions::new().create(true).append(true).open(file));
    let mut buf_write = BufWriter::new(new_file);
    for s in chunk {
        crash_if_err!(1, buf_write.write_all(s.line.as_bytes()));
        crash_if_err!(
            1,
            buf_write.write_all(if settings.zero_terminated { "\0" } else { "\n" }.as_bytes(),)
        );
    }
    crash_if_err!(1, buf_write.flush());
}

fn fill_buff(
    vec: &mut VecDeque<Line>,
    file: File,
    max_bytes: usize,
    settings: &GlobalSettings,
) -> usize {
    let mut total_read = 0;
    let mut bytes_read = 0;
    for line in BufReader::new(file).split(if settings.zero_terminated {
        b'\0'
    } else {
        b'\n'
    }) {
        let line_s = String::from_utf8(crash_if_err!(1, line)).unwrap();
        bytes_read += line_s.len() + 1;
        let deserialized = Line::new(line_s, settings);
        total_read += deserialized.estimate_size();
        vec.push_back(deserialized);
        if total_read > max_bytes {
            break;
        }
    }

    bytes_read
}
<commit_msg>sort: improve memory usage for extsort<commit_after>use std::cmp::Ordering;
use std::collections::VecDeque;
use std::fs::{File, OpenOptions};
use std::io::SeekFrom;
use std::io::{BufRead, BufReader, BufWriter, Seek, Write};
use std::path::Path;

use tempdir::TempDir;

use super::{GlobalSettings, Line};

/// Iterator that provides sorted `T`s
pub struct ExtSortedIterator {
    buffers: Vec<VecDeque<Line>>,
    chunk_offsets: Vec<u64>,
    max_per_chunk: usize,
    chunks: usize,
    tmp_dir: TempDir,
    settings: GlobalSettings,
    failed: bool,
}

impl Iterator for ExtSortedIterator {
    type Item = Line;

    /// # Errors
    ///
    /// This method can fail due to issues reading intermediate sorted chunks
    /// from disk
    fn next(&mut self) -> Option<Self::Item> {
        if self.failed {
            return None;
        }
        // fill up any empty buffers
        let mut empty = true;
        for chunk_num in 0..self.chunks {
            if self.buffers[chunk_num as usize].is_empty() {
                let mut f = crash_if_err!(
                    1,
                    File::open(self.tmp_dir.path().join(chunk_num.to_string()))
                );
                crash_if_err!(1, f.seek(SeekFrom::Start(self.chunk_offsets[chunk_num])));
                let bytes_read = fill_buff(
                    &mut self.buffers[chunk_num as usize],
                    f,
                    self.max_per_chunk,
                    &self.settings,
                );
                self.chunk_offsets[chunk_num as usize] += bytes_read as u64;
                if !self.buffers[chunk_num as usize].is_empty() {
                    empty = false;
                }
            } else {
                empty = false;
            }
        }
        if empty {
            return None;
        }

        // find the next record to write
        // check is_empty() before unwrap()ing
        let mut idx = 0;
        for chunk_num in 0..self.chunks as usize {
            if !self.buffers[chunk_num].is_empty()
                && (self.buffers[idx].is_empty()
                    || super::compare_by(
                        self.buffers[chunk_num].front().unwrap(),
                        self.buffers[idx].front().unwrap(),
                        &self.settings,
                    ) == Ordering::Less)
            {
                idx = chunk_num;
            }
        }

        // unwrap due to checks above
        let r = self.buffers[idx].pop_front().unwrap();
        Some(r)
    }
}

/// Sort (based on `compare`) the `T`s provided by `unsorted` and return an
/// iterator
///
/// # Errors
///
/// This method can fail due to issues writing intermediate sorted chunks
/// to disk.
pub fn ext_sort(
    unsorted: impl Iterator<Item = Line>,
    settings: &GlobalSettings,
) -> ExtSortedIterator {
    let tmp_dir = crash_if_err!(1, TempDir::new_in(&settings.tmp_dir, "uutils_sort"));

    let mut iter = ExtSortedIterator {
        buffers: Vec::new(),
        chunk_offsets: Vec::new(),
        max_per_chunk: 0,
        chunks: 0,
        tmp_dir,
        settings: settings.clone(),
        failed: false,
    };

    let mut total_read = 0;
    let mut chunk = Vec::new();

    // make the initial chunks on disk
    for seq in unsorted {
        let seq_size = seq.estimate_size();
        total_read += seq_size;

        chunk.push(seq);

        if total_read + chunk.len() * std::mem::size_of::<Line>() >= settings.buffer_size {
            super::sort_by(&mut chunk, &settings);
            write_chunk(
                settings,
                &iter.tmp_dir.path().join(iter.chunks.to_string()),
                &mut chunk,
            );
            chunk.clear();
            total_read = 0;
            iter.chunks += 1;
        }
    }
    // write the last chunk
    if !chunk.is_empty() {
        super::sort_by(&mut chunk, &settings);
        write_chunk(
            settings,
            &iter.tmp_dir.path().join(iter.chunks.to_string()),
            &mut chunk,
        );
        iter.chunks += 1;
    }

    // We manually drop here to not go over our memory limit when we allocate below.
    drop(chunk);

    // initialize buffers for each chunk
    //
    // Having a right sized buffer for each chunk for smallish values seems silly to me?
    //
    // We will have to have the entire iter in memory sometime right?
    // Set minimum to the size of the writer buffer, ~8K

    const MINIMUM_READBACK_BUFFER: usize = 8200;
    let right_sized_buffer = settings
        .buffer_size
        .checked_div(iter.chunks)
        .unwrap_or(settings.buffer_size);
    iter.max_per_chunk = if right_sized_buffer > MINIMUM_READBACK_BUFFER {
        right_sized_buffer
    } else {
        MINIMUM_READBACK_BUFFER
    };
    iter.buffers = vec![VecDeque::new(); iter.chunks];
    iter.chunk_offsets = vec![0; iter.chunks];
    for chunk_num in 0..iter.chunks {
        let offset = fill_buff(
            &mut iter.buffers[chunk_num],
            crash_if_err!(
                1,
                File::open(iter.tmp_dir.path().join(chunk_num.to_string()))
            ),
            iter.max_per_chunk,
            &settings,
        );
        iter.chunk_offsets[chunk_num] = offset as u64;
    }

    iter
}

fn write_chunk(settings: &GlobalSettings, file: &Path, chunk: &mut Vec<Line>) {
    let new_file = crash_if_err!(1, OpenOptions::new().create(true).append(true).open(file));
    let mut buf_write = BufWriter::new(new_file);
    for s in chunk {
        crash_if_err!(1, buf_write.write_all(s.line.as_bytes()));
        crash_if_err!(
            1,
            buf_write.write_all(if settings.zero_terminated { "\0" } else { "\n" }.as_bytes(),)
        );
    }
    crash_if_err!(1, buf_write.flush());
}

fn fill_buff(
    vec: &mut VecDeque<Line>,
    file: File,
    max_bytes: usize,
    settings: &GlobalSettings,
) -> usize {
    let mut total_read = 0;
    let mut bytes_read = 0;
    for line in BufReader::new(file).split(if settings.zero_terminated {
        b'\0'
    } else {
        b'\n'
    }) {
        let line_s = String::from_utf8(crash_if_err!(1, line)).unwrap();
        bytes_read += line_s.len() + 1;
        let deserialized = Line::new(line_s, settings);
        total_read += deserialized.estimate_size();
        vec.push_back(deserialized);
        if total_read > max_bytes {
            break;
        }
    }

    bytes_read
}
<|endoftext|>


<commit_before>use std::collections::HashMap;

use serde;
use rustc_serialize::json::Json;
use chrono::{DateTime, UTC, Timelike};
use byteorder::{WriteBytesExt, BigEndian};

use token::Token;
use schema::FieldRef;

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct DocRef(u32, u16);

impl DocRef {
    pub fn segment(&self) -> u32 {
        self.0
    }

    pub fn ord(&self) -> u16 {
        self.1
    }

    pub fn as_u64(&self) -> u64 {
        (self.0 as u64) << 16 | (self.1 as u64)
    }

    pub fn from_segment_ord(segment: u32, ord: u16) -> DocRef {
        DocRef(segment, ord)
    }

    pub fn from_u64(val: u64) -> DocRef {
        let segment = (val >> 16) & 0xFFFFFFFF;
        let ord = val & 0xFFFF;
        DocRef(segment as u32, ord as u16)
    }
}

#[derive(Debug, Clone)]
pub enum FieldValue {
    String(String),
    Integer(i64),
    Boolean(bool),
    DateTime(DateTime<UTC>),
}

impl FieldValue {
    pub fn to_bytes(&self) -> Vec<u8> {
        match *self {
            FieldValue::String(ref string) => {
                let mut bytes = Vec::with_capacity(string.len());

                for byte in string.as_bytes() {
                    bytes.push(*byte);
                }

                bytes
            }
            FieldValue::Integer(value) => {
                let mut bytes = Vec::with_capacity(8);
                bytes.write_i64::<BigEndian>(value).unwrap();
                bytes
            }
            FieldValue::Boolean(value) => {
                if value {
                    vec![b't']
                } else {
                    vec![b'f']
                }
            }
            FieldValue::DateTime(value) => {
                let mut bytes = Vec::with_capacity(0);
                let timestamp = value.timestamp();
                let micros = value.nanosecond() / 1000;
                let timestamp_with_micros = timestamp * 1000000 + micros as i64;
                bytes.write_i64::<BigEndian>(timestamp_with_micros).unwrap();
                bytes
            }
        }
    }

    pub fn as_json(&self) -> Json {
        match *self {
            FieldValue::String(ref string) => Json::String(string.clone()),
            FieldValue::Boolean(value) => Json::Boolean(value),
            FieldValue::Integer(value) => Json::I64(value),
            FieldValue::DateTime(value) => Json::String(value.to_rfc3339()),
        }
    }
}

impl serde::Serialize for FieldValue {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where S: serde::Serializer
    {
        match *self {
            FieldValue::String(ref string) => serializer.serialize_str(string),
            FieldValue::Boolean(value) => serializer.serialize_bool(value),
            FieldValue::Integer(value) => serializer.serialize_i64(value),
            FieldValue::DateTime(value) => serializer.serialize_str(&value.to_rfc3339()),
        }
    }
}

#[derive(Debug, Clone)]
pub struct Document {
    pub key: String,
    pub indexed_fields: HashMap<FieldRef, Vec<Token>>,
    pub stored_fields: HashMap<FieldRef, FieldValue>,
}
<commit_msg>Removed FieldValue.as_json<commit_after>use std::collections::HashMap;

use serde;
use chrono::{DateTime, UTC, Timelike};
use byteorder::{WriteBytesExt, BigEndian};

use token::Token;
use schema::FieldRef;

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct DocRef(u32, u16);

impl DocRef {
    pub fn segment(&self) -> u32 {
        self.0
    }

    pub fn ord(&self) -> u16 {
        self.1
    }

    pub fn as_u64(&self) -> u64 {
        (self.0 as u64) << 16 | (self.1 as u64)
    }

    pub fn from_segment_ord(segment: u32, ord: u16) -> DocRef {
        DocRef(segment, ord)
    }

    pub fn from_u64(val: u64) -> DocRef {
        let segment = (val >> 16) & 0xFFFFFFFF;
        let ord = val & 0xFFFF;
        DocRef(segment as u32, ord as u16)
    }
}

#[derive(Debug, Clone)]
pub enum FieldValue {
    String(String),
    Integer(i64),
    Boolean(bool),
    DateTime(DateTime<UTC>),
}

impl FieldValue {
    pub fn to_bytes(&self) -> Vec<u8> {
        match *self {
            FieldValue::String(ref string) => {
                let mut bytes = Vec::with_capacity(string.len());

                for byte in string.as_bytes() {
                    bytes.push(*byte);
                }

                bytes
            }
            FieldValue::Integer(value) => {
                let mut bytes = Vec::with_capacity(8);
                bytes.write_i64::<BigEndian>(value).unwrap();
                bytes
            }
            FieldValue::Boolean(value) => {
                if value {
                    vec![b't']
                } else {
                    vec![b'f']
                }
            }
            FieldValue::DateTime(value) => {
                let mut bytes = Vec::with_capacity(0);
                let timestamp = value.timestamp();
                let micros = value.nanosecond() / 1000;
                let timestamp_with_micros = timestamp * 1000000 + micros as i64;
                bytes.write_i64::<BigEndian>(timestamp_with_micros).unwrap();
                bytes
            }
        }
    }
}

impl serde::Serialize for FieldValue {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where S: serde::Serializer
    {
        match *self {
            FieldValue::String(ref string) => serializer.serialize_str(string),
            FieldValue::Boolean(value) => serializer.serialize_bool(value),
            FieldValue::Integer(value) => serializer.serialize_i64(value),
            FieldValue::DateTime(value) => serializer.serialize_str(&value.to_rfc3339()),
        }
    }
}

#[derive(Debug, Clone)]
pub struct Document {
    pub key: String,
    pub indexed_fields: HashMap<FieldRef, Vec<Token>>,
    pub stored_fields: HashMap<FieldRef, FieldValue>,
}
<|endoftext|>


<commit_before>//
// base64n.rs - new base64 module
//
// The Base64 Alphabet
//
//  Value Encoding  Value Encoding  Value Encoding  Value Encoding
//      0 A            17 R            34 i            51 z
//      1 B            18 S            35 j            52 0
//      2 C            19 T            36 k            53 1
//      3 D            20 U            37 l            54 2
//      4 E            21 V            38 m            55 3
//      5 F            22 W            39 n            56 4
//      6 G            23 X            40 o            57 5
//      7 H            24 Y            41 p            58 6
//      8 I            25 Z            42 q            59 7
//      9 J            26 a            43 r            60 8
//     10 K            27 b            44 s            61 9
//     11 L            28 c            45 t            62 +
//     12 M            29 d            46 u            63 /
//     13 N            30 e            47 v
//     14 O            31 f            48 w         (pad) =
//     15 P            32 g            49 x
//     16 Q            33 h            50 y
//
// The "URL and Filename safe" Base 64 Alphabet
//
// Value Encoding  Value Encoding  Value Encoding  Value Encoding
//     0 A            17 R            34 i            51 z
//     1 B            18 S            35 j            52 0
//     2 C            19 T            36 k            53 1
//     3 D            20 U            37 l            54 2
//     4 E            21 V            38 m            55 3
//     5 F            22 W            39 n            56 4
//     6 G            23 X            40 o            57 5
//     7 H            24 Y            41 p            58 6
//     8 I            25 Z            42 q            59 7
//     9 J            26 a            43 r            60 8
//    10 K            27 b            44 s            61 9
//    11 L            28 c            45 t            62 - (minus)
//    12 M            29 d            46 u            63 _ (underline)
//    13 N            30 e            47 v
//    14 O            31 f            48 w
//    15 P            32 g            49 x
//    16 Q            33 h            50 y         (pad) =
//

use std;

import vec::len;

const PAD: u8 = 61u8;

iface enc {
    fn encode(dst: [mutable u8], src: [u8]);
    fn encode_u(dst: [mutable u8], src: [u8]);
    fn encode_bytes(src: [u8]) -> [u8];
    fn encode_bytes_u(src: [u8]) -> [u8];
    fn encode_str(src: str) -> str;
    fn encode_str_u(src: str) -> str;
    // FIXME `decode` and `decode_u` should return desired length of `dst`
    fn decode(dst: [mutable u8], src: [u8]);
    fn decode_u(dst: [mutable u8], src: [u8]);
    fn decode_bytes(src: [u8]) -> [u8];
}

fn mk() -> enc {
    type _enc = {table: [u8], table_u: [u8],
                 decode_map: [u8], decode_map_u: [u8]};

    impl of enc for _enc {
        fn encode(dst: [mutable u8], src: [u8]) {
            b64encode(self.table, dst, src);
        }
        fn encode_u(dst: [mutable u8], src: [u8]) {
            b64encode(self.table_u, dst, src);
        }
        fn encode_bytes(src: [u8]) -> [u8] {
            let dst_length = encoded_len(len(src));
            let dst = vec::to_mut(vec::from_elem(dst_length, 0u8));
            self.encode(dst, src);
            vec::from_mut(dst)
        }
        fn encode_bytes_u(src: [u8]) -> [u8] {
            let dst_length = encoded_len(len(src));
            let dst = vec::to_mut(vec::from_elem(dst_length, 0u8));
            self.encode_u(dst, src);
            vec::from_mut(dst)
        }
        fn encode_str(src: str) -> str {
            let src = str::bytes(src);
            str::from_bytes(self.encode_bytes(src))
        }
        fn encode_str_u(src: str) -> str {
            let src = str::bytes(src);
            str::from_bytes(self.encode_bytes_u(src))
        }
        fn decode(dst: [mutable u8], src: [u8]) {
            // FIXME need self.decode_map instead of self.table.
            b64decode(self.decode_map, dst, src);
        }
        fn decode_bytes(src: [u8]) -> [u8] { [] }
    }

    let table = vec::to_mut(vec::from_elem(64u, 0u8)), i = 0u8;
    u8::range(65u8, 91u8)  { |j| table[i] = j; i += 1u8; }
    u8::range(97u8, 123u8) { |j| table[i] = j; i += 1u8; }
    u8::range(48u8, 58u8)  { |j| table[i] = j; i += 1u8; }
    table[i] = 43u8; table[i + 1u8] = 47u8;

    let table_u = table;
    table_u[i] = 45u8; table_u[i + 1u8] = 95u8;

    let decode_map = vec::to_mut(vec::from_elem(256u, 0xff_u8));
    let decode_map_u = vec::to_mut(vec::from_elem(256u, 0xff_u8));

    i = 0u8;
    while i < 64u8 {
        decode_map[table[i]] = i as u8;
        i += 1u8;
    }

    i = 0u8;
    while i < 64u8 {
        decode_map_u[table_u[i]] = i as u8;
        i += 1u8;
    }

    {table: vec::from_mut(table),
     table_u: vec::from_mut(table_u),
     decode_map: vec::from_mut(decode_map),
     decode_map_u: vec::from_mut(decode_map_u)} as enc
}

fn encoded_len(src_length: uint) -> uint {
    (src_length + 2u) / 3u * 4u
}

fn decoded_len(src_length: uint) -> uint {
    src_length / 4u * 3u
}

fn b64encode(table: [u8], dst: [mutable u8], src: [u8]) {
    if len(src) == 0u {
        ret;
    }

    let src_length = len(src);
    let dst_length = len(dst);
    let dst_curr = 0u;
    let src_curr = 0u;

    if dst_length % 4u != 0u {
        fail "dst's length should be divisible by 4";
    }

    while src_length > 0u {
        dst[dst_curr + 0u] = 0u8;
        dst[dst_curr + 1u] = 0u8;
        dst[dst_curr + 2u] = 0u8;
        dst[dst_curr + 3u] = 0u8;

        if src_length == 1u {
            dst[dst_curr + 0u] |= (src[src_curr + 0u]) >> 2u8;
            dst[dst_curr + 1u] |= (src[src_curr + 0u] << 4u8) & 0x3f_u8;
        } else if src_length == 2u {
            dst[dst_curr + 0u] |= (src[src_curr + 0u]) >> 2u8;
            dst[dst_curr + 1u] |= (src[src_curr + 0u] << 4u8) & 0x3f_u8;
            dst[dst_curr + 1u] |= (src[src_curr + 1u] >> 4u8);
            dst[dst_curr + 2u] |= (src[src_curr + 1u] << 2u8) & 0x3f_u8;
        } else {
            dst[dst_curr + 0u] |= (src[src_curr + 0u]) >> 2u8;
            dst[dst_curr + 1u] |= (src[src_curr + 0u] << 4u8) & 0x3f_u8;
            dst[dst_curr + 1u] |= (src[src_curr + 1u] >> 4u8);
            dst[dst_curr + 2u] |= (src[src_curr + 1u] << 2u8) & 0x3f_u8;
            dst[dst_curr + 2u] |= (src[src_curr + 2u] >> 6u8);
            dst[dst_curr + 3u] |= (src[src_curr + 2u]) & 0x3f_u8;
        }

        dst[dst_curr + 0u] = table[dst[dst_curr + 0u]];
        dst[dst_curr + 1u] = table[dst[dst_curr + 1u]];
        dst[dst_curr + 2u] = table[dst[dst_curr + 2u]];
        dst[dst_curr + 3u] = table[dst[dst_curr + 3u]];

        if src_length < 3u {
            dst[dst_curr + 3u] = PAD;
            if src_length < 2u {
                dst[dst_curr + 2u] = PAD;
            }
            break;
        }

        src_length -= 3u;
        src_curr += 3u;
        dst_curr += 4u;
    }
}

fn b64decode(table: [u8], dst: [mutable u8], src: [u8]) {
    // FIXME write
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_encode_bytes() {
        let src = [102u8, 111u8, 111u8, 98u8, 97u8, 114u8];
        let enc = mk();
        let res = enc.encode_bytes(src);
        assert res == [90u8, 109u8, 57u8, 118u8, 89u8, 109u8, 70u8, 121u8];
    }
    #[test]
    fn test_encode_str() {
        let src = "foobar";
        let enc = mk();
        let res = enc.encode_str(src);
        assert res == "Zm9vYmFy";
    }
}
<commit_msg>wip: base64n.rs<commit_after>//
// base64n.rs - new base64 module
//
// The Base64 Alphabet
//
//  Value Encoding  Value Encoding  Value Encoding  Value Encoding
//      0 A            17 R            34 i            51 z
//      1 B            18 S            35 j            52 0
//      2 C            19 T            36 k            53 1
//      3 D            20 U            37 l            54 2
//      4 E            21 V            38 m            55 3
//      5 F            22 W            39 n            56 4
//      6 G            23 X            40 o            57 5
//      7 H            24 Y            41 p            58 6
//      8 I            25 Z            42 q            59 7
//      9 J            26 a            43 r            60 8
//     10 K            27 b            44 s            61 9
//     11 L            28 c            45 t            62 +
//     12 M            29 d            46 u            63 /
//     13 N            30 e            47 v
//     14 O            31 f            48 w         (pad) =
//     15 P            32 g            49 x
//     16 Q            33 h            50 y
//
// The "URL and Filename safe" Base 64 Alphabet
//
// Value Encoding  Value Encoding  Value Encoding  Value Encoding
//     0 A            17 R            34 i            51 z
//     1 B            18 S            35 j            52 0
//     2 C            19 T            36 k            53 1
//     3 D            20 U            37 l            54 2
//     4 E            21 V            38 m            55 3
//     5 F            22 W            39 n            56 4
//     6 G            23 X            40 o            57 5
//     7 H            24 Y            41 p            58 6
//     8 I            25 Z            42 q            59 7
//     9 J            26 a            43 r            60 8
//    10 K            27 b            44 s            61 9
//    11 L            28 c            45 t            62 - (minus)
//    12 M            29 d            46 u            63 _ (underline)
//    13 N            30 e            47 v
//    14 O            31 f            48 w
//    15 P            32 g            49 x
//    16 Q            33 h            50 y         (pad) =
//

use std;

import vec::len;

const PAD: u8 = 61u8;

iface enc {
    fn encode(dst: [mutable u8], src: [u8]);
    fn encode_u(dst: [mutable u8], src: [u8]);
    fn encode_bytes(src: [u8]) -> [u8];
    fn encode_bytes_u(src: [u8]) -> [u8];
    fn encode_str(src: str) -> str;
    fn encode_str_u(src: str) -> str;
    // FIXME `decode` and `decode_u` should return desired length of `dst`
    fn decode(dst: [mutable u8], src: [u8]);
    fn decode_u(dst: [mutable u8], src: [u8]);
    fn decode_bytes(src: [u8]) -> [u8];
    fn decode_bytes_u(src: [u8]) -> [u8];
}

fn mk() -> enc {
    type _enc = {table: [u8], table_u: [u8],
                 decode_map: [u8], decode_map_u: [u8]};

    impl of enc for _enc {
        fn encode(dst: [mutable u8], src: [u8]) {
            b64encode(self.table, dst, src);
        }
        fn encode_u(dst: [mutable u8], src: [u8]) {
            b64encode(self.table_u, dst, src);
        }
        fn encode_bytes(src: [u8]) -> [u8] {
            let dst_length = encoded_len(len(src));
            let dst = vec::to_mut(vec::from_elem(dst_length, 0u8));
            self.encode(dst, src);
            vec::from_mut(dst)
        }
        fn encode_bytes_u(src: [u8]) -> [u8] {
            let dst_length = encoded_len(len(src));
            let dst = vec::to_mut(vec::from_elem(dst_length, 0u8));
            self.encode_u(dst, src);
            vec::from_mut(dst)
        }
        fn encode_str(src: str) -> str {
            let src = str::bytes(src);
            str::from_bytes(self.encode_bytes(src))
        }
        fn encode_str_u(src: str) -> str {
            let src = str::bytes(src);
            str::from_bytes(self.encode_bytes_u(src))
        }
        fn decode(dst: [mutable u8], src: [u8]) {
            b64decode(self.decode_map, dst, src);
        }
        fn decode_u(dst: [mutable u8], src: [u8]) {
            b64decode(self.decode_map_u, dst, src);
        }
        fn decode_bytes(src: [u8]) -> [u8] { [] }
        fn decode_bytes_u(src: [u8]) -> [u8] { [] }
    }

    let table = vec::to_mut(vec::from_elem(64u, 0u8)), i = 0u8;
    u8::range(65u8, 91u8)  { |j| table[i] = j; i += 1u8; }
    u8::range(97u8, 123u8) { |j| table[i] = j; i += 1u8; }
    u8::range(48u8, 58u8)  { |j| table[i] = j; i += 1u8; }
    table[i] = 43u8; table[i + 1u8] = 47u8;

    let table_u = table;
    table_u[i] = 45u8; table_u[i + 1u8] = 95u8;

    let decode_map = vec::to_mut(vec::from_elem(256u, 0xff_u8));
    let decode_map_u = vec::to_mut(vec::from_elem(256u, 0xff_u8));

    i = 0u8;
    while i < 64u8 {
        decode_map[table[i]] = i as u8;
        i += 1u8;
    }

    i = 0u8;
    while i < 64u8 {
        decode_map_u[table_u[i]] = i as u8;
        i += 1u8;
    }

    {table: vec::from_mut(table),
     table_u: vec::from_mut(table_u),
     decode_map: vec::from_mut(decode_map),
     decode_map_u: vec::from_mut(decode_map_u)} as enc
}

fn encoded_len(src_length: uint) -> uint {
    (src_length + 2u) / 3u * 4u
}

fn decoded_len(src_length: uint) -> uint {
    src_length / 4u * 3u
}

fn b64encode(table: [u8], dst: [mutable u8], src: [u8]) {
    if len(src) == 0u {
        ret;
    }

    let src_length = len(src);
    let dst_length = len(dst);
    let dst_curr = 0u;
    let src_curr = 0u;

    if dst_length % 4u != 0u {
        fail "dst's length should be divisible by 4";
    }

    while src_length > 0u {
        dst[dst_curr + 0u] = 0u8;
        dst[dst_curr + 1u] = 0u8;
        dst[dst_curr + 2u] = 0u8;
        dst[dst_curr + 3u] = 0u8;

        if src_length == 1u {
            dst[dst_curr + 0u] |= (src[src_curr + 0u]) >> 2u8;
            dst[dst_curr + 1u] |= (src[src_curr + 0u] << 4u8) & 0x3f_u8;
        } else if src_length == 2u {
            dst[dst_curr + 0u] |= (src[src_curr + 0u]) >> 2u8;
            dst[dst_curr + 1u] |= (src[src_curr + 0u] << 4u8) & 0x3f_u8;
            dst[dst_curr + 1u] |= (src[src_curr + 1u] >> 4u8);
            dst[dst_curr + 2u] |= (src[src_curr + 1u] << 2u8) & 0x3f_u8;
        } else {
            dst[dst_curr + 0u] |= (src[src_curr + 0u]) >> 2u8;
            dst[dst_curr + 1u] |= (src[src_curr + 0u] << 4u8) & 0x3f_u8;
            dst[dst_curr + 1u] |= (src[src_curr + 1u] >> 4u8);
            dst[dst_curr + 2u] |= (src[src_curr + 1u] << 2u8) & 0x3f_u8;
            dst[dst_curr + 2u] |= (src[src_curr + 2u] >> 6u8);
            dst[dst_curr + 3u] |= (src[src_curr + 2u]) & 0x3f_u8;
        }

        dst[dst_curr + 0u] = table[dst[dst_curr + 0u]];
        dst[dst_curr + 1u] = table[dst[dst_curr + 1u]];
        dst[dst_curr + 2u] = table[dst[dst_curr + 2u]];
        dst[dst_curr + 3u] = table[dst[dst_curr + 3u]];

        if src_length < 3u {
            dst[dst_curr + 3u] = PAD;
            if src_length < 2u {
                dst[dst_curr + 2u] = PAD;
            }
            break;
        }

        src_length -= 3u;
        src_curr += 3u;
        dst_curr += 4u;
    }
}

fn b64decode(table: [u8], dst: [mutable u8], src: [u8]) {
    // FIXME write
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_encode_bytes() {
        let src = [102u8, 111u8, 111u8, 98u8, 97u8, 114u8];
        let enc = mk();
        let res = enc.encode_bytes(src);
        assert res == [90u8, 109u8, 57u8, 118u8, 89u8, 109u8, 70u8, 121u8];
    }
    #[test]
    fn test_encode_str() {
        let src = "foobar";
        let enc = mk();
        let res = enc.encode_str(src);
        assert res == "Zm9vYmFy";
    }
}
<|endoftext|>


<commit_before>//
// Mnemosyne: a functional systems programming language.
// (c) 2015 Hawk Weisman
//
// Mnemosyne is released under the MIT License. Please refer to
// the LICENSE file at the top-level directory of this distribution
// or at https://github.com/hawkw/mnemosyne/.
//

use std::iter;
use std::fmt::Write;
use std::rc::Rc;

use ::forktable::ForkTable;

use ast::*;
use self::annotations::*;
use self::types::Type;

/// A symbol table is a `ForkTable` mapping `String`s to `Type` annotations.
///
/// This table should be forked upon entering a new scope.
pub type SymbolTable<'a> = ForkTable<'a, String, SymbolAnnotation<'a>>;

#[macro_use]
macro_rules! indent {
    ($to:expr) => ( iter::repeat('\t')
                        .take($to)
                        .collect::<String>() )
}

pub mod ast;
pub mod types;
pub mod annotations;

impl<'a> AnnotateTypes<'a> for Unscoped<'a, Form<'a, UnscopedState>> {
    #[allow(unused_variables)]
    fn annotate_types(self, scope: SymbolTable) -> Scoped<'a, Self> {
        unimplemented!() //TODO: implement
    }
}

impl<'a> AnnotateTypes<'a> for Unscoped<'a, DefForm<'a, UnscopedState>> {
    #[allow(unused_variables)]
    fn annotate_types(self, scope: SymbolTable) -> Scoped<'a, Self>{
        unimplemented!() //TODO: implement
    }
}

#[derive(Clone,Debug,PartialEq)]
pub enum SymbolAnnotation<'a> {
    TypeDef(Type)
  , Value { /// The type of the symbol
            ty: Type,
            /// An optional proven value for the symbol.
            ///
            /// This should be defined iff the symbol signifies a constant value
            /// or a constant expression, or if we were able to prove that the value
            /// remains constant within the current scope.
            proven_value: Option<Rc<Expr<'a, ScopedState>>>
          }
}
<commit_msg>Documentation<commit_after>//
// Mnemosyne: a functional systems programming language.
// (c) 2015 Hawk Weisman
//
// Mnemosyne is released under the MIT License. Please refer to
// the LICENSE file at the top-level directory of this distribution
// or at https://github.com/hawkw/mnemosyne/.
//

use std::iter;
use std::fmt::Write;
use std::rc::Rc;

use ::forktable::ForkTable;

use ast::*;
use self::annotations::*;
use self::types::Type;

/// A symbol table is a `ForkTable` mapping `String`s to `Type` annotations.
///
/// As of `84f1de8`, symbol annotations can be type annotations for values
/// or annotations for type definitions. This is provisional; type defs
/// may be moved to a separate  table later.
///
/// This table should be forked upon entering a new scope.
pub type SymbolTable<'a> = ForkTable<'a, String, SymbolAnnotation<'a>>;

#[macro_use]
macro_rules! indent {
    ($to:expr) => ( iter::repeat('\t')
                        .take($to)
                        .collect::<String>() )
}

pub mod ast;
pub mod types;
pub mod annotations;

impl<'a> AnnotateTypes<'a> for Unscoped<'a, Form<'a, UnscopedState>> {
    #[allow(unused_variables)]
    fn annotate_types(self, scope: SymbolTable) -> Scoped<'a, Self> {
        unimplemented!() //TODO: implement
    }
}

impl<'a> AnnotateTypes<'a> for Unscoped<'a, DefForm<'a, UnscopedState>> {
    #[allow(unused_variables)]
    fn annotate_types(self, scope: SymbolTable) -> Scoped<'a, Self>{
        unimplemented!() //TODO: implement
    }
}

#[derive(Clone,Debug,PartialEq)]
pub enum SymbolAnnotation<'a> {
    TypeDef(Type)
  , Value { /// The type of the symbol
            ty: Type,
            /// An optional proven value for the symbol.
            ///
            /// This should be defined iff the symbol signifies a constant value
            /// or a constant expression, or if we were able to prove that the value
            /// remains constant within the current scope.
            proven_value: Option<Rc<Expr<'a, ScopedState>>>
          }
}
<|endoftext|>


<commit_before>use std::path::PathBuf;
use std::result::Result as RResult;

use toml::{Parser, Value};

/**
 * Errors which are related to configuration-file loading
 */
pub mod error {
    use std::error::Error;
    use std::fmt::{Display, Formatter};
    use std::fmt::Error as FmtError;

    /**
     * The kind of an error
     */
    #[derive(Clone, Debug, PartialEq)]
    pub enum ConfigErrorKind {
        ConfigParsingFailed,
        NoConfigFileFound,
    }

    /**
     * Configuration error type
     */
    #[derive(Debug)]
    pub struct ConfigError {
        kind: ConfigErrorKind,
        cause: Option<Box<Error>>,
    }

    impl ConfigError {

        /**
         * Instantiate a new ConfigError, optionally with cause
         */
        pub fn new(kind: ConfigErrorKind, cause: Option<Box<Error>>) -> ConfigError {
            ConfigError {
                kind: kind,
                cause: cause,
            }
        }

        /**
         * get the Kind of the Error
         */
        pub fn kind(&self) -> ConfigErrorKind {
            self.kind.clone()
        }

        /**
         * Get the string, the ConfigError can be described with
         */
        pub fn as_str(e: &ConfigError) -> &'static str {
            match e.kind() {
                ConfigErrorKind::ConfigParsingFailed => "Config parsing failed",
                ConfigErrorKind::NoConfigFileFound   => "No config file found",
            }
        }

    }

    impl Display for ConfigError {

        fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
            try!(write!(fmt, "{}", ConfigError::as_str(self)));
            Ok(())
        }

    }

    impl Error for ConfigError {

        fn description(&self) -> &str {
            ConfigError::as_str(self)
        }

        fn cause(&self) -> Option<&Error> {
            self.cause.as_ref().map(|e| &**e)
        }

    }

}

use self::error::{ConfigError, ConfigErrorKind};

/**
 * Result type of this module. Either T or ConfigError
 */
pub type Result<T> = RResult<T, ConfigError>;

/**
 * Configuration object
 *
 * Holds all config variables which are globally available plus the configuration object from the
 * config parser, which can be accessed.
 */
#[derive(Debug)]
pub struct Configuration {

    /**
     * The plain configuration object for direct access if necessary
     */
    config: Value,

    /**
     * The verbosity the program should run with
     */
    verbosity: bool,

    /**
     * The editor which should be used
     */
    editor: Option<String>,

    /**
     * The options the editor should get when opening some file
     */
    editor_opts: String,
}

impl Configuration {

    /**
     * Get a new configuration object.
     *
     * The passed runtimepath is used for searching the configuration file, whereas several file
     * names are tested. If that does not work, the home directory and the XDG basedir are tested
     * with all variants.
     *
     * If that doesn't work either, an error is returned.
     */
    pub fn new(rtp: &PathBuf) -> Result<Configuration> {
        fetch_config(&rtp).map(|cfg| {
            let verbosity   = get_verbosity(&cfg);
            let editor      = get_editor(&cfg);
            let editor_opts = get_editor_opts(&cfg);

            debug!("Building configuration");
            debug!("  - verbosity  : {:?}", verbosity);
            debug!("  - editor     : {:?}", editor);
            debug!("  - editor-opts: {}", editor_opts);

            Configuration {
                config: cfg,
                verbosity: verbosity,
                editor: editor,
                editor_opts: editor_opts,
            }
        })
    }

    pub fn editor(&self) -> Option<&String> {
        self.editor.as_ref()
    }

    pub fn config(&self) -> &Value {
        &self.config
    }

    pub fn store_config(&self) -> Option<&Value> {
        match &self.config {
            &Value::Table(ref tabl) => tabl.get("store"),
            _ => None,
        }
    }

}

fn get_verbosity(v: &Value) -> bool {
    match v {
        &Value::Table(ref t) => t.get("verbose")
                .map(|v| match v { &Value::Boolean(b) => b, _ => false, })
                .unwrap_or(false),
        _ => false,
    }
}

fn get_editor(v: &Value) -> Option<String> {
    match v {
        &Value::Table(ref t) => t.get("editor")
                .and_then(|v| match v { &Value::String(ref s) => Some(s.clone()), _ => None, }),
        _ => None,
    }
}

fn get_editor_opts(v: &Value) -> String {
    match v {
        &Value::Table(ref t) => t.get("editor-opts")
                .and_then(|v| match v { &Value::String(ref s) => Some(s.clone()), _ => None, })
                .unwrap_or(String::new()),
        _ => String::new(),
    }
}

/**
 * Helper to fetch the config file
 *
 * Tests several variants for the config file path and uses the first one which works.
 */
fn fetch_config(rtp: &PathBuf) -> Result<Value> {
    use std::env;
    use std::fs::File;
    use std::io::Read;

    use xdg_basedir;
    use itertools::Itertools;

    use libimagutil::variants::generate_variants as gen_vars;

    let variants = vec!["config", "config.toml", "imagrc", "imagrc.toml"];
    let modifier = |base: &PathBuf, v: &'static str| {
        let mut base = base.clone();
        base.push(format!("/{}", v));
        base
    };

    vec![
        gen_vars(rtp.clone(), variants.clone(), &modifier),

        env::var("HOME").map(|home| gen_vars(PathBuf::from(home), variants.clone(), &modifier))
                        .unwrap_or(vec![]),

        xdg_basedir::get_data_home().map(|data_dir| gen_vars(data_dir, variants.clone(), &modifier))
                                    .unwrap_or(vec![]),
    ].iter()
        .flatten()
        .filter(|path| path.exists())
        .map(|path| {
            let content = {
                let mut s = String::new();
                let mut f = File::open(path);
                if f.is_err() {
                }
                let mut f = f.unwrap();
                f.read_to_string(&mut s);
                s
            };
            Parser::new(&content[..]).parse()
        })
        .filter(|loaded| loaded.is_some())
        .nth(0)
        .map(|inner| Value::Table(inner.unwrap()))
        .ok_or(ConfigError::new(ConfigErrorKind::NoConfigFileFound, None))
}

<commit_msg>Remove unneeded keyword "mut"<commit_after>use std::path::PathBuf;
use std::result::Result as RResult;

use toml::{Parser, Value};

/**
 * Errors which are related to configuration-file loading
 */
pub mod error {
    use std::error::Error;
    use std::fmt::{Display, Formatter};
    use std::fmt::Error as FmtError;

    /**
     * The kind of an error
     */
    #[derive(Clone, Debug, PartialEq)]
    pub enum ConfigErrorKind {
        ConfigParsingFailed,
        NoConfigFileFound,
    }

    /**
     * Configuration error type
     */
    #[derive(Debug)]
    pub struct ConfigError {
        kind: ConfigErrorKind,
        cause: Option<Box<Error>>,
    }

    impl ConfigError {

        /**
         * Instantiate a new ConfigError, optionally with cause
         */
        pub fn new(kind: ConfigErrorKind, cause: Option<Box<Error>>) -> ConfigError {
            ConfigError {
                kind: kind,
                cause: cause,
            }
        }

        /**
         * get the Kind of the Error
         */
        pub fn kind(&self) -> ConfigErrorKind {
            self.kind.clone()
        }

        /**
         * Get the string, the ConfigError can be described with
         */
        pub fn as_str(e: &ConfigError) -> &'static str {
            match e.kind() {
                ConfigErrorKind::ConfigParsingFailed => "Config parsing failed",
                ConfigErrorKind::NoConfigFileFound   => "No config file found",
            }
        }

    }

    impl Display for ConfigError {

        fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
            try!(write!(fmt, "{}", ConfigError::as_str(self)));
            Ok(())
        }

    }

    impl Error for ConfigError {

        fn description(&self) -> &str {
            ConfigError::as_str(self)
        }

        fn cause(&self) -> Option<&Error> {
            self.cause.as_ref().map(|e| &**e)
        }

    }

}

use self::error::{ConfigError, ConfigErrorKind};

/**
 * Result type of this module. Either T or ConfigError
 */
pub type Result<T> = RResult<T, ConfigError>;

/**
 * Configuration object
 *
 * Holds all config variables which are globally available plus the configuration object from the
 * config parser, which can be accessed.
 */
#[derive(Debug)]
pub struct Configuration {

    /**
     * The plain configuration object for direct access if necessary
     */
    config: Value,

    /**
     * The verbosity the program should run with
     */
    verbosity: bool,

    /**
     * The editor which should be used
     */
    editor: Option<String>,

    /**
     * The options the editor should get when opening some file
     */
    editor_opts: String,
}

impl Configuration {

    /**
     * Get a new configuration object.
     *
     * The passed runtimepath is used for searching the configuration file, whereas several file
     * names are tested. If that does not work, the home directory and the XDG basedir are tested
     * with all variants.
     *
     * If that doesn't work either, an error is returned.
     */
    pub fn new(rtp: &PathBuf) -> Result<Configuration> {
        fetch_config(&rtp).map(|cfg| {
            let verbosity   = get_verbosity(&cfg);
            let editor      = get_editor(&cfg);
            let editor_opts = get_editor_opts(&cfg);

            debug!("Building configuration");
            debug!("  - verbosity  : {:?}", verbosity);
            debug!("  - editor     : {:?}", editor);
            debug!("  - editor-opts: {}", editor_opts);

            Configuration {
                config: cfg,
                verbosity: verbosity,
                editor: editor,
                editor_opts: editor_opts,
            }
        })
    }

    pub fn editor(&self) -> Option<&String> {
        self.editor.as_ref()
    }

    pub fn config(&self) -> &Value {
        &self.config
    }

    pub fn store_config(&self) -> Option<&Value> {
        match &self.config {
            &Value::Table(ref tabl) => tabl.get("store"),
            _ => None,
        }
    }

}

fn get_verbosity(v: &Value) -> bool {
    match v {
        &Value::Table(ref t) => t.get("verbose")
                .map(|v| match v { &Value::Boolean(b) => b, _ => false, })
                .unwrap_or(false),
        _ => false,
    }
}

fn get_editor(v: &Value) -> Option<String> {
    match v {
        &Value::Table(ref t) => t.get("editor")
                .and_then(|v| match v { &Value::String(ref s) => Some(s.clone()), _ => None, }),
        _ => None,
    }
}

fn get_editor_opts(v: &Value) -> String {
    match v {
        &Value::Table(ref t) => t.get("editor-opts")
                .and_then(|v| match v { &Value::String(ref s) => Some(s.clone()), _ => None, })
                .unwrap_or(String::new()),
        _ => String::new(),
    }
}

/**
 * Helper to fetch the config file
 *
 * Tests several variants for the config file path and uses the first one which works.
 */
fn fetch_config(rtp: &PathBuf) -> Result<Value> {
    use std::env;
    use std::fs::File;
    use std::io::Read;

    use xdg_basedir;
    use itertools::Itertools;

    use libimagutil::variants::generate_variants as gen_vars;

    let variants = vec!["config", "config.toml", "imagrc", "imagrc.toml"];
    let modifier = |base: &PathBuf, v: &'static str| {
        let mut base = base.clone();
        base.push(format!("/{}", v));
        base
    };

    vec![
        gen_vars(rtp.clone(), variants.clone(), &modifier),

        env::var("HOME").map(|home| gen_vars(PathBuf::from(home), variants.clone(), &modifier))
                        .unwrap_or(vec![]),

        xdg_basedir::get_data_home().map(|data_dir| gen_vars(data_dir, variants.clone(), &modifier))
                                    .unwrap_or(vec![]),
    ].iter()
        .flatten()
        .filter(|path| path.exists())
        .map(|path| {
            let content = {
                let mut s = String::new();
                let f = File::open(path);
                if f.is_err() {
                }
                let mut f = f.unwrap();
                f.read_to_string(&mut s);
                s
            };
            Parser::new(&content[..]).parse()
        })
        .filter(|loaded| loaded.is_some())
        .nth(0)
        .map(|inner| Value::Table(inner.unwrap()))
        .ok_or(ConfigError::new(ConfigErrorKind::NoConfigFileFound, None))
}

<|endoftext|>