<commit_before>//! Types around a convolution, see also https://en.wikipedia.org/wiki/Convolution.
//!
//! Convolutions in this library can be defined in time or frequency domain. In
//! frequency domain the convolution is automatically transformed into a multiplication
//! which is the analog operation to a convolution in time domain.
use RealNumber;

/// The domain of a convolution.
/// Convolutions in this library can be defined in time or frequency domain. In
/// frequency domain the convolution is automatically transformed into a multiplication
/// which is the analog operation to a convolution in time domain.
#[derive(Copy)]
#[derive(Clone)]
#[derive(PartialEq)]
#[derive(Debug)]
pub enum ConvDomain {
	/// Time domain, the x-axis is in [s]
	Time,
	/// Frequency domain, the x-axis in in [Hz]
    Frequency
}

/// Meta information about the convolution.
pub trait ConvInfo {
    fn domain(&self) -> ConvDomain;
}

/// A convolution function
pub trait ConvFunction<T> : ConvInfo
    where T: RealNumber {
    fn calc(&self, x: T) -> T;
}

/// Allows to create a lookup table with linear interpolation between table points.
/// This usually speeds up a convolution and sacrifies accuracy.
pub struct LinearTableLookup<T>
    where T: RealNumber {
    table: Vec<T>,
    domain: ConvDomain,
    delta: T,
    offset: T
}

macro_rules! add_linear_table_lookup_impl {
    ($($data_type:ident),*) => {
        $(
            impl ConvInfo for LinearTableLookup<$data_type> {
                fn domain(&self) -> ConvDomain {
                    self.domain
                }
            }

            impl ConvFunction<$data_type> for LinearTableLookup<$data_type> {
                fn calc(&self, x: $data_type) -> $data_type {
                    let len = self.table.len();
                    let center = len / 2;
                    let center_float = center as $data_type;
                    let x = x / self.delta - self.offset + center_float;
                    let round_float = x.round();
                    let round = round_float as usize;
                    if round >= len {
                        return 0.0;
                    }

                    let round_tolerance = 1e-6;
                    if (x - round_float).abs() < round_tolerance {
                        return self.table[round];
                    }

                    if x > round_float {
                        let other = round + 1;
                        if other >= len {
                            return self.table[round];
                        }
                        let y0 = self.table[round];
                        let x0 = round_float;
                        let y1 = self.table[other];
                        return y0 + (y1 - y0) * (x - x0);
                    } else {
                        if round == 0 {
                            return self.table[round];
                        }
                        let other = round - 1;
                        let y0 = self.table[round];
                        let x0 = round_float;
                        let y1 = self.table[other];
                        return y0 + (y1 - y0) * (x0 - x);
                    }
                }
            }

            impl LinearTableLookup<$data_type> {
                pub fn from_raw_parts(table: &[$data_type], domain: ConvDomain, delta: $data_type, offset: $data_type) -> Self {
                    let owned_table = Vec::from(table);
                    LinearTableLookup { table: owned_table, domain: domain, delta: delta, offset: offset }
                }

                pub fn from_conv_function(other: &ConvFunction<$data_type>, delta: $data_type, offset: $data_type, len: usize) -> Self {
                    let center = (len / 2) as isize;
                    let mut table = vec![0.0; len];
                    let mut i = -center;
                    for n in &mut table {
                        *n = other.calc((i as $data_type) * delta);
                        i += 1;
                    }
                    LinearTableLookup { table: table, domain: other.domain(), delta: delta, offset: offset }
                }
            }
        )*
    }
}
add_linear_table_lookup_impl!(f32, f64);

/// Raised cosine function according to https://en.wikipedia.org/wiki/Raised-cosine_filter
pub struct  RaiseCosineFuncton<T>
    where T: RealNumber {
    rolloff: T
}

impl<T> ConvInfo for RaiseCosineFuncton<T>
    where T: RealNumber {
    fn domain(&self) -> ConvDomain {
        ConvDomain::Time
    }
}

impl<T> ConvFunction<T> for RaiseCosineFuncton<T>
    where T: RealNumber {
    fn calc(&self, x: T) -> T {
        if x == T::zero() {
            return T::one();
        }

        let one = T::one();
        let two = T::from(2.0).unwrap();
        let pi = two * one.asin();
        let four = two * two;
        if x.abs() == one / (two * self.rolloff) {
            let arg = pi / two / self.rolloff;
            return (arg).sin() / arg * pi / four;
        }

        let pi_x = pi * x;
        let arg = two * self.rolloff * x;
        return pi_x.sin() * (pi_x * self.rolloff).cos() / pi_x / (one - (arg * arg))
    }
}

impl<T> RaiseCosineFuncton<T>
    where T: RealNumber {
    pub fn new(rolloff: T) -> Self {
        RaiseCosineFuncton { rolloff: rolloff }
    }
}

#[cfg(test)]
mod tests {
	use super::*;
    use super::super::RealNumber;
    use std::fmt::Debug;

    fn conv_test<T, C>(conv: C, expected: &[T], step: T, tolerance: T)
        where T: RealNumber + Debug,
              C: ConvFunction<T> {
        let mut result = vec![T::zero(); expected.len()];
        let mut j = -(expected.len() as isize / 2);
        for i in 0..result.len() {
            result[i] = conv.calc(T::from(j).unwrap() * step);
            j += 1;
        }

        for i in 0..result.len() {
            if (result[i] - expected[i]).abs() > tolerance {
                panic!("assertion failed: {:?} != {:?}", result, expected);
            }
        }
    }

	#[test]
	fn raised_cosine32_test()
	{
        let window = RaiseCosineFuncton::new(0.35);
        let expected =
            [0.0, 0.2171850639713355, 0.4840621929215732, 0.7430526238101408, 0.9312114164253432,
             1.0, 0.9312114164253432, 0.7430526238101408, 0.4840621929215732, 0.2171850639713355];
        conv_test(window, &expected, 0.2, 1e-4);
	}

    #[test]
    fn lookup_table32_test() {
        let window = RaiseCosineFuncton::new(0.35);
        let table = LinearTableLookup::<f64>::from_conv_function(&window, 0.2, 0.0, 10);
        let expected =
            [0.0, 0.2171850639713355, 0.4840621929215732, 0.7430526238101408, 0.9312114164253432,
             1.0, 0.9312114164253432, 0.7430526238101408, 0.4840621929215732, 0.2171850639713355];
        conv_test(table, &expected, 0.2, 1e-4);
    }

    #[test]
    fn linear_interpolation_lookup_table32_test() {
        let window = RaiseCosineFuncton::new(0.35);
        let table = LinearTableLookup::<f64>::from_conv_function(&window, 0.4, 0.0, 10);
        let expected =
            [0.0, 0.2171850639713355, 0.4840621929215732, 0.7430526238101408, 0.9312114164253432,
             1.0, 0.9312114164253432, 0.7430526238101408, 0.4840621929215732, 0.2171850639713355];
        conv_test(table, &expected, 0.2, 0.1);
    }
}<commit_msg>Added more flavors of conv functions<commit_after>//! Types around a convolution, see also https://en.wikipedia.org/wiki/Convolution.
//!
//! Convolutions in this library can be defined in time or frequency domain. In
//! frequency domain the convolution is automatically transformed into a multiplication
//! which is the analog operation to a convolution in time domain.
use RealNumber;
use num::traits::Zero;
use num::complex::{Complex, Complex32,Complex64};

/// The domain of a convolution.
/// Convolutions in this library can be defined in time or frequency domain. In
/// frequency domain the convolution is automatically transformed into a multiplication
/// which is the analog operation to a convolution in time domain.
#[derive(Copy)]
#[derive(Clone)]
#[derive(PartialEq)]
#[derive(Debug)]
pub enum ConvDomain {
	/// Time domain, the x-axis is in [s]
	Time,
	/// Frequency domain, the x-axis in in [Hz]
    Frequency
}

/// Meta information about the convolution.
pub trait ConvInfo {
    fn domain(&self) -> ConvDomain;
}

/// A convolution function in time domain and real number space
pub trait RealTimeConvFunction<T> : ConvInfo
    where T: RealNumber {
    fn calc(&self, x: T) -> T;
}

/// A convolution function in time domain and complex number space
pub trait ComplexTimeConvFunction<T> : ConvInfo
    where T: RealNumber {
    fn calc(&self, x: T) -> Complex<T>;
}

/// A convolution function in frequency domain and complex number space
pub trait ComplexFrequencyConvFunction<T> : ConvInfo
    where T: RealNumber {
    fn calc(&self, x: T) -> Complex<T>;
}

/// Allows to create a lookup table with linear interpolation between table points.
/// This usually speeds up a convolution and sacrifies accuracy.
pub struct RealTimeLinearTableLookup<T>
    where T: RealNumber {
    table: Vec<T>,
    domain: ConvDomain,
    delta: T,
    offset: T
}

/// Allows to create a lookup table with linear interpolation between table points.
/// This usually speeds up a convolution and sacrifies accuracy.
pub struct ComplexTimeLinearTableLookup<T>
    where T: RealNumber {
    table: Vec<Complex<T>>,
    domain: ConvDomain,
    delta: T,
    offset: T
}

/// Allows to create a lookup table with linear interpolation between table points.
/// This usually speeds up a convolution and sacrifies accuracy.
pub struct ComplexFrequencyLinearTableLookup<T>
    where T: RealNumber {
    table: Vec<Complex<T>>,
    domain: ConvDomain,
    delta: T,
    offset: T
}

macro_rules! add_linear_table_lookup_impl {
    ($($name: ident: $conv_type: ident, $($data_type: ident, $result_type:ident),*);*) => {
        $(
            $(
                impl ConvInfo for $name<$data_type> {
                    fn domain(&self) -> ConvDomain {
                        self.domain
                    }
                }

                impl $conv_type<$data_type> for $name<$data_type> {
                    fn calc(&self, x: $data_type) -> $result_type {
                        let len = self.table.len();
                        let center = len / 2;
                        let center_float = center as $data_type;
                        let x = x / self.delta - self.offset + center_float;
                        let round_float = x.round();
                        let round = round_float as usize;
                        if round >= len {
                            return $result_type::zero();
                        }

                        let round_tolerance = 1e-6;
                        if (x - round_float).abs() < round_tolerance {
                            return self.table[round];
                        }

                        if x > round_float {
                            let other = round + 1;
                            if other >= len {
                                return self.table[round];
                            }
                            let y0 = self.table[round];
                            let x0 = round_float;
                            let y1 = self.table[other];
                            return y0 + (y1 - y0) * (x - x0);
                        } else {
                            if round == 0 {
                                return self.table[round];
                            }
                            let other = round - 1;
                            let y0 = self.table[round];
                            let x0 = round_float;
                            let y1 = self.table[other];
                            return y0 + (y1 - y0) * (x0 - x);
                        }
                    }
                }

                impl $name<$data_type> {
                    pub fn from_raw_parts(table: &[$result_type], domain: ConvDomain, delta: $data_type, offset: $data_type) -> Self {
                        let owned_table = Vec::from(table);
                        $name { table: owned_table, domain: domain, delta: delta, offset: offset }
                    }

                    pub fn from_conv_function(other: &$conv_type<$data_type>, delta: $data_type, offset: $data_type, len: usize) -> Self {
                        let center = (len / 2) as isize;
                        let mut table = vec![$result_type::zero(); len];
                        let mut i = -center;
                        for n in &mut table {
                            *n = other.calc((i as $data_type) * delta);
                            i += 1;
                        }
                        $name { table: table, domain: other.domain(), delta: delta, offset: offset }
                    }
                }
            )*
        )*
    }
}
add_linear_table_lookup_impl!(
    RealTimeLinearTableLookup: RealTimeConvFunction, f32, f32, f64, f64;
    ComplexTimeLinearTableLookup: ComplexTimeConvFunction, f32, Complex32, f64, Complex64;
    ComplexFrequencyLinearTableLookup: ComplexFrequencyConvFunction, f32, Complex32, f64, Complex64);

/// Raised cosine function according to https://en.wikipedia.org/wiki/Raised-cosine_filter
pub struct  RaiseCosineFuncton<T>
    where T: RealNumber {
    rolloff: T
}

impl<T> ConvInfo for RaiseCosineFuncton<T>
    where T: RealNumber {
    fn domain(&self) -> ConvDomain {
        ConvDomain::Time
    }
}

impl<T> RealTimeConvFunction<T> for RaiseCosineFuncton<T>
    where T: RealNumber {
    fn calc(&self, x: T) -> T {
        if x == T::zero() {
            return T::one();
        }

        let one = T::one();
        let two = T::from(2.0).unwrap();
        let pi = two * one.asin();
        let four = two * two;
        if x.abs() == one / (two * self.rolloff) {
            let arg = pi / two / self.rolloff;
            return (arg).sin() / arg * pi / four;
        }

        let pi_x = pi * x;
        let arg = two * self.rolloff * x;
        return pi_x.sin() * (pi_x * self.rolloff).cos() / pi_x / (one - (arg * arg))
    }
}

impl<T> RaiseCosineFuncton<T>
    where T: RealNumber {
    pub fn new(rolloff: T) -> Self {
        RaiseCosineFuncton { rolloff: rolloff }
    }
}

#[cfg(test)]
mod tests {
	use super::*;
    use super::super::RealNumber;
    use std::fmt::Debug;

    fn conv_test<T, C>(conv: C, expected: &[T], step: T, tolerance: T)
        where T: RealNumber + Debug,
              C: RealTimeConvFunction<T> {
        let mut result = vec![T::zero(); expected.len()];
        let mut j = -(expected.len() as isize / 2);
        for i in 0..result.len() {
            result[i] = conv.calc(T::from(j).unwrap() * step);
            j += 1;
        }

        for i in 0..result.len() {
            if (result[i] - expected[i]).abs() > tolerance {
                panic!("assertion failed: {:?} != {:?}", result, expected);
            }
        }
    }

	#[test]
	fn raised_cosine32_test()
	{
        let window = RaiseCosineFuncton::new(0.35);
        let expected =
            [0.0, 0.2171850639713355, 0.4840621929215732, 0.7430526238101408, 0.9312114164253432,
             1.0, 0.9312114164253432, 0.7430526238101408, 0.4840621929215732, 0.2171850639713355];
        conv_test(window, &expected, 0.2, 1e-4);
	}

    #[test]
    fn lookup_table32_test() {
        let window = RaiseCosineFuncton::new(0.35);
        let table = RealTimeLinearTableLookup::<f64>::from_conv_function(&window, 0.2, 0.0, 10);
        let expected =
            [0.0, 0.2171850639713355, 0.4840621929215732, 0.7430526238101408, 0.9312114164253432,
             1.0, 0.9312114164253432, 0.7430526238101408, 0.4840621929215732, 0.2171850639713355];
        conv_test(table, &expected, 0.2, 1e-4);
    }

    #[test]
    fn linear_interpolation_lookup_table32_test() {
        let window = RaiseCosineFuncton::new(0.35);
        let table = RealTimeLinearTableLookup::<f64>::from_conv_function(&window, 0.4, 0.0, 10);
        let expected =
            [0.0, 0.2171850639713355, 0.4840621929215732, 0.7430526238101408, 0.9312114164253432,
             1.0, 0.9312114164253432, 0.7430526238101408, 0.4840621929215732, 0.2171850639713355];
        conv_test(table, &expected, 0.2, 0.1);
    }
}<|endoftext|>


<commit_before>generate_error_module!(
    generate_error_types!(ViewError, ViewErrorKind,
        Unknown              => "Unknown view error",
        GlobError            => "Error while glob()ing",
        PatternError         => "Error in glob() pattern",
        PatternBuildingError => "Could not build glob() pattern"
    );
);

pub use self::error::ViewError;
pub use self::error::ViewErrorKind;

<commit_msg>Pass MapErrInto trait out of error module<commit_after>generate_error_module!(
    generate_error_types!(ViewError, ViewErrorKind,
        Unknown              => "Unknown view error",
        GlobError            => "Error while glob()ing",
        PatternError         => "Error in glob() pattern",
        PatternBuildingError => "Could not build glob() pattern"
    );
);

pub use self::error::ViewError;
pub use self::error::ViewErrorKind;
pub use self::error::MapErrInto;
<|endoftext|>


<commit_before>use cairo;
use cairo::MatrixTrait;

use aspect_ratio::AspectRatio;
use draw::add_clipping_rect;
use drawing_ctx::{self, RsvgDrawingCtx};
use float_eq_cairo::ApproxEqCairo;
use state::ComputedValues;
use viewbox::*;

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ClipMode {
    ClipToViewport,
    ClipToVbox,
}

pub fn draw_in_viewport<F>(
    vx: f64,
    vy: f64,
    vw: f64,
    vh: f64,
    clip_mode: ClipMode,
    do_clip: bool,
    vbox: Option<ViewBox>,
    preserve_aspect_ratio: AspectRatio,
    values: &ComputedValues,
    affine: cairo::Matrix,
    draw_ctx: *mut RsvgDrawingCtx,
    clipping: bool,
    draw_fn: F,
) where
    F: FnOnce(),
{
    let mut ctx = RsvgDrawingCtxWrapper(draw_ctx);

    in_viewport(
        &mut ctx,
        vx,
        vy,
        vw,
        vh,
        clip_mode,
        do_clip,
        vbox,
        preserve_aspect_ratio,
        values,
        affine,
        clipping,
        draw_fn,
    );
}

trait ViewportCtx {
    fn push_view_box(&mut self, width: f64, height: f64);
    fn pop_view_box(&mut self);
    fn push_discrete_layer(&mut self, values: &ComputedValues, clipping: bool);
    fn pop_discrete_layer(&mut self, values: &ComputedValues, clipping: bool);
    fn add_clipping_rect(&mut self, x: f64, y: f64, w: f64, h: f64);
    fn set_affine(&mut self, affine: cairo::Matrix);
}

struct RsvgDrawingCtxWrapper(pub *mut RsvgDrawingCtx);

impl ViewportCtx for RsvgDrawingCtxWrapper {
    fn push_view_box(&mut self, width: f64, height: f64) {
        drawing_ctx::push_view_box(self.0, width, height);
    }

    fn pop_view_box(&mut self) {
        drawing_ctx::pop_view_box(self.0);
    }

    fn push_discrete_layer(&mut self, values: &ComputedValues, clipping: bool) {
        drawing_ctx::push_discrete_layer(self.0, values, clipping);
    }

    fn pop_discrete_layer(&mut self, values: &ComputedValues, clipping: bool) {
        drawing_ctx::pop_discrete_layer(self.0, values, clipping);
    }

    fn add_clipping_rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
        add_clipping_rect(self.0, x, y, w, h);
    }

    fn set_affine(&mut self, affine: cairo::Matrix) {
        drawing_ctx::get_cairo_context(self.0).set_matrix(affine);
    }
}

fn in_viewport<F>(
    ctx: &mut ViewportCtx,
    vx: f64,
    vy: f64,
    vw: f64,
    vh: f64,
    clip_mode: ClipMode,
    do_clip: bool,
    vbox: Option<ViewBox>,
    preserve_aspect_ratio: AspectRatio,
    values: &ComputedValues,
    mut affine: cairo::Matrix,
    clipping: bool,
    draw_fn: F,
) where
    F: FnOnce(),
{
    // width or height set to 0 disables rendering of the element
    // https://www.w3.org/TR/SVG/struct.html#SVGElementWidthAttribute
    // https://www.w3.org/TR/SVG/struct.html#UseElementWidthAttribute
    // https://www.w3.org/TR/SVG/struct.html#ImageElementWidthAttribute
    // https://www.w3.org/TR/SVG/painting.html#MarkerWidthAttribute

    if vw.approx_eq_cairo(&0.0) || vh.approx_eq_cairo(&0.0) {
        return;
    }

    let old_affine = affine;

    if let Some(vbox) = vbox {
        // the preserveAspectRatio attribute is only used if viewBox is specified
        // https://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute

        if vbox.0.width.approx_eq_cairo(&0.0) || vbox.0.height.approx_eq_cairo(&0.0) {
            // Width or height of 0 for the viewBox disables rendering of the element
            // https://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
            return;
        }

        ctx.push_view_box(vbox.0.width, vbox.0.height);
        ctx.push_discrete_layer(values, clipping);

        let (x, y, w, h) =
            preserve_aspect_ratio.compute(vbox.0.width, vbox.0.height, vx, vy, vw, vh);

        affine.translate(x, y);
        affine.scale(w / vbox.0.width, h / vbox.0.height);
        affine.translate(-vbox.0.x, -vbox.0.y);

        ctx.set_affine(affine);

        if do_clip && clip_mode == ClipMode::ClipToVbox {
            ctx.add_clipping_rect(vbox.0.x, vbox.0.y, vbox.0.width, vbox.0.height);
        }
    } else {
        ctx.push_view_box(vw, vh);
        ctx.push_discrete_layer(values, clipping);

        affine.translate(vx, vy);
        ctx.set_affine(affine);
    }

    if do_clip && clip_mode == ClipMode::ClipToViewport {
        ctx.set_affine(old_affine);
        ctx.add_clipping_rect(vx, vy, vw, vh);
        ctx.set_affine(affine);
    }

    draw_fn();

    ctx.pop_discrete_layer(values, clipping);
    ctx.pop_view_box();
}

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

    #[derive(Default, PartialEq)]
    struct Ctx {
        pub view_box_size: Option<(f64, f64)>,
        pub clipping_rect: Option<(f64, f64, f64, f64)>,
        pub affine: Option<cairo::Matrix>,

        pub expected_view_box_size: Option<(f64, f64)>,
        pub expected_clipping_rect: Option<(f64, f64, f64, f64)>,
        pub expected_affine: Option<cairo::Matrix>,
    }

    impl ViewportCtx for Ctx {
        fn push_view_box(&mut self, width: f64, height: f64) {
            self.view_box_size = Some((width, height));
        }

        fn pop_view_box(&mut self) {}

        fn push_discrete_layer(&mut self, _values: &ComputedValues, _clipping: bool) {}

        fn pop_discrete_layer(&mut self, _values: &ComputedValues, _clipping: bool) {}

        fn add_clipping_rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
            self.clipping_rect = Some((x, y, w, h));
        }

        fn set_affine(&mut self, affine: cairo::Matrix) {
            self.affine = Some(affine);
        }
    }

    fn call_in_viewport(
        vx: f64,
        vy: f64,
        vw: f64,
        vh: f64,
        clip_mode: ClipMode,
        do_clip: bool,
        vbox: Option<ViewBox>,
        preserve_aspect_ratio: AspectRatio,
        affine: cairo::Matrix,
        ctx: &mut Ctx,
    ) {
        in_viewport(
            ctx,
            vx,
            vy,
            vw,
            vh,
            clip_mode,
            do_clip,
            vbox,
            preserve_aspect_ratio,
            &ComputedValues::default(),
            affine,
            false,
            || (),
        );

        assert_eq!(ctx.view_box_size, ctx.expected_view_box_size);
        assert_eq!(ctx.clipping_rect, ctx.expected_clipping_rect);
        assert_eq!(ctx.affine, ctx.expected_affine);
    }

    #[test]
    fn clip_to_viewport() {
        let mut affine = cairo::Matrix::identity();
        affine.scale(0.25, 0.25);

        let mut ctx = Ctx {
            view_box_size: None,
            clipping_rect: None,
            affine: None,

            expected_view_box_size: Some((40.0, 40.0)),
            expected_clipping_rect: Some((10.0, 10.0, 10.0, 10.0)),
            expected_affine: Some(affine),
        };

        call_in_viewport(
            10.0,
            10.0,
            10.0,
            10.0,
            ClipMode::ClipToViewport,
            true,
            Some(ViewBox(cairo::Rectangle {
                x: 40.0,
                y: 40.0,
                width: 40.0,
                height: 40.0,
            })),
            AspectRatio::parse("xMidYMid meet", ()).unwrap(),
            cairo::Matrix::identity(),
            &mut ctx,
        );
    }

    #[test]
    fn clip_to_vbox() {
        let mut affine = cairo::Matrix::identity();
        affine.translate(10.0, 10.0);
        affine.scale(0.40, 0.40);

        let mut ctx = Ctx {
            view_box_size: None,
            clipping_rect: None,
            affine: None,

            expected_view_box_size: Some((50.0, 50.0)),
            expected_clipping_rect: Some((0.0, 0.0, 50.0, 50.0)),
            expected_affine: Some(affine),
        };

        call_in_viewport(
            10.0,
            10.0,
            20.0,
            20.0,
            ClipMode::ClipToVbox,
            true,
            Some(ViewBox(cairo::Rectangle {
                x: 0.0,
                y: 0.0,
                width: 50.0,
                height: 50.0,
            })),
            AspectRatio::parse("xMidYMid meet", ()).unwrap(),
            cairo::Matrix::identity(),
            &mut ctx,
        );
    }
}
<commit_msg>viewport.rs: push a discrete layer in a single place<commit_after>use cairo;
use cairo::MatrixTrait;

use aspect_ratio::AspectRatio;
use draw::add_clipping_rect;
use drawing_ctx::{self, RsvgDrawingCtx};
use float_eq_cairo::ApproxEqCairo;
use state::ComputedValues;
use viewbox::*;

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ClipMode {
    ClipToViewport,
    ClipToVbox,
}

pub fn draw_in_viewport<F>(
    vx: f64,
    vy: f64,
    vw: f64,
    vh: f64,
    clip_mode: ClipMode,
    do_clip: bool,
    vbox: Option<ViewBox>,
    preserve_aspect_ratio: AspectRatio,
    values: &ComputedValues,
    affine: cairo::Matrix,
    draw_ctx: *mut RsvgDrawingCtx,
    clipping: bool,
    draw_fn: F,
) where
    F: FnOnce(),
{
    let mut ctx = RsvgDrawingCtxWrapper(draw_ctx);

    in_viewport(
        &mut ctx,
        vx,
        vy,
        vw,
        vh,
        clip_mode,
        do_clip,
        vbox,
        preserve_aspect_ratio,
        values,
        affine,
        clipping,
        draw_fn,
    );
}

trait ViewportCtx {
    fn push_view_box(&mut self, width: f64, height: f64);
    fn pop_view_box(&mut self);
    fn push_discrete_layer(&mut self, values: &ComputedValues, clipping: bool);
    fn pop_discrete_layer(&mut self, values: &ComputedValues, clipping: bool);
    fn add_clipping_rect(&mut self, x: f64, y: f64, w: f64, h: f64);
    fn set_affine(&mut self, affine: cairo::Matrix);
}

struct RsvgDrawingCtxWrapper(pub *mut RsvgDrawingCtx);

impl ViewportCtx for RsvgDrawingCtxWrapper {
    fn push_view_box(&mut self, width: f64, height: f64) {
        drawing_ctx::push_view_box(self.0, width, height);
    }

    fn pop_view_box(&mut self) {
        drawing_ctx::pop_view_box(self.0);
    }

    fn push_discrete_layer(&mut self, values: &ComputedValues, clipping: bool) {
        drawing_ctx::push_discrete_layer(self.0, values, clipping);
    }

    fn pop_discrete_layer(&mut self, values: &ComputedValues, clipping: bool) {
        drawing_ctx::pop_discrete_layer(self.0, values, clipping);
    }

    fn add_clipping_rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
        add_clipping_rect(self.0, x, y, w, h);
    }

    fn set_affine(&mut self, affine: cairo::Matrix) {
        drawing_ctx::get_cairo_context(self.0).set_matrix(affine);
    }
}

fn in_viewport<F>(
    ctx: &mut ViewportCtx,
    vx: f64,
    vy: f64,
    vw: f64,
    vh: f64,
    clip_mode: ClipMode,
    do_clip: bool,
    vbox: Option<ViewBox>,
    preserve_aspect_ratio: AspectRatio,
    values: &ComputedValues,
    mut affine: cairo::Matrix,
    clipping: bool,
    draw_fn: F,
) where
    F: FnOnce(),
{
    // width or height set to 0 disables rendering of the element
    // https://www.w3.org/TR/SVG/struct.html#SVGElementWidthAttribute
    // https://www.w3.org/TR/SVG/struct.html#UseElementWidthAttribute
    // https://www.w3.org/TR/SVG/struct.html#ImageElementWidthAttribute
    // https://www.w3.org/TR/SVG/painting.html#MarkerWidthAttribute

    if vw.approx_eq_cairo(&0.0) || vh.approx_eq_cairo(&0.0) {
        return;
    }

    let old_affine = affine;

    ctx.push_discrete_layer(values, clipping);

    if let Some(vbox) = vbox {
        // the preserveAspectRatio attribute is only used if viewBox is specified
        // https://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute

        if vbox.0.width.approx_eq_cairo(&0.0) || vbox.0.height.approx_eq_cairo(&0.0) {
            // Width or height of 0 for the viewBox disables rendering of the element
            // https://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
            return;
        }

        ctx.push_view_box(vbox.0.width, vbox.0.height);

        let (x, y, w, h) =
            preserve_aspect_ratio.compute(vbox.0.width, vbox.0.height, vx, vy, vw, vh);

        affine.translate(x, y);
        affine.scale(w / vbox.0.width, h / vbox.0.height);
        affine.translate(-vbox.0.x, -vbox.0.y);

        ctx.set_affine(affine);

        if do_clip && clip_mode == ClipMode::ClipToVbox {
            ctx.add_clipping_rect(vbox.0.x, vbox.0.y, vbox.0.width, vbox.0.height);
        }
    } else {
        ctx.push_view_box(vw, vh);

        affine.translate(vx, vy);
        ctx.set_affine(affine);
    }

    if do_clip && clip_mode == ClipMode::ClipToViewport {
        ctx.set_affine(old_affine);
        ctx.add_clipping_rect(vx, vy, vw, vh);
        ctx.set_affine(affine);
    }

    draw_fn();

    ctx.pop_view_box();
    ctx.pop_discrete_layer(values, clipping);
}

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

    #[derive(Default, PartialEq)]
    struct Ctx {
        pub view_box_size: Option<(f64, f64)>,
        pub clipping_rect: Option<(f64, f64, f64, f64)>,
        pub affine: Option<cairo::Matrix>,

        pub expected_view_box_size: Option<(f64, f64)>,
        pub expected_clipping_rect: Option<(f64, f64, f64, f64)>,
        pub expected_affine: Option<cairo::Matrix>,
    }

    impl ViewportCtx for Ctx {
        fn push_view_box(&mut self, width: f64, height: f64) {
            self.view_box_size = Some((width, height));
        }

        fn pop_view_box(&mut self) {}

        fn push_discrete_layer(&mut self, _values: &ComputedValues, _clipping: bool) {}

        fn pop_discrete_layer(&mut self, _values: &ComputedValues, _clipping: bool) {}

        fn add_clipping_rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
            self.clipping_rect = Some((x, y, w, h));
        }

        fn set_affine(&mut self, affine: cairo::Matrix) {
            self.affine = Some(affine);
        }
    }

    fn call_in_viewport(
        vx: f64,
        vy: f64,
        vw: f64,
        vh: f64,
        clip_mode: ClipMode,
        do_clip: bool,
        vbox: Option<ViewBox>,
        preserve_aspect_ratio: AspectRatio,
        affine: cairo::Matrix,
        ctx: &mut Ctx,
    ) {
        in_viewport(
            ctx,
            vx,
            vy,
            vw,
            vh,
            clip_mode,
            do_clip,
            vbox,
            preserve_aspect_ratio,
            &ComputedValues::default(),
            affine,
            false,
            || (),
        );

        assert_eq!(ctx.view_box_size, ctx.expected_view_box_size);
        assert_eq!(ctx.clipping_rect, ctx.expected_clipping_rect);
        assert_eq!(ctx.affine, ctx.expected_affine);
    }

    #[test]
    fn clip_to_viewport() {
        let mut affine = cairo::Matrix::identity();
        affine.scale(0.25, 0.25);

        let mut ctx = Ctx {
            view_box_size: None,
            clipping_rect: None,
            affine: None,

            expected_view_box_size: Some((40.0, 40.0)),
            expected_clipping_rect: Some((10.0, 10.0, 10.0, 10.0)),
            expected_affine: Some(affine),
        };

        call_in_viewport(
            10.0,
            10.0,
            10.0,
            10.0,
            ClipMode::ClipToViewport,
            true,
            Some(ViewBox(cairo::Rectangle {
                x: 40.0,
                y: 40.0,
                width: 40.0,
                height: 40.0,
            })),
            AspectRatio::parse("xMidYMid meet", ()).unwrap(),
            cairo::Matrix::identity(),
            &mut ctx,
        );
    }

    #[test]
    fn clip_to_vbox() {
        let mut affine = cairo::Matrix::identity();
        affine.translate(10.0, 10.0);
        affine.scale(0.40, 0.40);

        let mut ctx = Ctx {
            view_box_size: None,
            clipping_rect: None,
            affine: None,

            expected_view_box_size: Some((50.0, 50.0)),
            expected_clipping_rect: Some((0.0, 0.0, 50.0, 50.0)),
            expected_affine: Some(affine),
        };

        call_in_viewport(
            10.0,
            10.0,
            20.0,
            20.0,
            ClipMode::ClipToVbox,
            true,
            Some(ViewBox(cairo::Rectangle {
                x: 0.0,
                y: 0.0,
                width: 50.0,
                height: 50.0,
            })),
            AspectRatio::parse("xMidYMid meet", ()).unwrap(),
            cairo::Matrix::identity(),
            &mut ctx,
        );
    }
}
<|endoftext|>


<commit_before>use std::{
    ffi::OsString,
    fs::{self, remove_dir_all},
    path::{Path, PathBuf},
};

use binary_node::{BinaryInstallation, BinaryTrait, NodeBinary};
use buildpack::{
    eyre::{self, bail, eyre},
    fs_utils::{copy_dir_all, symlink_dir, symlink_file},
    hash_utils::file_sha256_hex,
    is_cnb_build, is_local_build,
    libcnb::{
        self,
        build::{BuildContext, BuildResult, BuildResultBuilder},
        data::{build_plan::BuildPlan, layer_content_metadata::LayerTypes, layer_name},
        detect::{DetectContext, DetectResult, DetectResultBuilder},
        generic::{GenericMetadata, GenericPlatform},
        layer::{ExistingLayerStrategy, Layer, LayerResult, LayerResultBuilder},
        layer_env::{LayerEnv, ModificationBehavior, Scope},
        Buildpack,
        Error::BuildpackError,
    },
    tracing, BuildpackTrait, SYSTEM_INSTALLED,
};
use serde::{Deserialize, Serialize};

pub struct NodeBuildpack;

impl BuildpackTrait for NodeBuildpack {
    fn toml() -> &'static str {
        include_str!("../buildpack.toml")
    }
}

const NVMRC: &str = ".nvmrc";
const PACKAGE_JSON: &str = "package.json";
const PACKAGE_LOCK: &str = "package-lock.json";
const TOOL_VERSIONS: &str = ".tool-versions";

impl Buildpack for NodeBuildpack {
    type Platform = GenericPlatform;
    type Metadata = GenericMetadata;
    type Error = eyre::Report;

    fn detect(&self, _context: DetectContext<Self>) -> libcnb::Result<DetectResult, Self::Error> {
        // Read `.tool-versions` for Node.js version
        let tool_versions = Self::tool_versions();

        // Read `.nvmrc` for Node.js version
        let nvmrc = fs::read_to_string(NVMRC)
            .map(|content| content.trim().to_string())
            .ok();

        // Read `package.json` for Node.js version
        let package_json = fs::read_to_string(PACKAGE_JSON)
            .ok()
            .and_then(|json| serde_json::from_str::<serde_json::Value>(&json).ok());

        // Detect `package-lock.json`
        let package_lock = PathBuf::from(PACKAGE_LOCK);

        // Fail early
        if !(tool_versions.contains_key("nodejs")
            || tool_versions.contains_key("node")
            || package_json.is_some()
            || package_lock.exists()
            || nvmrc.is_some()
            || Self::any_exist(&["main.js", "index.js"]))
        {
            return DetectResultBuilder::fail().build();
        }

        let mut requires = Vec::new();
        let mut provides = Vec::new();

        // Resolve Node.js version from `.tool-versions`, `.nvmrc`, `package.json`, or installed `node` version
        let (version, source) = if let Some(version) = tool_versions
            .get("nodejs")
            .or_else(|| tool_versions.get("node"))
        {
            (version.to_string(), TOOL_VERSIONS)
        } else if let Some(versionish) = nvmrc {
            (versionish, NVMRC)
        } else if let Some(semver) = package_json.as_ref().and_then(|package| {
            package
                .pointer("/engines/node")
                .and_then(|semver| semver.as_str().map(|semver| semver.to_string()))
        }) {
            (semver, PACKAGE_JSON)
        } else if let Some(version) = (NodeBinary {}).installed_version(None) {
            (version, SYSTEM_INSTALLED)
        } else {
            ("".to_string(), "")
        };

        // Require and provide Node.js
        let (require, provide) = Self::require_and_provide(
            format!("node {}", version).trim(),
            source,
            format!("Install Node.js {}", version).trim(),
        );
        requires.push(require);
        provides.push(provide);

        // Determine how NPM packages are to be installed
        if package_lock.exists() || package_json.is_some() {
            let (require, provide) = Self::require_and_provide(
                "npm install",
                if package_lock.exists() {
                    PACKAGE_LOCK
                } else {
                    PACKAGE_JSON
                },
                "Install NPM packages",
            );
            requires.push(require);
            provides.push(provide);
        }

        let mut build_plan = BuildPlan::new();
        build_plan.requires = requires;
        build_plan.provides = provides;
        DetectResultBuilder::pass().build_plan(build_plan).build()
    }

    fn build(&self, context: BuildContext<Self>) -> libcnb::Result<BuildResult, Self::Error> {
        for entry in &context.buildpack_plan.entries {
            let (name, args) = Self::split_entry_name(&entry.name);
            match name.as_str() {
                "node" => {
                    context.handle_layer(layer_name!("node"), NodeLayer::new(args))?;
                }
                "npm" => {
                    context.handle_layer(layer_name!("npm"), NpmLayer)?;
                }
                _ => {
                    return Err(BuildpackError(eyre!(
                        "Unhandled buildpack plan entry: {}",
                        name
                    )))
                }
            };
        }

        BuildResultBuilder::new().build()
    }
}

struct NodeLayer {
    // The semantic version requirement for the Node.js binary
    requirement: String,
}

impl NodeLayer {
    fn new(args: Vec<String>) -> Self {
        // Join args with commas because semver requirement parser expects that is
        // how parts of a requirement are separated
        let versionish = args.join(",");

        // Determine the semver requirement from versionish which could be a well formed semantic version (e.g 14.0.1),
        // a semver requirement (e.g. ^14.0), or an alias (e.g. `lts`)
        let mut requirement = if let Some(version) = versionish.strip_prefix('v') {
            version.to_string()
        } else if versionish == "lts" {
            // TODO: Determine LTS without doing a fetch, perhaps based on date
            // https://nodejs.org/en/about/releases/
            "^16".to_string()
        } else {
            versionish.clone()
        };
        if requirement.is_empty() {
            requirement = "*".to_string();
        }

        NodeLayer { requirement }
    }
}

impl Layer for NodeLayer {
    type Buildpack = NodeBuildpack;
    type Metadata = GenericMetadata;

    fn types(&self) -> LayerTypes {
        LayerTypes {
            build: true,
            launch: true,
            cache: true,
        }
    }

    fn existing_layer_strategy(
        &self,
        _context: &BuildContext<Self::Buildpack>,
        layer_data: &libcnb::layer::LayerData<Self::Metadata>,
    ) -> Result<libcnb::layer::ExistingLayerStrategy, <Self::Buildpack as Buildpack>::Error> {
        let installed = NodeBinary {}.installed_at(
            &layer_data.path.join("bin").join("node"),
            Some(self.requirement.clone()),
        )?;
        let strategy = if installed {
            tracing::info!(
                "Existing `node` layer has `./bin/node` matching semver `{}`; will keep",
                self.requirement
            );
            ExistingLayerStrategy::Keep
        } else {
            tracing::info!(
                "Existing `node` layer does not have `./bin/node` matching semver `{}`; will recreate",
                self.requirement
            );
            ExistingLayerStrategy::Recreate
        };
        Ok(strategy)
    }

    fn create(
        &self,
        context: &BuildContext<Self::Buildpack>,
        layer_path: &Path,
    ) -> Result<LayerResult<Self::Metadata>, eyre::Report> {
        tracing::info!("Creating `node` layer with semver `{}`", self.requirement);

        let node = NodeBinary {}.require_sync(Some(self.requirement.clone()), true)?;
        let version = node.version()?;

        if is_local_build(context) {
            if node.is_stencila_install() {
                tracing::info!("Linking to `node {}` installed by Stencila", version);
                let source = node.grandparent()?;

                symlink_dir(source.join("bin"), &layer_path.join("bin"))?;
                symlink_dir(source.join("lib"), &layer_path.join("lib"))?;
            } else {
                tracing::info!("Linking to `node {}` installed on system", version);
                let source = node.parent()?;

                let bin_path = layer_path.join("bin");
                fs::create_dir_all(&bin_path)?;
                symlink_file(node.path, bin_path.join(node.name))?;
                symlink_file(source.join("corepack"), bin_path.join("corepack"))?;
                symlink_file(source.join("npm"), bin_path.join("npm"))?;
                symlink_file(source.join("npx"), bin_path.join("npx"))?;

                let lib_path = layer_path.join("lib");
                fs::create_dir_all(&lib_path)?;
                symlink_dir(
                    source.join("..").join("lib").join("node_modules"),
                    lib_path.join("node_modules"),
                )?;
            }
        } else {
            #[allow(clippy::collapsible_else_if)]
            if node.is_stencila_install() {
                tracing::info!("Using `node {}` installed by Stencila", version);
                let source = node.grandparent()?;

                copy_dir_all(source, layer_path)?;
            } else {
                bail!("Only able to build `node` layer if Node has been installed by Stencila")
            }
        }

        LayerResultBuilder::new(GenericMetadata::default()).build()
    }
}

struct NpmLayer;

#[derive(Clone, Serialize, Deserialize)]
struct NpmLayerMetadata {
    package_hash: String,
}

impl NpmLayerMetadata {
    /// Generate `package_hash` string from a project directory
    ///
    /// If the directory has a `package-lock.json` that will be used. If not
    /// `package.json` will be used. This means that if the user removes
    /// or updates `package-lock.json` the layer will be updated. If there is no lock file
    /// then the layer will only be updated if there are changes in `package.json`.
    fn generate_package_hash(app_dir: &Path) -> Result<String, eyre::Report> {
        file_sha256_hex(app_dir.join("package-lock.json"))
            .or_else(|_| file_sha256_hex(app_dir.join("package.json")))
    }
}

impl Layer for NpmLayer {
    type Buildpack = NodeBuildpack;
    type Metadata = NpmLayerMetadata;

    fn types(&self) -> LayerTypes {
        LayerTypes {
            build: true,
            launch: true,
            cache: true,
        }
    }

    fn existing_layer_strategy(
        &self,
        context: &BuildContext<Self::Buildpack>,
        layer_data: &libcnb::layer::LayerData<Self::Metadata>,
    ) -> Result<libcnb::layer::ExistingLayerStrategy, <Self::Buildpack as Buildpack>::Error> {
        let package_lock_hash =
            NpmLayerMetadata::generate_package_hash(&context.app_dir).unwrap_or_default();
        let strategy = if package_lock_hash == layer_data.content_metadata.metadata.package_hash {
            tracing::info!("Existing `npm` layer has same package hash; will keep",);
            ExistingLayerStrategy::Keep
        } else {
            tracing::info!("Existing `npm` layer has different package hash; will update");
            ExistingLayerStrategy::Update
        };
        Ok(strategy)
    }

    fn create(
        &self,
        context: &BuildContext<Self::Buildpack>,
        layer_path: &Path,
    ) -> Result<LayerResult<Self::Metadata>, eyre::Report> {
        tracing::info!("Creating `npm` layer");
        self.install(context, layer_path)
    }

    fn update(
        &self,
        context: &BuildContext<Self::Buildpack>,
        layer_data: &libcnb::layer::LayerData<Self::Metadata>,
    ) -> Result<LayerResult<Self::Metadata>, <Self::Buildpack as Buildpack>::Error> {
        tracing::info!("Updating `npm` layer");
        self.install(context, &layer_data.path)
    }
}

impl NpmLayer {
    fn install(
        &self,
        context: &BuildContext<NodeBuildpack>,
        layer_path: &Path,
    ) -> Result<LayerResult<NpmLayerMetadata>, eyre::Report> {
        // Use `node` and `npm` installed in the `node` layer
        let node_layer = layer_path
            .canonicalize()?
            .parent()
            .expect("Should have parent")
            .join("node");

        // Prepend to the PATH because some package install scripts
        // use `/usr/bin/env node` which looks for `node` on PATH
        let mut envs: Vec<(OsString, OsString)> = vec![(
            "PATH".into(),
            NodeBuildpack::prepend_path(&node_layer.join("bin"))?,
        )];

        // If not a local build use `layer_path/cache` as the NPM cache
        if !is_local_build(context) {
            envs.push(("NPM_CONFIG_CACHE".into(), layer_path.join("cache").into()));
        }

        // Call the `npm-cli.js` script installed in the `node` layer
        // This is done, rather than executing `bin/npm` directly there are issues with node `require`
        // module resolution when the latter is used.
        let node = BinaryInstallation {
            name: "node".into(),
            path: node_layer.join("bin").join("node"),
            env: envs,
            ..Default::default()
        };
        let npm = node_layer
            .join("lib")
            .join("node_modules")
            .join("npm")
            .join("bin")
            .join("npm-cli.js")
            .display()
            .to_string();

        // Read any `package-lock.json` so that we can restore it, or remove the new one that
        // install creates. This needs to be done to avoid affecting the `package_hash` generated at the end
        // of this function (and because we should leave app_dir as unaffected a possible)
        let package_lock_path = context.app_dir.join("package-lock.json");
        let package_lock_contents = fs::read_to_string(&package_lock_path).ok();

        // Do the install
        node.run_sync(&[&npm, "install"])?;

        // Remove or restore `package-lock.json`
        match package_lock_contents {
            Some(contents) => fs::write(&package_lock_path, contents)?,
            None => fs::remove_file(&package_lock_path)?,
        }

        // Generate a 'package hash' to be able to tell if layer is stale in `existing_layer_strategy`
        let package_hash = NpmLayerMetadata::generate_package_hash(&context.app_dir)?;

        let mut layer_env = LayerEnv::new();

        // If a CNB build move `node_module` to `layer_path/node_modules` because
        // "Implementations MUST NOT write to any other location than layer_path".
        //
        // It feels like there should be other ways to do this, but using `--prefix` flag or
        // setting `NPM_CONFIG_PREFIX` or `NODE_PATH` didn't work. At least one other buildpack does it this way too:
        // https://github.com/paketo-buildpacks/npm-install/blob/83ebc22dde31d3f7423a215c8eb7549a180bbf35/install_build_process.go#L60-L76
        //
        // Instead of create a symlink to `node_module` in the app_dir, this prepends NODE_PATH.
        if is_cnb_build(context) {
            let app_node_modules = context.app_dir.join("node_modules");
            let layer_node_modules = layer_path.join("node_modules");
            copy_dir_all(&app_node_modules, &layer_node_modules)?;
            remove_dir_all(&app_node_modules)?;
            layer_env.insert(
                Scope::All,
                ModificationBehavior::Prepend,
                "NODE_PATH",
                layer_node_modules,
            );
        }

        LayerResultBuilder::new(NpmLayerMetadata { package_hash })
            .env(layer_env)
            .build()
    }
}
<commit_msg>fix(Node buildpack): Ensure layers are handled in correct order<commit_after>use std::{
    collections::HashMap,
    ffi::OsString,
    fs::{self, remove_dir_all},
    path::{Path, PathBuf},
};

use binary_node::{BinaryInstallation, BinaryTrait, NodeBinary};
use buildpack::{
    eyre,
    fs_utils::{copy_dir_all, symlink_dir, symlink_file},
    hash_utils::file_sha256_hex,
    is_cnb_build, is_local_build,
    libcnb::{
        self,
        build::{BuildContext, BuildResult, BuildResultBuilder},
        data::{build_plan::BuildPlan, layer_content_metadata::LayerTypes, layer_name},
        detect::{DetectContext, DetectResult, DetectResultBuilder},
        generic::{GenericMetadata, GenericPlatform},
        layer::{ExistingLayerStrategy, Layer, LayerResult, LayerResultBuilder},
        layer_env::{LayerEnv, ModificationBehavior, Scope},
        Buildpack,
    },
    tracing, BuildpackTrait, SYSTEM_INSTALLED,
};
use serde::{Deserialize, Serialize};

pub struct NodeBuildpack;

impl BuildpackTrait for NodeBuildpack {
    fn toml() -> &'static str {
        include_str!("../buildpack.toml")
    }
}

const NODE_MODULES: &str = "node_modules";
const NVMRC: &str = ".nvmrc";
const PACKAGE_JSON: &str = "package.json";
const PACKAGE_LOCK: &str = "package-lock.json";
const TOOL_VERSIONS: &str = ".tool-versions";

impl Buildpack for NodeBuildpack {
    type Platform = GenericPlatform;
    type Metadata = GenericMetadata;
    type Error = eyre::Report;

    fn detect(&self, _context: DetectContext<Self>) -> libcnb::Result<DetectResult, Self::Error> {
        // Read `.tool-versions` for Node.js version
        let tool_versions = Self::tool_versions();

        // Read `.nvmrc` for Node.js version
        let nvmrc = fs::read_to_string(NVMRC)
            .map(|content| content.trim().to_string())
            .ok();

        // Read `package.json` for Node.js version
        let package_json = fs::read_to_string(PACKAGE_JSON)
            .ok()
            .and_then(|json| serde_json::from_str::<serde_json::Value>(&json).ok());

        // Detect `package-lock.json`
        let package_lock = PathBuf::from(PACKAGE_LOCK);

        // Fail early
        if !(tool_versions.contains_key("nodejs")
            || tool_versions.contains_key("node")
            || package_json.is_some()
            || package_lock.exists()
            || nvmrc.is_some()
            || Self::any_exist(&["main.js", "index.js"]))
        {
            return DetectResultBuilder::fail().build();
        }

        let mut requires = Vec::new();
        let mut provides = Vec::new();

        // Resolve Node.js version from `.tool-versions`, `.nvmrc`, `package.json`, or installed `node` version
        let (version, source) = if let Some(version) = tool_versions
            .get("nodejs")
            .or_else(|| tool_versions.get("node"))
        {
            (version.to_string(), TOOL_VERSIONS)
        } else if let Some(versionish) = nvmrc {
            (versionish, NVMRC)
        } else if let Some(semver) = package_json.as_ref().and_then(|package| {
            package
                .pointer("/engines/node")
                .and_then(|semver| semver.as_str().map(|semver| semver.to_string()))
        }) {
            (semver, PACKAGE_JSON)
        } else if let Some(version) = (NodeBinary {}).installed_version(None) {
            (version, SYSTEM_INSTALLED)
        } else {
            ("".to_string(), "")
        };

        // Require and provide Node.js
        let (require, provide) = Self::require_and_provide(
            format!("node {}", version).trim(),
            source,
            format!("Install Node.js {}", version).trim(),
        );
        requires.push(require);
        provides.push(provide);

        // Determine how NPM packages are to be installed
        if package_lock.exists() || package_json.is_some() {
            let (require, provide) = Self::require_and_provide(
                "node_modules",
                if package_lock.exists() {
                    PACKAGE_LOCK
                } else {
                    PACKAGE_JSON
                },
                "Install Node.js packages into `node_modules`",
            );
            requires.push(require);
            provides.push(provide);
        }

        let mut build_plan = BuildPlan::new();
        build_plan.requires = requires;
        build_plan.provides = provides;
        DetectResultBuilder::pass().build_plan(build_plan).build()
    }

    fn build(&self, context: BuildContext<Self>) -> libcnb::Result<BuildResult, Self::Error> {
        let entries: HashMap<_, _> = context
            .buildpack_plan
            .entries
            .iter()
            .map(|entry| Self::split_entry_name(&entry.name))
            .collect();

        if let Some(args) = entries.get("node") {
            context.handle_layer(layer_name!("node"), NodeLayer::new(args.clone()))?;
        }

        if entries.contains_key("node_modules") {
            context.handle_layer(layer_name!("node_modules"), NodeModulesLayer)?;
        }

        BuildResultBuilder::new().build()
    }
}

struct NodeLayer {
    // The semantic version requirement for the Node.js binary
    requirement: String,
}

impl NodeLayer {
    fn new(args: Vec<String>) -> Self {
        // Join args with commas because semver requirement parser expects that is
        // how parts of a requirement are separated
        let versionish = args.join(",");

        // Determine the semver requirement from versionish which could be a well formed semantic version (e.g 14.0.1),
        // a semver requirement (e.g. ^14.0), or an alias (e.g. `lts`)
        let requirement = if let Some(version) = versionish.strip_prefix('v') {
            version.to_string()
        } else if versionish == "lts" {
            // TODO: Determine LTS without doing a fetch, perhaps based on date
            // https://nodejs.org/en/about/releases/
            "^16".to_string()
        } else {
            versionish.clone()
        };

        NodeLayer { requirement }
    }
}

impl Layer for NodeLayer {
    type Buildpack = NodeBuildpack;
    type Metadata = GenericMetadata;

    fn types(&self) -> LayerTypes {
        LayerTypes {
            build: true,
            launch: true,
            cache: true,
        }
    }

    fn existing_layer_strategy(
        &self,
        _context: &BuildContext<Self::Buildpack>,
        layer_data: &libcnb::layer::LayerData<Self::Metadata>,
    ) -> Result<libcnb::layer::ExistingLayerStrategy, <Self::Buildpack as Buildpack>::Error> {
        let installed = NodeBinary {}.installed_at(
            &layer_data.path.join("bin").join("node"),
            Some(self.requirement.clone()),
        )?;
        let strategy = if installed {
            tracing::info!(
                "Existing `node` layer has `./bin/node` matching semver requirement `{}`; will keep",
                self.requirement
            );
            ExistingLayerStrategy::Keep
        } else {
            tracing::info!(
                "Existing `node` layer does not have `./bin/node` matching semver requirement `{}`; will recreate",
                self.requirement
            );
            ExistingLayerStrategy::Recreate
        };
        Ok(strategy)
    }

    fn create(
        &self,
        context: &BuildContext<Self::Buildpack>,
        layer_path: &Path,
    ) -> Result<LayerResult<Self::Metadata>, eyre::Report> {
        tracing::info!(
            "Creating `node` layer with semver requirement `{}`",
            self.requirement
        );

        let node = NodeBinary {}.require_sync(Some(self.requirement.clone()), true)?;
        let version = node.version()?;

        if is_local_build(context) {
            if node.is_stencila_install() {
                tracing::info!("Linking to `node {}` installed by Stencila", version);
                let source = node.grandparent()?;

                symlink_dir(source.join("bin"), &layer_path.join("bin"))?;
                symlink_dir(source.join("lib"), &layer_path.join("lib"))?;
            } else {
                tracing::info!("Linking to `node {}` installed on system", version);
                let source = node.parent()?;

                let bin_path = layer_path.join("bin");
                fs::create_dir_all(&bin_path)?;
                symlink_file(node.path, bin_path.join(node.name))?;
                symlink_file(source.join("corepack"), bin_path.join("corepack"))?;
                symlink_file(source.join("npm"), bin_path.join("npm"))?;
                symlink_file(source.join("npx"), bin_path.join("npx"))?;

                let lib_path = layer_path.join("lib");
                fs::create_dir_all(&lib_path)?;
                symlink_dir(
                    source.join("..").join("lib").join(NODE_MODULES),
                    lib_path.join(NODE_MODULES),
                )?;
            }
        } else {
            #[allow(clippy::collapsible_else_if)]
            if node.is_stencila_install() {
                tracing::info!("Using `node {}` installed by Stencila", version);
                let source = node.grandparent()?;

                copy_dir_all(source, layer_path)?;
            } else {
                tracing::info!("Using `node {}` installed on stack image", version);
            }
        }

        LayerResultBuilder::new(GenericMetadata::default()).build()
    }
}

struct NodeModulesLayer;

#[derive(Clone, Serialize, Deserialize)]
struct NodeModulesLayerMetadata {
    package_hash: String,
}

impl NodeModulesLayerMetadata {
    /// Generate `package_hash` string for an app directory
    ///
    /// If the directory has a `package-lock.json` that will be used. If not
    /// `package.json` will be used. This means that if the user removes
    /// or updates `package-lock.json` the layer will be updated. If there is no lock file
    /// then the layer will only be updated if there are changes in `package.json`.
    fn generate_package_hash(app_dir: &Path) -> Result<String, eyre::Report> {
        file_sha256_hex(app_dir.join(PACKAGE_LOCK))
            .or_else(|_| file_sha256_hex(app_dir.join(PACKAGE_JSON)))
    }
}

impl Layer for NodeModulesLayer {
    type Buildpack = NodeBuildpack;
    type Metadata = NodeModulesLayerMetadata;

    fn types(&self) -> LayerTypes {
        LayerTypes {
            build: true,
            launch: true,
            cache: true,
        }
    }

    fn existing_layer_strategy(
        &self,
        context: &BuildContext<Self::Buildpack>,
        layer_data: &libcnb::layer::LayerData<Self::Metadata>,
    ) -> Result<libcnb::layer::ExistingLayerStrategy, <Self::Buildpack as Buildpack>::Error> {
        let package_hash =
            NodeModulesLayerMetadata::generate_package_hash(&context.app_dir).unwrap_or_default();
        let strategy = if package_hash == layer_data.content_metadata.metadata.package_hash {
            tracing::info!("Existing `node_modules` layer has same package hash; will keep",);
            ExistingLayerStrategy::Keep
        } else {
            tracing::info!("Existing `node_modules` layer has different package hash; will update");
            ExistingLayerStrategy::Update
        };
        Ok(strategy)
    }

    fn create(
        &self,
        context: &BuildContext<Self::Buildpack>,
        layer_path: &Path,
    ) -> Result<LayerResult<Self::Metadata>, eyre::Report> {
        tracing::info!("Creating `node_modules` layer");
        self.install(context, layer_path)
    }

    fn update(
        &self,
        context: &BuildContext<Self::Buildpack>,
        layer_data: &libcnb::layer::LayerData<Self::Metadata>,
    ) -> Result<LayerResult<Self::Metadata>, <Self::Buildpack as Buildpack>::Error> {
        tracing::info!("Updating `node_modules` layer");
        self.install(context, &layer_data.path)
    }
}

impl NodeModulesLayer {
    fn install(
        &self,
        context: &BuildContext<NodeBuildpack>,
        layer_path: &Path,
    ) -> Result<LayerResult<NodeModulesLayerMetadata>, eyre::Report> {
        // Use `node` and `npm` installed in the `node` layer
        let node_layer = layer_path
            .canonicalize()?
            .parent()
            .expect("Should have parent")
            .join("node");

        // Prepend to the PATH because some package install scripts
        // use `/usr/bin/env node` which looks for `node` on PATH
        let mut envs: Vec<(OsString, OsString)> = vec![(
            "PATH".into(),
            NodeBuildpack::prepend_path(&node_layer.join("bin"))?,
        )];

        // If this is a CNB build use `layer_path/cache` as the NPM cache
        if is_cnb_build(context) {
            envs.push(("NPM_CONFIG_CACHE".into(), layer_path.join("cache").into()));
        }

        // Call the `npm-cli.js` script installed in the `node` layer
        // This is done, rather than executing `bin/npm` directly there are issues with node `require`
        // module resolution when the latter is used.
        let node = BinaryInstallation {
            name: "node".into(),
            path: node_layer.join("bin").join("node"),
            env: envs,
            ..Default::default()
        };
        let npm = node_layer
            .join("lib")
            .join(NODE_MODULES)
            .join("npm")
            .join("bin")
            .join("npm-cli.js")
            .display()
            .to_string();

        // Read any `package-lock.json` so that we can restore it, or remove the new one that
        // install creates. This needs to be done to avoid affecting the `package_hash` generated at the end
        // of this function (and because we should leave app_dir untouched)
        let package_lock_path = context.app_dir.join(PACKAGE_LOCK);
        let package_lock_contents = fs::read_to_string(&package_lock_path).ok();

        // Do the install
        node.run_sync(&[&npm, "install"])?;

        // Remove or restore `package-lock.json`
        match package_lock_contents {
            Some(contents) => fs::write(&package_lock_path, contents)?,
            None => {
                if package_lock_path.exists() {
                    fs::remove_file(&package_lock_path)?
                }
            }
        }

        // Generate a 'package hash' to be able to tell if layer is stale in `existing_layer_strategy`
        let package_hash = NodeModulesLayerMetadata::generate_package_hash(&context.app_dir)?;

        let mut layer_env = LayerEnv::new();

        // If this is a CNB build move `node_modules` to `layer_path/node_modules` because
        // "Implementations MUST NOT write to any other location than layer_path".
        //
        // It feels like there should be other ways to do this, but using `--prefix` flag or
        // setting `NPM_CONFIG_PREFIX` or `NODE_PATH` didn't work. At least one other buildpack does it this way too:
        // https://github.com/paketo-buildpacks/npm-install/blob/83ebc22dde31d3f7423a215c8eb7549a180bbf35/install_build_process.go#L60-L76
        //
        // Instead of creating a symlink to `node_modules` in the app_dir, add it to NODE_PATH.
        if is_cnb_build(context) {
            let app_node_modules = context.app_dir.join(NODE_MODULES);
            let layer_node_modules = layer_path.join(NODE_MODULES);
            copy_dir_all(&app_node_modules, &layer_node_modules)?;
            remove_dir_all(&app_node_modules)?;
            layer_env.insert(
                Scope::All,
                ModificationBehavior::Prepend,
                "NODE_PATH",
                layer_node_modules,
            );
        }

        LayerResultBuilder::new(NodeModulesLayerMetadata { package_hash })
            .env(layer_env)
            .build()
    }
}
<|endoftext|>


<commit_before>use alloc::borrow::Cow;
use alloc::vec;
use alloc::vec::Vec;

#[derive(Debug, PartialEq)]
pub struct ObjectIdentifier<'a> {
    // Store the OID as DER encoded. This means we can 0-copy on parse.
    der_encoded: Cow<'a, [u8]>,
}

fn _read_base128_int<I: Iterator<Item = u8>>(mut reader: I) -> Option<u32> {
    let mut ret = 0u32;
    for _ in 0..4 {
        let b = reader.next()?;
        ret <<= 7;
        ret |= u32::from(b & 0x7f);
        if b & 0x80 == 0 {
            return Some(ret);
        }
    }
    None
}

fn _write_base128_int(data: &mut Vec<u8>, n: u32) {
    if n == 0 {
        data.push(0);
        return;
    }

    let mut l = 0;
    let mut i = n;
    while i > 0 {
        l += 1;
        i >>= 7;
    }

    for i in (0..l).rev() {
        let mut o = (n >> (i * 7)) as u8;
        o &= 0x7f;
        if i != 0 {
            o |= 0x80;
        }
        data.push(o);
    }
}

impl<'a> ObjectIdentifier<'a> {
    pub fn from_string(oid: &str) -> Option<ObjectIdentifier<'a>> {
        let mut parts = oid.split('.');

        let first = parts.next()?.parse::<u32>().ok()?;
        let second = parts.next()?.parse::<u32>().ok()?;
        if first > 2 || (first < 2 && second >= 40) {
            return None;
        }

        let mut der_data = vec![];
        _write_base128_int(&mut der_data, 40 * first + second);
        for part in parts {
            _write_base128_int(&mut der_data, part.parse::<u32>().ok()?);
        }
        Some(ObjectIdentifier {
            der_encoded: Cow::Owned(der_data),
        })
    }

    pub(crate) fn from_der(data: &'a [u8]) -> Option<ObjectIdentifier<'a>> {
        if data.is_empty() {
            return None;
        }
        let mut cursor = data.iter().copied();
        while cursor.len() > 0 {
            _read_base128_int(&mut cursor)?;
        }

        Some(ObjectIdentifier {
            der_encoded: Cow::Borrowed(data),
        })
    }
}

#[cfg(test)]
mod tests {
    use crate::ObjectIdentifier;

    #[test]
    fn test_object_identitifier_from_string() {
        for val in &[
            "",
            "1",
            "3.10",
            "1.50",
            "2.12.a3.4",
            "a.4",
            "1.a",
            ".2.5",
            "2..5",
            "2.5.",
        ] {
            assert_eq!(ObjectIdentifier::from_string(val), None);
        }

        for val in &[
            "2.5",
            "2.5.2",
            "1.2.840.113549",
            "1.2.3.4",
            "1.2.840.133549.1.1.5",
            "2.100.3",
        ] {
            assert!(ObjectIdentifier::from_string(val).is_some());
        }
    }
}
<commit_msg>Document ObjectIdentifier (#56)<commit_after>use alloc::borrow::Cow;
use alloc::vec;
use alloc::vec::Vec;

/// Represents an ASN.1 `OBJECT IDENTIFIER`. ObjectIdentifiers are opaque, the only thing may be
/// done with them is test if they are equal to another `ObjectIdentifier`. The generally
/// recommended practice for handling them is to create some `ObjectIdentifier` constants with
/// `ObjectIdentifier::from_string` and then compare ObjectIdentifiers you get from parsing to
/// those.
#[derive(Debug, PartialEq)]
pub struct ObjectIdentifier<'a> {
    // Store the OID as DER encoded. This means we can 0-copy on parse.
    der_encoded: Cow<'a, [u8]>,
}

fn _read_base128_int<I: Iterator<Item = u8>>(mut reader: I) -> Option<u32> {
    let mut ret = 0u32;
    for _ in 0..4 {
        let b = reader.next()?;
        ret <<= 7;
        ret |= u32::from(b & 0x7f);
        if b & 0x80 == 0 {
            return Some(ret);
        }
    }
    None
}

fn _write_base128_int(data: &mut Vec<u8>, n: u32) {
    if n == 0 {
        data.push(0);
        return;
    }

    let mut l = 0;
    let mut i = n;
    while i > 0 {
        l += 1;
        i >>= 7;
    }

    for i in (0..l).rev() {
        let mut o = (n >> (i * 7)) as u8;
        o &= 0x7f;
        if i != 0 {
            o |= 0x80;
        }
        data.push(o);
    }
}

impl<'a> ObjectIdentifier<'a> {
    /// Parses an OID from a dotted string, e.g. `"1.2.840.113549"`.
    pub fn from_string(oid: &str) -> Option<ObjectIdentifier<'a>> {
        let mut parts = oid.split('.');

        let first = parts.next()?.parse::<u32>().ok()?;
        let second = parts.next()?.parse::<u32>().ok()?;
        if first > 2 || (first < 2 && second >= 40) {
            return None;
        }

        let mut der_data = vec![];
        _write_base128_int(&mut der_data, 40 * first + second);
        for part in parts {
            _write_base128_int(&mut der_data, part.parse::<u32>().ok()?);
        }
        Some(ObjectIdentifier {
            der_encoded: Cow::Owned(der_data),
        })
    }

    pub(crate) fn from_der(data: &'a [u8]) -> Option<ObjectIdentifier<'a>> {
        if data.is_empty() {
            return None;
        }
        let mut cursor = data.iter().copied();
        while cursor.len() > 0 {
            _read_base128_int(&mut cursor)?;
        }

        Some(ObjectIdentifier {
            der_encoded: Cow::Borrowed(data),
        })
    }
}

#[cfg(test)]
mod tests {
    use crate::ObjectIdentifier;

    #[test]
    fn test_object_identitifier_from_string() {
        for val in &[
            "",
            "1",
            "3.10",
            "1.50",
            "2.12.a3.4",
            "a.4",
            "1.a",
            ".2.5",
            "2..5",
            "2.5.",
        ] {
            assert_eq!(ObjectIdentifier::from_string(val), None);
        }

        for val in &[
            "2.5",
            "2.5.2",
            "1.2.840.113549",
            "1.2.3.4",
            "1.2.840.133549.1.1.5",
            "2.100.3",
        ] {
            assert!(ObjectIdentifier::from_string(val).is_some());
        }
    }
}
<|endoftext|>


<commit_before>use std::ffi::{CStr, CString};
use std::str::Utf8Error;
use std::mem;

use alpm_sys::*;
use pgp::SigLevel;
use libc::{self, c_char};

use {Alpm, AlpmResult, Error, PackageRef, Group};
use util::{self, alpm_list_to_vec, vec_to_alpm_list, str_to_unowned_char_array,
    cstring_to_owned_char_array};

/// A database of packages. This is only ever available as a reference
#[derive(Debug)]
pub struct Db<'a> {
    inner: *const Struct_alpm_db,
    // we need this handle so we can get error codes
    handle: &'a Alpm,
}

impl<'a> Db<'a> {

    pub(crate) fn new(inner: *const Struct_alpm_db, handle: &'a Alpm) -> Db<'a> {
        Db {
            inner: inner,
            handle: handle,
        }
    }

    /// Gets the name of the database.
    pub fn name(&self) -> Result<&'a str, Utf8Error> {
        unsafe {
            CStr::from_ptr(alpm_db_get_name(self.inner)).to_str()
        }
    }

    /// Gets the signature checking level of the database.
    pub fn siglevel(&self) -> SigLevel {
        unsafe { alpm_db_get_siglevel(self.inner).into() }
    }

    /// Checks the database is valid. If not, an error
    pub fn is_valid(&self) -> AlpmResult<()> {
        if unsafe { alpm_db_get_valid(self.inner) == 0 } {
            Ok(())
        } else {
            Err(self.handle.error().unwrap_or(Error::__Unknown))
        }
    }

    /// Gets a list of the servers used by the database.
    pub fn servers(&self) -> Vec<&str> {
        unsafe {
            let servers = alpm_db_get_servers(self.inner);
            alpm_list_to_vec(servers, |char_ptr| {
                CStr::from_ptr(char_ptr as *const c_char).to_str().unwrap()
            })
        }
    }

    /// Sets the servers used by the database.
    pub fn set_servers<R>(&self, servers: Vec<R>) -> AlpmResult<()>
        where R: AsRef<str>
    {
        unsafe {
            let list = vec_to_alpm_list(servers, |s| unsafe {
                str_to_unowned_char_array(&s.as_ref())
            });
            let res = alpm_db_set_servers(self.inner, list);
            if res == 0 {
                Ok(())
            } else {
                Err(self.handle.error().unwrap_or(Error::__Unknown))
            }
        }
    }

    /// Adds a server to the list of servers used by the database.
    pub fn add_server(&self, url: &str) -> AlpmResult<()> {
        let url = CString::new(url)?;
        if unsafe { alpm_db_add_server(self.inner, url.as_ptr()) } == 0 {
            Ok(())
        } else {
            Err(self.handle.error().unwrap_or(Error::__Unknown))
        }
    }

    /// Removes a server from the list of servers used by the database.
    pub fn remove_server(&self, url: &str) -> AlpmResult<()> {
        let url = CString::new(url)?;
        if unsafe { alpm_db_remove_server(self.inner, url.as_ptr()) } == 0 {
            Ok(())
        } else {
            Err(self.handle.error().unwrap_or(Error::__Unknown))
        }
    }

    /// Update (sync) the database with remote.
    ///
    /// If force is set to `false` and the database is up to date the function will return
    /// successfully without doing anything.
    pub fn update(&self, force: bool) -> AlpmResult<()> {
        let force = if force { 1 } else { 0 };
        if unsafe { alpm_db_update(force, self.inner) } == 0 {
            Ok(())
        } else {
            if let Some(err) = self.handle.error() {
                Err(err)
            } else {
                // Update not needed (and force == false)
                Ok(())
            }
        }
    }

    /// Gets a package with the given name from the database
    pub fn pkg(&self, name: &str) -> AlpmResult<&'a PackageRef> {
        let name = CString::new(name)?;
        let pkg_ptr = unsafe { alpm_db_get_pkg(self.inner, name.as_ptr()) };
        if pkg_ptr.is_null() {
            Err(self.handle.error().unwrap_or(Error::__Unknown))
        } else {
            //println!("{:?}", pkg_ptr);
            Ok( unsafe { PackageRef::new(pkg_ptr) })
        }
    }

    /// Gets all packages in the db cache
    pub fn pkg_cache(&self) -> Vec<&PackageRef> {
        unsafe {
            let cache_ptr = alpm_db_get_pkgcache(self.inner);
            alpm_list_to_vec(cache_ptr, |pkg_ptr| {
                &*(pkg_ptr as *const PackageRef)
            })
        }
    }

    /// Gets a package group from the database by name.
    pub fn group(&self, name: &str) -> AlpmResult<Group<'a>> {
        unsafe {
            let name = CString::new(name).unwrap();
            let group_ptr = alpm_db_get_group(self.inner, name.as_ptr());
            if group_ptr.is_null() {
                Err(self.handle.error().unwrap_or(Error::__Unknown))
            } else {
                let group_ptr = group_ptr as *const alpm_group_t;
                Ok(Group {
                    name: CStr::from_ptr((*group_ptr).name).to_str().unwrap(),
                    packages: alpm_list_to_vec((*group_ptr).packages, |pkg_ptr| {
                        &*(pkg_ptr as *const PackageRef)
                    }),
                })
            }
        }
    }

    /// Gets the package group cache of the database.
    pub fn group_cache(&self) -> AlpmResult<Vec<Group<'a>>> {
        unsafe {
            let group_cache = alpm_db_get_groupcache(self.inner);
            if group_cache.is_null() {
                Err(self.handle.error().unwrap_or(Error::__Unknown))
            } else {
                Ok(alpm_list_to_vec(group_cache, |group_ptr| {
                    let group_ptr = group_ptr as *const alpm_group_t;
                    Group {
                        name: CStr::from_ptr((*group_ptr).name).to_str().unwrap(),
                        packages: alpm_list_to_vec((*group_ptr).packages, |pkg_ptr| {
                            &*(pkg_ptr as *const PackageRef)
                        }),
                    }
                }))
            }
        }
    }

    /// Searches the database for packages matching the needles.
    ///
    /// This function has a memory leak, but I'm 99% sure it's internal to libalpm. Needs more
    /// testing.
    pub fn search(&self, needles: Vec<&str>) -> AlpmResult<Vec<&PackageRef>> {
        let needles_outer: Vec<CString> = needles.iter()
            .map(|s| CString::new(*s).unwrap())
            .collect();
        println!("{:?}", needles_outer);
        unsafe {
            let needles = util::vec_as_alpm_list(&needles_outer, cstring_to_owned_char_array);
            let pkgs = alpm_db_search(self.inner, needles);
            alpm_list_free(needles);
            if ! pkgs.is_null() {
                Ok(alpm_list_to_vec(pkgs, |pkg_ptr| &*(pkg_ptr as *mut PackageRef )))
            } else {
                Err(self.handle.error().unwrap_or(Error::__Unknown))
            }
        }
    }

    /// Sets what this database is to be used for.
    pub fn set_usage(&self, usage: Usage) -> AlpmResult<()> {
        if unsafe { alpm_db_set_usage(self.inner, usage.into()) } == 0 {
            Ok(())
        } else {
            Err(self.handle.error().unwrap_or(Error::__Unknown))
        }
    }

    /// Gets what this database is to be used for.
    pub fn usage(&self) -> AlpmResult<Usage> {
        unsafe {
            let usage: u32 = mem::zeroed();
            if alpm_db_get_usage(self.inner, &usage) == 0 {
                Ok(usage.into())
            } else {
                Err(self.handle.error().unwrap_or(Error::__Unknown))
            }
        }
    }
}

/// A struct to say what to use a given database for
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Usage {
    pub sync: bool,
    pub search: bool,
    pub install: bool,
    pub upgrade: bool,
}

impl Usage {
    #[inline(always)]
    pub fn sync() -> Usage { Usage { sync: true, ..Default::default() } }
    #[inline(always)]
    pub fn search() -> Usage { Usage { search: true, ..Default::default() } }
    #[inline(always)]
    pub fn install() -> Usage { Usage { install: true, ..Default::default() } }
    #[inline(always)]
    pub fn upgrade() -> Usage { Usage { upgrade: true, ..Default::default() } }
    #[inline(always)]
    pub fn all() -> Usage {
        Usage {
            sync: true,
            search: true,
            install: true,
            upgrade: true,
        }
    }
}

impl Default for Usage {
    fn default() -> Usage {
        Usage {
            sync: false,
            search: false,
            install: false,
            upgrade: false,
        }
    }
}

impl Into<u32> for Usage {
    fn into(self) -> u32 {
        let mut acc = 0;
        if self.sync {
            acc |= ALPM_DB_USAGE_SYNC;
        };
        if self.search {
            acc |= ALPM_DB_USAGE_SEARCH;
        };
        if self.install {
            acc |= ALPM_DB_USAGE_INSTALL;
        };
        if self.upgrade {
            acc |= ALPM_DB_USAGE_UPGRADE;
        };
        acc
    }
}

impl From<u32> for Usage {
    fn from(from: u32) -> Self {
        Usage {
            sync: from & ALPM_DB_USAGE_SYNC != 0,
            search: from & ALPM_DB_USAGE_SEARCH != 0,
            install: from & ALPM_DB_USAGE_INSTALL != 0,
            upgrade: from & ALPM_DB_USAGE_UPGRADE != 0,
        }
    }
}
<commit_msg>fix memory leak in Db.search<commit_after>use std::ffi::{CStr, CString};
use std::str::Utf8Error;
use std::mem;

use alpm_sys::*;
use pgp::SigLevel;
use libc::{self, c_char};

use {Alpm, AlpmResult, Error, PackageRef, Group};
use util::{self, alpm_list_to_vec, vec_to_alpm_list, str_to_unowned_char_array,
    cstring_to_owned_char_array};

/// A database of packages. This is only ever available as a reference
#[derive(Debug)]
pub struct Db<'a> {
    inner: *const Struct_alpm_db,
    // we need this handle so we can get error codes
    handle: &'a Alpm,
}

impl<'a> Db<'a> {

    pub(crate) fn new(inner: *const Struct_alpm_db, handle: &'a Alpm) -> Db<'a> {
        Db {
            inner: inner,
            handle: handle,
        }
    }

    /// Gets the name of the database.
    pub fn name(&self) -> Result<&'a str, Utf8Error> {
        unsafe {
            CStr::from_ptr(alpm_db_get_name(self.inner)).to_str()
        }
    }

    /// Gets the signature checking level of the database.
    pub fn siglevel(&self) -> SigLevel {
        unsafe { alpm_db_get_siglevel(self.inner).into() }
    }

    /// Checks the database is valid. If not, an error
    pub fn is_valid(&self) -> AlpmResult<()> {
        if unsafe { alpm_db_get_valid(self.inner) == 0 } {
            Ok(())
        } else {
            Err(self.handle.error().unwrap_or(Error::__Unknown))
        }
    }

    /// Gets a list of the servers used by the database.
    pub fn servers(&self) -> Vec<&str> {
        unsafe {
            let servers = alpm_db_get_servers(self.inner);
            alpm_list_to_vec(servers, |char_ptr| {
                CStr::from_ptr(char_ptr as *const c_char).to_str().unwrap()
            })
        }
    }

    /// Sets the servers used by the database.
    pub fn set_servers<R>(&self, servers: Vec<R>) -> AlpmResult<()>
        where R: AsRef<str>
    {
        unsafe {
            let list = vec_to_alpm_list(servers, |s| unsafe {
                str_to_unowned_char_array(&s.as_ref())
            });
            let res = alpm_db_set_servers(self.inner, list);
            if res == 0 {
                Ok(())
            } else {
                Err(self.handle.error().unwrap_or(Error::__Unknown))
            }
        }
    }

    /// Adds a server to the list of servers used by the database.
    pub fn add_server(&self, url: &str) -> AlpmResult<()> {
        let url = CString::new(url)?;
        if unsafe { alpm_db_add_server(self.inner, url.as_ptr()) } == 0 {
            Ok(())
        } else {
            Err(self.handle.error().unwrap_or(Error::__Unknown))
        }
    }

    /// Removes a server from the list of servers used by the database.
    pub fn remove_server(&self, url: &str) -> AlpmResult<()> {
        let url = CString::new(url)?;
        if unsafe { alpm_db_remove_server(self.inner, url.as_ptr()) } == 0 {
            Ok(())
        } else {
            Err(self.handle.error().unwrap_or(Error::__Unknown))
        }
    }

    /// Update (sync) the database with remote.
    ///
    /// If force is set to `false` and the database is up to date the function will return
    /// successfully without doing anything.
    pub fn update(&self, force: bool) -> AlpmResult<()> {
        let force = if force { 1 } else { 0 };
        if unsafe { alpm_db_update(force, self.inner) } == 0 {
            Ok(())
        } else {
            if let Some(err) = self.handle.error() {
                Err(err)
            } else {
                // Update not needed (and force == false)
                Ok(())
            }
        }
    }

    /// Gets a package with the given name from the database
    pub fn pkg(&self, name: &str) -> AlpmResult<&'a PackageRef> {
        let name = CString::new(name)?;
        let pkg_ptr = unsafe { alpm_db_get_pkg(self.inner, name.as_ptr()) };
        if pkg_ptr.is_null() {
            Err(self.handle.error().unwrap_or(Error::__Unknown))
        } else {
            //println!("{:?}", pkg_ptr);
            Ok( unsafe { PackageRef::new(pkg_ptr) })
        }
    }

    /// Gets all packages in the db cache
    pub fn pkg_cache(&self) -> Vec<&PackageRef> {
        unsafe {
            let cache_ptr = alpm_db_get_pkgcache(self.inner);
            alpm_list_to_vec(cache_ptr, |pkg_ptr| {
                &*(pkg_ptr as *const PackageRef)
            })
        }
    }

    /// Gets a package group from the database by name.
    pub fn group(&self, name: &str) -> AlpmResult<Group<'a>> {
        unsafe {
            let name = CString::new(name).unwrap();
            let group_ptr = alpm_db_get_group(self.inner, name.as_ptr());
            if group_ptr.is_null() {
                Err(self.handle.error().unwrap_or(Error::__Unknown))
            } else {
                let group_ptr = group_ptr as *const alpm_group_t;
                Ok(Group {
                    name: CStr::from_ptr((*group_ptr).name).to_str().unwrap(),
                    packages: alpm_list_to_vec((*group_ptr).packages, |pkg_ptr| {
                        &*(pkg_ptr as *const PackageRef)
                    }),
                })
            }
        }
    }

    /// Gets the package group cache of the database.
    pub fn group_cache(&self) -> AlpmResult<Vec<Group<'a>>> {
        unsafe {
            let group_cache = alpm_db_get_groupcache(self.inner);
            if group_cache.is_null() {
                Err(self.handle.error().unwrap_or(Error::__Unknown))
            } else {
                Ok(alpm_list_to_vec(group_cache, |group_ptr| {
                    let group_ptr = group_ptr as *const alpm_group_t;
                    Group {
                        name: CStr::from_ptr((*group_ptr).name).to_str().unwrap(),
                        packages: alpm_list_to_vec((*group_ptr).packages, |pkg_ptr| {
                            &*(pkg_ptr as *const PackageRef)
                        }),
                    }
                }))
            }
        }
    }

    /// Searches the database for packages matching the needles.
    pub fn search(&self, needles: Vec<&str>) -> AlpmResult<Vec<&PackageRef>> {
        let needles_outer: Vec<CString> = needles.iter()
            .map(|s| CString::new(*s).unwrap())
            .collect();
        println!("{:?}", needles_outer);
        unsafe {
            let needles = util::vec_as_alpm_list(&needles_outer, cstring_to_owned_char_array);
            let pkgs = alpm_db_search(self.inner, needles);
            alpm_list_free(needles);
            if ! pkgs.is_null() {
                let pkgs_vec = alpm_list_to_vec(pkgs, |pkg_ptr| &*(pkg_ptr as *mut PackageRef ));
                alpm_list_free(pkgs);
                Ok(pkgs_vec)
            } else {
                Err(self.handle.error().unwrap_or(Error::__Unknown))
            }
        }
    }

    /// Sets what this database is to be used for.
    pub fn set_usage(&self, usage: Usage) -> AlpmResult<()> {
        if unsafe { alpm_db_set_usage(self.inner, usage.into()) } == 0 {
            Ok(())
        } else {
            Err(self.handle.error().unwrap_or(Error::__Unknown))
        }
    }

    /// Gets what this database is to be used for.
    pub fn usage(&self) -> AlpmResult<Usage> {
        unsafe {
            let usage: u32 = mem::zeroed();
            if alpm_db_get_usage(self.inner, &usage) == 0 {
                Ok(usage.into())
            } else {
                Err(self.handle.error().unwrap_or(Error::__Unknown))
            }
        }
    }
}

/// A struct to say what to use a given database for
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Usage {
    pub sync: bool,
    pub search: bool,
    pub install: bool,
    pub upgrade: bool,
}

impl Usage {
    #[inline(always)]
    pub fn sync() -> Usage { Usage { sync: true, ..Default::default() } }
    #[inline(always)]
    pub fn search() -> Usage { Usage { search: true, ..Default::default() } }
    #[inline(always)]
    pub fn install() -> Usage { Usage { install: true, ..Default::default() } }
    #[inline(always)]
    pub fn upgrade() -> Usage { Usage { upgrade: true, ..Default::default() } }
    #[inline(always)]
    pub fn all() -> Usage {
        Usage {
            sync: true,
            search: true,
            install: true,
            upgrade: true,
        }
    }
}

impl Default for Usage {
    fn default() -> Usage {
        Usage {
            sync: false,
            search: false,
            install: false,
            upgrade: false,
        }
    }
}

impl Into<u32> for Usage {
    fn into(self) -> u32 {
        let mut acc = 0;
        if self.sync {
            acc |= ALPM_DB_USAGE_SYNC;
        };
        if self.search {
            acc |= ALPM_DB_USAGE_SEARCH;
        };
        if self.install {
            acc |= ALPM_DB_USAGE_INSTALL;
        };
        if self.upgrade {
            acc |= ALPM_DB_USAGE_UPGRADE;
        };
        acc
    }
}

impl From<u32> for Usage {
    fn from(from: u32) -> Self {
        Usage {
            sync: from & ALPM_DB_USAGE_SYNC != 0,
            search: from & ALPM_DB_USAGE_SEARCH != 0,
            install: from & ALPM_DB_USAGE_INSTALL != 0,
            upgrade: from & ALPM_DB_USAGE_UPGRADE != 0,
        }
    }
}
<|endoftext|>


<commit_before>use engine::{IntoMap, Tile};
use util::units::{Point, Size};

use std::path::AsPath;
use std::fs::File;
use std::io::Read;

/// A map.
pub struct Map {
    pub tiles: Vec<Vec<Tile>>,
    pub size: Size,
}

impl Map {

    /// Creates a new map.
    ///
    /// This method takes a type which implements the `IntoMap` trait to create
    /// the new map.
    ///
    /// # Examples
    ///
    /// From a 2D vector of Tiles:
    ///
    /// ```
    /// use rslike::engine::Map;
    ///
    /// let tiles = vec![vec![]];
    /// let map = Map::new(tiles);
    /// ```
    ///
    /// From a String:
    ///
    /// ```
    /// use rslike::engine::Map;
    ///
    /// let mapstr = "###\n# #\n###".to_string();
    /// let map = Map::new(mapstr);
    /// ```
    pub fn new<T>(maplike: T) -> Map where T: IntoMap {
        maplike.as_map().unwrap()
    }

    pub fn from_file<T>(path: T) -> Map where T: AsPath {
        let mut level_file = File::open(&path).ok().expect("Could not find level file");
        let mut level_string = String::new();

        level_file.read_to_string(&mut level_string).ok().expect("Could not read from level file");

        Map::new(level_string)
    }

    pub fn at(&self, loc: Point) -> Tile {
        self.tiles[loc.y as usize][loc.x as usize]
    }

    pub fn is_walkable(&self, loc: Point) -> bool {
        self.at(loc).is_walkable()
    }

    pub fn set_tile(&mut self, loc: Point, tile: Tile) {
        self.tiles[loc.y as usize][loc.x as usize] = tile;
    }

    pub fn height(&self) -> i32 {
        self.tiles.len() as i32
    }

    pub fn width(&self) -> i32 {
        self.tiles[0].len() as i32
    }

}
<commit_msg>Minor formatting change in map.rs<commit_after>use engine::{IntoMap, Tile};
use util::units::{Point, Size};

use std::path::AsPath;
use std::fs::File;
use std::io::Read;

/// A map.
pub struct Map {
    pub tiles: Vec<Vec<Tile>>,
    pub size: Size,
}

impl Map {

    /// Creates a new map.
    ///
    /// This method takes a type which implements the `IntoMap` trait to create
    /// the new map.
    ///
    /// # Examples
    ///
    /// From a 2D vector of Tiles:
    ///
    /// ```
    /// use rslike::engine::Map;
    ///
    /// let tiles = vec![vec![]];
    /// let map = Map::new(tiles);
    /// ```
    ///
    /// From a String:
    ///
    /// ```
    /// use rslike::engine::Map;
    ///
    /// let mapstr = "###\n# #\n###".to_string();
    /// let map = Map::new(mapstr);
    /// ```
    pub fn new<T>(maplike: T) -> Map where T: IntoMap {
        maplike.as_map().unwrap()
    }

    pub fn from_file<T>(path: T) -> Map where T: AsPath {
        let mut level_file = File::open(&path).ok().expect("Could not find level file");

        let mut level_string = String::new();
        level_file.read_to_string(&mut level_string).ok().expect("Could not read from level file");

        Map::new(level_string)
    }

    pub fn at(&self, loc: Point) -> Tile {
        self.tiles[loc.y as usize][loc.x as usize]
    }

    pub fn is_walkable(&self, loc: Point) -> bool {
        self.at(loc).is_walkable()
    }

    pub fn set_tile(&mut self, loc: Point, tile: Tile) {
        self.tiles[loc.y as usize][loc.x as usize] = tile;
    }

    pub fn height(&self) -> i32 {
        self.tiles.len() as i32
    }

    pub fn width(&self) -> i32 {
        self.tiles[0].len() as i32
    }

}
<|endoftext|>