<commit_before>//! Details of the `ruma_api` procedural macro.

use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{
    parse::{Parse, ParseStream},
    Token, Type,
};

pub(crate) mod attribute;
pub(crate) mod metadata;
pub(crate) mod request;
pub(crate) mod response;

use self::{metadata::Metadata, request::Request, response::Response};
use crate::util;

/// The result of processing the `ruma_api` macro, ready for output back to source code.
pub struct Api {
    /// The `metadata` section of the macro.
    metadata: Metadata,

    /// The `request` section of the macro.
    request: Request,

    /// The `response` section of the macro.
    response: Response,

    /// The `error` section of the macro.
    error_ty: TokenStream,
}

impl Parse for Api {
    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
        let ruma_api = util::import_ruma_api();

        let metadata: Metadata = input.parse()?;
        let request: Request = input.parse()?;
        let response: Response = input.parse()?;
        let error_ty = match input.parse::<ErrorType>() {
            Ok(err) => err.ty.to_token_stream(),
            Err(_) => quote! { #ruma_api::error::Void },
        };

        let newtype_body_field = request.newtype_body_field();
        if metadata.method == "GET" && (request.has_body_fields() || newtype_body_field.is_some()) {
            let mut combined_error: Option<syn::Error> = None;
            let mut add_error = |field| {
                let error = syn::Error::new_spanned(field, "GET endpoints can't have body fields");
                if let Some(combined_error_ref) = &mut combined_error {
                    combined_error_ref.combine(error);
                } else {
                    combined_error = Some(error);
                }
            };

            for field in request.body_fields() {
                add_error(field);
            }

            if let Some(field) = newtype_body_field {
                add_error(field);
            }

            return Err(combined_error.unwrap());
        }

        Ok(Self { metadata, request, response, error_ty })
    }
}

pub fn expand_all(api: Api) -> syn::Result<TokenStream> {
    // Guarantee `ruma_api` is available and named something we can refer to.
    let ruma_api_import = util::import_ruma_api();

    let description = &api.metadata.description;
    let method = &api.metadata.method;
    // We don't (currently) use this literal as a literal in the generated code. Instead we just
    // put it into doc comments, for which the span information is irrelevant. So we can work
    // with only the literal's value from here on.
    let name = &api.metadata.name.value();
    let path = &api.metadata.path;
    let rate_limited = &api.metadata.rate_limited;
    let authentication = &api.metadata.authentication;

    let request_type = &api.request;
    let response_type = &api.response;

    let incoming_request_type =
        if api.request.contains_lifetimes() { quote!(IncomingRequest) } else { quote!(Request) };

    let extract_request_path = if api.request.has_path_fields() {
        quote! {
            let path_segments: ::std::vec::Vec<&::std::primitive::str> =
                request.uri().path()[1..].split('/').collect();
        }
    } else {
        TokenStream::new()
    };

    let (request_path_string, parse_request_path) =
        util::request_path_string_and_parse(&api.request, &api.metadata, &ruma_api_import);

    let request_query_string = util::build_query_string(&api.request, &ruma_api_import);

    let extract_request_query = util::extract_request_query(&api.request, &ruma_api_import);

    let parse_request_query = if let Some(field) = api.request.query_map_field() {
        let field_name = field.ident.as_ref().expect("expected field to have an identifier");

        quote! {
            #field_name: request_query,
        }
    } else {
        api.request.request_init_query_fields()
    };

    let mut header_kvs = api.request.append_header_kvs();
    if authentication == "AccessToken" {
        header_kvs.extend(quote! {
            req_builder = req_builder.header(
                #ruma_api_import::exports::http::header::AUTHORIZATION,
                #ruma_api_import::exports::http::header::HeaderValue::from_str(
                    &::std::format!(
                        "Bearer {}",
                        access_token.ok_or(
                            #ruma_api_import::error::IntoHttpError::NeedsAuthentication
                        )?
                    )
                )?
            );
        });
    }

    let extract_request_headers = if api.request.has_header_fields() {
        quote! {
            let headers = request.headers();
        }
    } else {
        TokenStream::new()
    };

    let extract_request_body =
        if api.request.has_body_fields() || api.request.newtype_body_field().is_some() {
            let body_lifetimes = if api.request.has_body_lifetimes() {
                // duplicate the anonymous lifetime as many times as needed
                let lifetimes =
                    std::iter::repeat(quote! { '_ }).take(api.request.body_lifetime_count());
                quote! { < #( #lifetimes, )* >}
            } else {
                TokenStream::new()
            };
            quote! {
                let request_body: <
                    RequestBody #body_lifetimes
                    as #ruma_api_import::exports::ruma_serde::Outgoing
                >::Incoming = {
                    // If the request body is completely empty, pretend it is an empty JSON object
                    // instead. This allows requests with only optional body parameters to be
                    // deserialized in that case.
                    let json = match request.body().as_slice() {
                        b"" => b"{}",
                        body => body,
                    };

                    #ruma_api_import::try_deserialize!(
                        request,
                        #ruma_api_import::exports::serde_json::from_slice(json)
                    )
                };
            }
        } else {
            TokenStream::new()
        };

    let parse_request_headers = if api.request.has_header_fields() {
        api.request.parse_headers_from_request()
    } else {
        TokenStream::new()
    };

    let request_body = util::build_request_body(&api.request, &ruma_api_import);

    let parse_request_body = util::parse_request_body(&api.request);

    let extract_response_headers = if api.response.has_header_fields() {
        quote! {
            let mut headers = response.headers().clone();
        }
    } else {
        TokenStream::new()
    };

    let typed_response_body_decl =
        if api.response.has_body_fields() || api.response.newtype_body_field().is_some() {
            quote! {
                let response_body: <
                    ResponseBody
                    as #ruma_api_import::exports::ruma_serde::Outgoing
                >::Incoming = {
                    // If the reponse body is completely empty, pretend it is an empty JSON object
                    // instead. This allows reponses with only optional body parameters to be
                    // deserialized in that case.
                    let json = match response.body().as_slice() {
                        b"" => b"{}",
                        body => body,
                    };

                    #ruma_api_import::try_deserialize!(
                        response,
                        #ruma_api_import::exports::serde_json::from_slice(json),
                    )
                };
            }
        } else {
            TokenStream::new()
        };

    let response_init_fields = api.response.init_fields();

    let serialize_response_headers = api.response.apply_header_fields();

    let body = api.response.to_body();

    let metadata_doc = format!("Metadata for the `{}` API endpoint.", name);
    let request_doc =
        format!("Data for a request to the `{}` API endpoint.\n\n{}", name, description.value());
    let response_doc = format!("Data in the response from the `{}` API endpoint.", name);

    let error = &api.error_ty;

    let request_lifetimes = api.request.combine_lifetimes();

    let non_auth_endpoint_impls = if authentication != "None" {
        TokenStream::new()
    } else {
        quote! {
            impl #request_lifetimes #ruma_api_import::OutgoingNonAuthRequest
                for Request #request_lifetimes
            {}

            impl #ruma_api_import::IncomingNonAuthRequest for #incoming_request_type {}
        }
    };

    Ok(quote! {
        #[doc = #request_doc]
        #request_type

        impl ::std::convert::TryFrom<#ruma_api_import::exports::http::Request<Vec<u8>>>
            for #incoming_request_type
        {
            type Error = #ruma_api_import::error::FromHttpRequestError;

            #[allow(unused_variables)]
            fn try_from(
                request: #ruma_api_import::exports::http::Request<Vec<u8>>
            ) -> ::std::result::Result<Self, Self::Error> {
                #extract_request_path
                #extract_request_query
                #extract_request_headers
                #extract_request_body

                Ok(Self {
                    #parse_request_path
                    #parse_request_query
                    #parse_request_headers
                    #parse_request_body
                })
            }
        }

        #[doc = #response_doc]
        #response_type

        impl ::std::convert::TryFrom<Response>
            for #ruma_api_import::exports::http::Response<Vec<u8>>
        {
            type Error = #ruma_api_import::error::IntoHttpError;

            #[allow(unused_variables)]
            fn try_from(response: Response) -> ::std::result::Result<Self, Self::Error> {
                let mut resp_builder = #ruma_api_import::exports::http::Response::builder()
                    .header(
                        #ruma_api_import::exports::http::header::CONTENT_TYPE,
                        "application/json",
                    );

                let mut headers = resp_builder
                    .headers_mut()
                    .expect("`http::ResponseBuilder` is in unusable state");
                #serialize_response_headers

                // This cannot fail because we parse each header value
                // checking for errors as each value is inserted and
                // we only allow keys from the `http::header` module.
                let response = resp_builder.body(#body).unwrap();
                Ok(response)
            }
        }

        impl ::std::convert::TryFrom<#ruma_api_import::exports::http::Response<Vec<u8>>>
            for Response
        {
            type Error = #ruma_api_import::error::FromHttpResponseError<#error>;

            #[allow(unused_variables)]
            fn try_from(
                response: #ruma_api_import::exports::http::Response<Vec<u8>>,
            ) -> ::std::result::Result<Self, Self::Error> {
                if response.status().as_u16() < 400 {
                    #extract_response_headers

                    #typed_response_body_decl

                    Ok(Self {
                        #response_init_fields
                    })
                } else {
                    match <#error as #ruma_api_import::EndpointError>::try_from_response(response) {
                        Ok(err) => Err(#ruma_api_import::error::ServerError::Known(err).into()),
                        Err(response_err) => {
                            Err(#ruma_api_import::error::ServerError::Unknown(response_err).into())
                        }
                    }
                }
            }
        }

        #[doc = #metadata_doc]
        pub const METADATA: #ruma_api_import::Metadata = #ruma_api_import::Metadata {
            description: #description,
            method: #ruma_api_import::exports::http::Method::#method,
            name: #name,
            path: #path,
            rate_limited: #rate_limited,
            authentication: #ruma_api_import::AuthScheme::#authentication,
        };

        impl #request_lifetimes #ruma_api_import::OutgoingRequest
            for Request #request_lifetimes
        {
            type EndpointError = #error;
            type IncomingResponse =
                <Response as #ruma_api_import::exports::ruma_serde::Outgoing>::Incoming;

            #[doc = #metadata_doc]
            const METADATA: #ruma_api_import::Metadata = self::METADATA;

            #[allow(unused_mut, unused_variables)]
            fn try_into_http_request(
                self,
                base_url: &::std::primitive::str,
                access_token: ::std::option::Option<&str>,
            ) -> ::std::result::Result<
                #ruma_api_import::exports::http::Request<Vec<u8>>,
                #ruma_api_import::error::IntoHttpError,
            > {
                let metadata = self::METADATA;

                let mut req_builder = #ruma_api_import::exports::http::Request::builder()
                    .method(#ruma_api_import::exports::http::Method::#method)
                    .uri(::std::format!(
                        "{}{}{}",
                        // FIXME: Once MSRV is >= 1.45.0, switch to
                        // base_url.strip_suffix('/').unwrap_or(base_url),
                        match base_url.as_bytes().last() {
                            Some(b'/') => &base_url[..base_url.len() - 1],
                            _ => base_url,
                        },
                        #request_path_string,
                        #request_query_string,
                    ));

                #header_kvs

                let http_request = req_builder.body(#request_body)?;

                Ok(http_request)
            }
        }

        impl #ruma_api_import::IncomingRequest for #incoming_request_type {
            type EndpointError = #error;
            type OutgoingResponse = Response;

            #[doc = #metadata_doc]
            const METADATA: #ruma_api_import::Metadata = self::METADATA;
        }

        #non_auth_endpoint_impls
    })
}

mod kw {
    syn::custom_keyword!(error);
}

pub struct ErrorType {
    pub error_kw: kw::error,
    pub ty: Type,
}

impl Parse for ErrorType {
    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
        let error_kw = input.parse::<kw::error>()?;
        input.parse::<Token![:]>()?;
        let ty = input.parse()?;

        Ok(Self { error_kw, ty })
    }
}
<commit_msg>api-macros: Improve readability of expand_all<commit_after>//! Details of the `ruma_api` procedural macro.

use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{
    parse::{Parse, ParseStream},
    Token, Type,
};

pub(crate) mod attribute;
pub(crate) mod metadata;
pub(crate) mod request;
pub(crate) mod response;

use self::{metadata::Metadata, request::Request, response::Response};
use crate::util;

/// The result of processing the `ruma_api` macro, ready for output back to source code.
pub struct Api {
    /// The `metadata` section of the macro.
    metadata: Metadata,

    /// The `request` section of the macro.
    request: Request,

    /// The `response` section of the macro.
    response: Response,

    /// The `error` section of the macro.
    error_ty: TokenStream,
}

impl Parse for Api {
    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
        let ruma_api = util::import_ruma_api();

        let metadata: Metadata = input.parse()?;
        let request: Request = input.parse()?;
        let response: Response = input.parse()?;
        let error_ty = match input.parse::<ErrorType>() {
            Ok(err) => err.ty.to_token_stream(),
            Err(_) => quote! { #ruma_api::error::Void },
        };

        let newtype_body_field = request.newtype_body_field();
        if metadata.method == "GET" && (request.has_body_fields() || newtype_body_field.is_some()) {
            let mut combined_error: Option<syn::Error> = None;
            let mut add_error = |field| {
                let error = syn::Error::new_spanned(field, "GET endpoints can't have body fields");
                if let Some(combined_error_ref) = &mut combined_error {
                    combined_error_ref.combine(error);
                } else {
                    combined_error = Some(error);
                }
            };

            for field in request.body_fields() {
                add_error(field);
            }

            if let Some(field) = newtype_body_field {
                add_error(field);
            }

            return Err(combined_error.unwrap());
        }

        Ok(Self { metadata, request, response, error_ty })
    }
}

pub fn expand_all(api: Api) -> syn::Result<TokenStream> {
    // Guarantee `ruma_api` is available and named something we can refer to.
    let ruma_api = util::import_ruma_api();
    let http = quote! { #ruma_api::exports::http };
    let ruma_serde = quote! { #ruma_api::exports::ruma_serde };
    let serde_json = quote! { #ruma_api::exports::serde_json };

    let description = &api.metadata.description;
    let method = &api.metadata.method;
    // We don't (currently) use this literal as a literal in the generated code. Instead we just
    // put it into doc comments, for which the span information is irrelevant. So we can work
    // with only the literal's value from here on.
    let name = &api.metadata.name.value();
    let path = &api.metadata.path;
    let rate_limited = &api.metadata.rate_limited;
    let authentication = &api.metadata.authentication;

    let request_type = &api.request;
    let response_type = &api.response;

    let incoming_request_type =
        if api.request.contains_lifetimes() { quote!(IncomingRequest) } else { quote!(Request) };

    let extract_request_path = if api.request.has_path_fields() {
        quote! {
            let path_segments: ::std::vec::Vec<&::std::primitive::str> =
                request.uri().path()[1..].split('/').collect();
        }
    } else {
        TokenStream::new()
    };

    let (request_path_string, parse_request_path) =
        util::request_path_string_and_parse(&api.request, &api.metadata, &ruma_api);

    let request_query_string = util::build_query_string(&api.request, &ruma_api);
    let extract_request_query = util::extract_request_query(&api.request, &ruma_api);

    let parse_request_query = if let Some(field) = api.request.query_map_field() {
        let field_name = field.ident.as_ref().expect("expected field to have an identifier");

        quote! {
            #field_name: request_query,
        }
    } else {
        api.request.request_init_query_fields()
    };

    let mut header_kvs = api.request.append_header_kvs();
    if authentication == "AccessToken" {
        header_kvs.extend(quote! {
            req_builder = req_builder.header(
                #http::header::AUTHORIZATION,
                #http::header::HeaderValue::from_str(
                    &::std::format!(
                        "Bearer {}",
                        access_token.ok_or(
                            #ruma_api::error::IntoHttpError::NeedsAuthentication
                        )?
                    )
                )?
            );
        });
    }

    let extract_request_headers = if api.request.has_header_fields() {
        quote! {
            let headers = request.headers();
        }
    } else {
        TokenStream::new()
    };

    let extract_request_body =
        if api.request.has_body_fields() || api.request.newtype_body_field().is_some() {
            let body_lifetimes = if api.request.has_body_lifetimes() {
                // duplicate the anonymous lifetime as many times as needed
                let lifetimes =
                    std::iter::repeat(quote! { '_ }).take(api.request.body_lifetime_count());
                quote! { < #( #lifetimes, )* >}
            } else {
                TokenStream::new()
            };
            quote! {
                let request_body: <
                    RequestBody #body_lifetimes
                    as #ruma_serde::Outgoing
                >::Incoming = {
                    // If the request body is completely empty, pretend it is an empty JSON object
                    // instead. This allows requests with only optional body parameters to be
                    // deserialized in that case.
                    let json = match request.body().as_slice() {
                        b"" => b"{}",
                        body => body,
                    };

                    #ruma_api::try_deserialize!(request, #serde_json::from_slice(json))
                };
            }
        } else {
            TokenStream::new()
        };

    let parse_request_headers = if api.request.has_header_fields() {
        api.request.parse_headers_from_request()
    } else {
        TokenStream::new()
    };

    let request_body = util::build_request_body(&api.request, &ruma_api);
    let parse_request_body = util::parse_request_body(&api.request);

    let extract_response_headers = if api.response.has_header_fields() {
        quote! {
            let mut headers = response.headers().clone();
        }
    } else {
        TokenStream::new()
    };

    let typed_response_body_decl =
        if api.response.has_body_fields() || api.response.newtype_body_field().is_some() {
            quote! {
                let response_body: <
                    ResponseBody
                    as #ruma_serde::Outgoing
                >::Incoming = {
                    // If the reponse body is completely empty, pretend it is an empty JSON object
                    // instead. This allows reponses with only optional body parameters to be
                    // deserialized in that case.
                    let json = match response.body().as_slice() {
                        b"" => b"{}",
                        body => body,
                    };

                    #ruma_api::try_deserialize!(
                        response,
                        #serde_json::from_slice(json),
                    )
                };
            }
        } else {
            TokenStream::new()
        };

    let response_init_fields = api.response.init_fields();
    let serialize_response_headers = api.response.apply_header_fields();

    let body = api.response.to_body();

    let metadata_doc = format!("Metadata for the `{}` API endpoint.", name);
    let request_doc =
        format!("Data for a request to the `{}` API endpoint.\n\n{}", name, description.value());
    let response_doc = format!("Data in the response from the `{}` API endpoint.", name);

    let error = &api.error_ty;
    let request_lifetimes = api.request.combine_lifetimes();

    let non_auth_endpoint_impls = if authentication != "None" {
        TokenStream::new()
    } else {
        quote! {
            impl #request_lifetimes #ruma_api::OutgoingNonAuthRequest
                for Request #request_lifetimes
            {}

            impl #ruma_api::IncomingNonAuthRequest for #incoming_request_type {}
        }
    };

    Ok(quote! {
        #[doc = #request_doc]
        #request_type

        impl ::std::convert::TryFrom<#http::Request<Vec<u8>>> for #incoming_request_type {
            type Error = #ruma_api::error::FromHttpRequestError;

            #[allow(unused_variables)]
            fn try_from(
                request: #http::Request<Vec<u8>>
            ) -> ::std::result::Result<Self, Self::Error> {
                #extract_request_path
                #extract_request_query
                #extract_request_headers
                #extract_request_body

                Ok(Self {
                    #parse_request_path
                    #parse_request_query
                    #parse_request_headers
                    #parse_request_body
                })
            }
        }

        #[doc = #response_doc]
        #response_type

        impl ::std::convert::TryFrom<Response> for #http::Response<Vec<u8>> {
            type Error = #ruma_api::error::IntoHttpError;

            #[allow(unused_variables)]
            fn try_from(response: Response) -> ::std::result::Result<Self, Self::Error> {
                let mut resp_builder = #http::Response::builder()
                    .header(#http::header::CONTENT_TYPE, "application/json");

                let mut headers = resp_builder
                    .headers_mut()
                    .expect("`http::ResponseBuilder` is in unusable state");
                #serialize_response_headers

                // This cannot fail because we parse each header value
                // checking for errors as each value is inserted and
                // we only allow keys from the `http::header` module.
                let response = resp_builder.body(#body).unwrap();
                Ok(response)
            }
        }

        impl ::std::convert::TryFrom<#http::Response<Vec<u8>>> for Response {
            type Error = #ruma_api::error::FromHttpResponseError<#error>;

            #[allow(unused_variables)]
            fn try_from(
                response: #http::Response<Vec<u8>>,
            ) -> ::std::result::Result<Self, Self::Error> {
                if response.status().as_u16() < 400 {
                    #extract_response_headers

                    #typed_response_body_decl

                    Ok(Self {
                        #response_init_fields
                    })
                } else {
                    match <#error as #ruma_api::EndpointError>::try_from_response(response) {
                        Ok(err) => Err(#ruma_api::error::ServerError::Known(err).into()),
                        Err(response_err) => {
                            Err(#ruma_api::error::ServerError::Unknown(response_err).into())
                        }
                    }
                }
            }
        }

        #[doc = #metadata_doc]
        pub const METADATA: #ruma_api::Metadata = #ruma_api::Metadata {
            description: #description,
            method: #http::Method::#method,
            name: #name,
            path: #path,
            rate_limited: #rate_limited,
            authentication: #ruma_api::AuthScheme::#authentication,
        };

        impl #request_lifetimes #ruma_api::OutgoingRequest for Request #request_lifetimes {
            type EndpointError = #error;
            type IncomingResponse = <Response as #ruma_serde::Outgoing>::Incoming;

            #[doc = #metadata_doc]
            const METADATA: #ruma_api::Metadata = self::METADATA;

            #[allow(unused_mut, unused_variables)]
            fn try_into_http_request(
                self,
                base_url: &::std::primitive::str,
                access_token: ::std::option::Option<&str>,
            ) -> ::std::result::Result<#http::Request<Vec<u8>>, #ruma_api::error::IntoHttpError> {
                let metadata = self::METADATA;

                let mut req_builder = #http::Request::builder()
                    .method(#http::Method::#method)
                    .uri(::std::format!(
                        "{}{}{}",
                        // FIXME: Once MSRV is >= 1.45.0, switch to
                        // base_url.strip_suffix('/').unwrap_or(base_url),
                        match base_url.as_bytes().last() {
                            Some(b'/') => &base_url[..base_url.len() - 1],
                            _ => base_url,
                        },
                        #request_path_string,
                        #request_query_string,
                    ));

                #header_kvs

                let http_request = req_builder.body(#request_body)?;

                Ok(http_request)
            }
        }

        impl #ruma_api::IncomingRequest for #incoming_request_type {
            type EndpointError = #error;
            type OutgoingResponse = Response;

            #[doc = #metadata_doc]
            const METADATA: #ruma_api::Metadata = self::METADATA;
        }

        #non_auth_endpoint_impls
    })
}

mod kw {
    syn::custom_keyword!(error);
}

pub struct ErrorType {
    pub error_kw: kw::error,
    pub ty: Type,
}

impl Parse for ErrorType {
    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
        let error_kw = input.parse::<kw::error>()?;
        input.parse::<Token![:]>()?;
        let ty = input.parse()?;

        Ok(Self { error_kw, ty })
    }
}
<|endoftext|>


<commit_before>/*
    Txtrevise (rust)
    Command line text editing tool
    Version 1.0.0 (equivalent to Python impl. 1.1)
    Copyright (c) 2015 Sam Saint-Pettersen
    Released under the MIT License.
*/

extern crate regex;
use std::env;
use std::io::{BufRead, BufReader, Write};
use std::fs::File;
use regex::Regex;

// Display error and then usage information.
fn display_error(err: &str) {
    println!("Error: {}.", err);
    display_usage();
}

/// Display usage information.
fn display_usage() {
    println!("\nTxtrevise v 1.0.0 (rust)");
    println!("Command line text editing tool");
    println!("Copyright (c) 2015 Sam Saint-Pettersen");
    println!("\nReleased under the MIT License");
    println!("\nUsage: {} [-h] (-q) -f <file> -l <line #> -m <word(s)>", program());
    println!("-r <word(s)>");
    println!("\n-f: File to edit");
    println!("-l: Line number to edit text on (starts at 1)");
    println!("-m: Word(s) to match");
    println!("-r: Replacement word(s) for matched word(s)");
    println!("-q: Quiet mode. Only output to console for errors");
    println!("-h: This help information");
}

/// Process file.
fn process_file(filename: &str, line_no: usize, matches: &str, repl: &str, verbose: bool) {
    let mut line_num: usize = 0;
    let mut index: usize = 0;
    let mut all_lines: Vec<String> = Vec::new();

    // Read each line in file sequentially, store selected line no.
    let f = File::open(filename).unwrap();
    let file = BufReader::new(&f);
    for line in file.lines() {
        all_lines.push(line.unwrap());
        if line_num == line_no - 1 { // - 1, because lines start at 0.
            index = line_num;
        }
        line_num += 1;
    }

    // Revise the selected line and append newline.
    all_lines[index] = match_replace(&all_lines[index], line_no, matches, repl, verbose);
    all_lines.push(String::new());

    // Write out changed lines to file.
    let mut w = File::create(filename).unwrap();
    let _ = w.write_all(all_lines.join("\n").as_bytes());
}

/// Match and replace word(s).
fn match_replace(line: &str, line_no: usize, matches: &str, repl: &str, verbose: bool) -> String {
    let new_line: String;
    let re = Regex::new(matches).unwrap();

    // If word(s) are matched, return edited line with replacement word(s).
    if re.is_match(line) {
        if verbose {
            println!("\nMatched at Line {}: {}", line_no, line);
        }
        new_line = re.replace_all(line, repl);
        if verbose {
            println!("Replaced with: {}", new_line);
        }
    }
    // Otherwise, return same line as before.
    else {
        if verbose {
            println!("\nNo matches at Line {}", line_no);
        }
        new_line = line.to_string();
    }
    new_line
}

/// Get next argument.
fn next_argument(i: usize) -> String {
    let a = env::args().nth(i + 1);
    let arg = match a {
        Some(a) => a,
        None => {
            display_error("Invalid argument");
            return String::new();
        }
    };
    arg
}

/// Get program name from first argument.
fn program() -> String {
    let a = env::args().nth(0);
    let arg = match a {
        Some(a) => a,
        None => return "txtrevise".to_string(),
    };
    arg
}

/// Entry method.
fn main() {
    let args: Vec<_> = env::args().collect();
    let mut i: usize = 0;
    let mut filename = String::new();
    let mut matches = String::new();
    let mut repl = String::new();
    let mut verbose: bool = true;
    let mut line_no: usize = 1;
    if args.len() > 1 {
        for a in args {
            if a == "-h" {
                display_usage();
            }
            if a == "-f" {
                filename = next_argument(i);
            }
            if a == "-l" {
                let l = next_argument(i);
                let ol = l.parse::<usize>().ok();
                line_no = match ol {
                    Some(line_no) => line_no,
                    None => {
                        display_error("Line number must be an integer");
                        return;
                    }
                };
                if line_no == 0 {
                    display_error("Line number must be greater than 0");
                    return;
                }
            }
            if a == "-m" {
                matches = next_argument(i);
            }
            if a == "-r" {
                repl = next_argument(i);
            }
            if a == "-q" {
                verbose = false;
            }
            i += 1;
        }
    }
    else {
        display_error("No options specified");
    }

    // With necessary arguments, process file.
    if env::args().len() > 2 && filename.len() > 0 {
        process_file(&filename, line_no, &matches, &repl, verbose);
    }
}
<commit_msg>Update main.rs<commit_after>/*
    Txtrevise (rust)
    Command line text editing tool
    Version 1.0.0 (equivalent to Python impl. 1.1)
    Copyright (c) 2015 Sam Saint-Pettersen
    Released under the MIT License.
*/

extern crate clioptions;
extern crate regex;
use clioptons::CliOptions;
use regex::Regex;
use std::io::{BufRead, BufReader, Write};
use std::fs::File;

// Display error and then usage information.
fn display_error(program: &str, err: &str) {
    println!("Error: {}.", err);
    display_usage(&program, -1);
}

/// Display usage information.
fn display_usage(program: &str, code: i32) {
    println!("\nTxtrevise v 1.0.0 (rust)");
    println!("Command line text editing tool");
    println!("Copyright (c) 2015 Sam Saint-Pettersen");
    println!("\nReleased under the MIT License");
    println!("\nUsage: {} [-h] (-q) -f <file> -l <line #> -m <word(s)>", program);
    println!("-r <word(s)>");
    println!("\n-f: File to edit");
    println!("-l: Line number to edit text on (starts at 1)");
    println!("-m: Word(s) to match");
    println!("-r: Replacement word(s) for matched word(s)");
    println!("-q: Quiet mode. Only output to console for errors");
    println!("-h: This help information");
    exit(code);
}

/// Process file.
fn process_file(filename: &str, line_no: usize, matches: &str, repl: &str, verbose: bool) {
    let mut line_num: usize = 0;
    let mut index: usize = 0;
    let mut all_lines: Vec<String> = Vec::new();

    // Read each line in file sequentially, store selected line no.
    let f = File::open(filename).unwrap();
    let file = BufReader::new(&f);
    for line in file.lines() {
        all_lines.push(line.unwrap());
        if line_num == line_no - 1 { // - 1, because lines start at 0.
            index = line_num;
        }
        line_num += 1;
    }

    // Revise the selected line and append newline.
    all_lines[index] = match_replace(&all_lines[index], line_no, matches, repl, verbose);
    all_lines.push(String::new());

    // Write out changed lines to file.
    let mut w = File::create(filename).unwrap();
    let _ = w.write_all(all_lines.join("\n").as_bytes());
}

/// Match and replace word(s).
fn match_replace(line: &str, line_no: usize, matches: &str, repl: &str, verbose: bool) -> String {
    let new_line: String;
    let re = Regex::new(matches).unwrap();

    // If word(s) are matched, return edited line with replacement word(s).
    if re.is_match(line) {
        if verbose {
            println!("\nMatched at Line {}: {}", line_no, line);
        }
        new_line = re.replace_all(line, repl);
        if verbose {
            println!("Replaced with: {}", new_line);
        }
    }
    // Otherwise, return same line as before.
    else {
        if verbose {
            println!("\nNo matches at Line {}", line_no);
        }
        new_line = line.to_string();
    }
    new_line
}

fn main() {
    let cli = CliOptions::new("txtrevise");
    let program = cli.get_program();
    let args = cli.get_args();
    let mut filename = String::new();
    let mut matches = String::new();
    let mut repl = String::new();
    let mut verbose: bool = true;
    let mut line_no: usize = 0;
    if cli.get_num() > 1 {
        for (i, a) in args.iter().enumerate() {
            if a == "-h" {
                display_usage();
            }
            if a == "-f" {
                filename = cli.next_argument(i);
            }
            if a == "-l" {
                let l = cli.next_argument(i);
                let ol = l.parse::<usize>().ok();
                line_no = match ol {
                    Some(line_no) => line_no,
                    None => {
                        display_error(&program, "Line number must be an integer");
                        return;
                    }
                };
                if line_no == 0 {
                    display_error(&program, "Line number must be greater than 0");
                    return;
                }
            }
            if a == "-m" {
                matches = cli.next_argument(i);
            }
            if a == "-r" {
                repl = cli.next_argument(i);
            }
            if a == "-q" {
                verbose = false;
            }
        }
    }
    else {
        display_error(&program, "No options specified");
    }

    // With necessary arguments, process file.
    if cli.get_num() > 2 && filename.len() > 0 {
        process_file(&filename, line_no, &matches, &repl, verbose);
    }
}
<|endoftext|>


<commit_before>//! Radiant exposure (base unit joule per square meter, kg · s⁻²).

quantity! {
    /// Radiant exposure (base unit joule per square meter, kg · s⁻²).
    quantity: RadiantExposure; "radiant exposure";
    /// Dimension of radiant exposure, MT⁻² (base unit joule per square meter, kg · s⁻²).
    dimension: ISQ<
        Z0,     // length
        P1,     // mass
        N2,     // time
        Z0,     // electric current
        Z0,     // thermodynamic temperature
        Z0,     // amount of substance
        Z0>;    // luminous intensity
    units {
        @yottajoule_per_square_meter: prefix!(yotta); "YJ/m²",
            "yottajoule per square meter", "yottajoules per square meter";
        @zettajoule_per_square_meter: prefix!(zetta); "ZJ/m²",
            "zettajoule per square meter", "zettajoules per square meter";
        @exajoule_per_square_meter: prefix!(exa); "EJ/m²",
            "exajoule per square meter", "exajoules per square meter";
        @petajoule_per_square_meter: prefix!(peta); "PJ/m²",
            "petajoule per square meter", "petajoules per square meter";
        @terajoule_per_square_meter: prefix!(tera); "TJ/m²",
            "terajoule per square meter", "terajoules per square meter";
        @gigajoule_per_square_meter: prefix!(giga); "GJ/m²",
            "gigajoule per square meter", "gigajoules per square meter";
        @megajoule_per_square_meter: prefix!(mega); "MJ/m²",
            "megajoule per square meter", "megajoules per square meter";
        /// Derived unit of density.
        @kilojoule_per_square_meter: prefix!(kilo); "kJ/m²",
            "kilojoule per square meter", "kilojoules per square meter";
        @hectojoule_per_square_meter: prefix!(hecto); "hJ/m²",
            "hectojoule per square meter", "hectojoules per square meter";
        @decajoule_per_square_meter: prefix!(deca); "daJ/m²",
            "decajoule per square meter", "decajoules per square meter";
        @joule_per_square_meter: prefix!(none); "J/m²",
            "joule per square meter", "joules per square meter";
        @decijoule_per_square_meter: prefix!(deci); "dJ/m²",
            "decijoule per square meter", "decijoules per square meter";
        @centijoule_per_square_meter: prefix!(centi); "cJ/m²",
            "centijoule per square meter", "centijoules per square meter";
        @millijoule_per_square_meter: prefix!(milli); "mJ/m²",
            "millijoule per square meter", "millijoules per square meter";
        @microjoule_per_square_meter: prefix!(micro); "µJ/m²",
            "microjoule per square meter", "microjoules per square meter";
        @nanojoule_per_square_meter: prefix!(nano); "nJ/m²",
            "nanojoule per square meter", "nanojoules per square meter";
        @picojoule_per_square_meter: prefix!(pico); "pJ/m²",
            "picojoule per square meter", "picojoules per square meter";
        @femtojoule_per_square_meter: prefix!(femto); "fJ/m²",
            "femtojoule per square meter", "femtojoules per square meter";
        @attojoule_per_square_meter: prefix!(atto); "aJ/m²",
            "attojoule per square meter", "attojoules per square meter";
        @zeptojoule_per_square_meter: prefix!(zepto); "zJ/m²",
            "zeptojoule per square meter", "zeptojoules per square meter";
        @yoctojoule_per_square_meter: prefix!(yocto); "yJ/m²",
            "yoctojoule per square meter", "yoctojoules per square meter";
    }
}

#[cfg(test)]
mod test {
    storage_types! {
        use num::One;
        use si::quantities::*;
        use si::energy as e;
        use si::length as l;
        use si::radiant_exposure as re;
        use tests::Test;

        #[test]
        fn check_dimension() {
            let _: RadiantExposure<V> =
                Energy::new::<e::kilojoule>(V::one())
                / (Length::new::<l::meter>(V::one())
                   * Length::new::<l::meter>(V::one()));
        }

        #[test]
        fn check_units() {
            test::<e::yottajoule, re::yottajoule_per_square_meter>();
            test::<e::zettajoule, re::zettajoule_per_square_meter>();
            test::<e::exajoule, re::exajoule_per_square_meter>();
            test::<e::petajoule, re::petajoule_per_square_meter>();
            test::<e::terajoule, re::terajoule_per_square_meter>();
            test::<e::gigajoule, re::gigajoule_per_square_meter>();
            test::<e::megajoule, re::megajoule_per_square_meter>();
            test::<e::kilojoule, re::kilojoule_per_square_meter>();
            test::<e::hectojoule, re::hectojoule_per_square_meter>();
            test::<e::decajoule, re::decajoule_per_square_meter>();
            test::<e::joule, re::joule_per_square_meter>();
            test::<e::decijoule, re::decijoule_per_square_meter>();
            test::<e::centijoule, re::centijoule_per_square_meter>();
            test::<e::millijoule, re::millijoule_per_square_meter>();
            test::<e::microjoule, re::microjoule_per_square_meter>();
            test::<e::nanojoule, re::nanojoule_per_square_meter>();
            test::<e::picojoule, re::picojoule_per_square_meter>();
            test::<e::femtojoule, re::femtojoule_per_square_meter>();
            test::<e::attojoule, re::attojoule_per_square_meter>();
            test::<e::zeptojoule, re::zeptojoule_per_square_meter>();
            test::<e::yoctojoule, re::yoctojoule_per_square_meter>();

            fn test<E: e::Conversion<V>, RE: re::Conversion<V>>() {
                Test::assert_approx_eq(
                    &RadiantExposure::new::<RE>(V::one()),
                    &(Energy::new::<E>(V::one())
                      / (Length::new::<l::meter>(V::one())
                         * Length::new::<l::meter>(V::one()))));
            }
        }
    }
}
<commit_msg>Add square centimeter and square millimeter denominators in nano through giga numerators<commit_after>//! Radiant exposure (base unit joule per square meter, kg · s⁻²).

quantity! {
    /// Radiant exposure (base unit joule per square meter, kg · s⁻²).
    quantity: RadiantExposure; "radiant exposure";
    /// Dimension of radiant exposure, MT⁻² (base unit joule per square meter, kg · s⁻²).
    dimension: ISQ<
        Z0,     // length
        P1,     // mass
        N2,     // time
        Z0,     // electric current
        Z0,     // thermodynamic temperature
        Z0,     // amount of substance
        Z0>;    // luminous intensity
    units {
        // square meter denominator
        @yottajoule_per_square_meter: prefix!(yotta); "YJ/m²",
            "yottajoule per square meter", "yottajoules per square meter";
        @zettajoule_per_square_meter: prefix!(zetta); "ZJ/m²",
            "zettajoule per square meter", "zettajoules per square meter";
        @exajoule_per_square_meter: prefix!(exa); "EJ/m²",
            "exajoule per square meter", "exajoules per square meter";
        @petajoule_per_square_meter: prefix!(peta); "PJ/m²",
            "petajoule per square meter", "petajoules per square meter";
        @terajoule_per_square_meter: prefix!(tera); "TJ/m²",
            "terajoule per square meter", "terajoules per square meter";
        @gigajoule_per_square_meter: prefix!(giga); "GJ/m²",
            "gigajoule per square meter", "gigajoules per square meter";
        @megajoule_per_square_meter: prefix!(mega); "MJ/m²",
            "megajoule per square meter", "megajoules per square meter";
        /// Derived unit of radiant exposure.
        @kilojoule_per_square_meter: prefix!(kilo); "kJ/m²",
            "kilojoule per square meter", "kilojoules per square meter";
        @hectojoule_per_square_meter: prefix!(hecto); "hJ/m²",
            "hectojoule per square meter", "hectojoules per square meter";
        @decajoule_per_square_meter: prefix!(deca); "daJ/m²",
            "decajoule per square meter", "decajoules per square meter";
        @joule_per_square_meter: prefix!(none); "J/m²",
            "joule per square meter", "joules per square meter";
        @decijoule_per_square_meter: prefix!(deci); "dJ/m²",
            "decijoule per square meter", "decijoules per square meter";
        @centijoule_per_square_meter: prefix!(centi); "cJ/m²",
            "centijoule per square meter", "centijoules per square meter";
        @millijoule_per_square_meter: prefix!(milli); "mJ/m²",
            "millijoule per square meter", "millijoules per square meter";
        @microjoule_per_square_meter: prefix!(micro); "µJ/m²",
            "microjoule per square meter", "microjoules per square meter";
        @nanojoule_per_square_meter: prefix!(nano); "nJ/m²",
            "nanojoule per square meter", "nanojoules per square meter";
        @picojoule_per_square_meter: prefix!(pico); "pJ/m²",
            "picojoule per square meter", "picojoules per square meter";
        @femtojoule_per_square_meter: prefix!(femto); "fJ/m²",
            "femtojoule per square meter", "femtojoules per square meter";
        @attojoule_per_square_meter: prefix!(atto); "aJ/m²",
            "attojoule per square meter", "attojoules per square meter";
        @zeptojoule_per_square_meter: prefix!(zepto); "zJ/m²",
            "zeptojoule per square meter", "zeptojoules per square meter";
        @yoctojoule_per_square_meter: prefix!(yocto); "yJ/m²",
            "yoctojoule per square meter", "yoctojoules per square meter";

        // square centimeter denominator
        @gigajoule_per_square_centimeter: 1.0E13; "GJ/cm²",
            "gigajoule per square centimeter", "gigajoules per square centimeter";
        @megajoule_per_square_centimeter: 1.0E10; "MJ/cm²",
            "megajoule per square centimeter", "megajoules per square centimeter";
        @kilojoule_per_square_centimeter: 1.0E7; "kJ/cm²",
            "kilojoule per square centimeter", "kilojoules per square centimeter";
        @hectojoule_per_square_centimeter: 1.0E6; "hJ/cm²",
            "hectojoule per square centimeter", "hectojoules per square centimeter";
        @decajoule_per_square_centimeter: 1.0E5; "daJ/cm²",
            "decajoule per square centimeter", "decajoules per square centimeter";
        @joule_per_square_centimeter: 1.0E4; "J/cm²",
            "joule per square centimeter", "joules per square centimeter";
        @decijoule_per_square_centimeter: 1.0E3; "dJ/cm²",
            "decijoule per square centimeter", "decijoules per square centimeter";
        @centijoule_per_square_centimeter: 1.0E2; "cJ/cm²",
            "centijoule per square centimeter", "centijoules per square centimeter";
        @millijoule_per_square_centimeter: 1.0E1; "mJ/cm²",
            "millijoule per square centimeter", "millijoules per square centimeter";
        @microjoule_per_square_centimeter: 1.0E-2; "µJ/cm²",
            "microjoule per square centimeter", "microjoules per square centimeter";
        @nanojoule_per_square_centimeter: 1.0E-5; "nJ/cm²",
            "nanojoule per square centimeter", "nanojoules per square centimeter";

        // square millimeter denominator
        @gigajoule_per_square_millimeter: 1.0E15; "GJ/mm²",
            "gigajoule per square millimeter", "gigajoules per square millimeter";
        @megajoule_per_square_millimeter: 1.0E12; "MJ/mm²",
            "megajoule per square millimeter", "megajoules per square millimeter";
        @kilojoule_per_square_millimeter: 1.0E9; "kJ/mm²",
            "kilojoule per square millimeter", "kilojoules per square millimeter";
        @hectojoule_per_square_millimeter: 1.0E8; "hJ/mm²",
            "hectojoule per square millimeter", "hectojoules per square millimeter";
        @decajoule_per_square_millimeter: 1.0E7; "daJ/mm²",
            "decajoule per square millimeter", "decajoules per square millimeter";
        @joule_per_square_millimeter: 1.0E6; "J/mm²",
            "joule per square millimeter", "joules per square millimeter";
        @decijoule_per_square_millimeter: 1.0E5; "dJ/mm²",
            "decijoule per square millimeter", "decijoules per square millimeter";
        @centijoule_per_square_millimeter: 1.0E4; "cJ/mm²",
            "centijoule per square millimeter", "centijoules per square millimeter";
        @millijoule_per_square_millimeter: 1.0E3; "mJ/mm²",
            "millijoule per square millimeter", "millijoules per square millimeter";
        @microjoule_per_square_millimeter: 1.0E0; "µJ/mm²",
            "microjoule per square millimeter", "microjoules per square millimeter";
        @nanojoule_per_square_millimeter: 1.0E-3; "nJ/mm²",
            "nanojoule per square millimeter", "nanojoules per square millimeter";
    }
}

#[cfg(test)]
mod test {
    storage_types! {
        use num::One;
        use si::quantities::*;
        use si::energy as e;
        use si::length as l;
        use si::area as a;
        use si::radiant_exposure as re;
        use tests::Test;

        #[test]
        fn check_dimension() {
            let _: RadiantExposure<V> =
                Energy::new::<e::kilojoule>(V::one())
                / (Length::new::<l::meter>(V::one())
                   * Length::new::<l::meter>(V::one()));
        }

        #[test]
        fn check_units() {
            test::<e::yottajoule, a::square_meter, re::yottajoule_per_square_meter>();
            test::<e::zettajoule, a::square_meter, re::zettajoule_per_square_meter>();
            test::<e::exajoule, a::square_meter, re::exajoule_per_square_meter>();
            test::<e::petajoule, a::square_meter, re::petajoule_per_square_meter>();
            test::<e::terajoule, a::square_meter, re::terajoule_per_square_meter>();
            test::<e::gigajoule, a::square_meter, re::gigajoule_per_square_meter>();
            test::<e::megajoule, a::square_meter, re::megajoule_per_square_meter>();
            test::<e::kilojoule, a::square_meter, re::kilojoule_per_square_meter>();
            test::<e::hectojoule, a::square_meter, re::hectojoule_per_square_meter>();
            test::<e::decajoule, a::square_meter, re::decajoule_per_square_meter>();
            test::<e::joule, a::square_meter, re::joule_per_square_meter>();
            test::<e::decijoule, a::square_meter, re::decijoule_per_square_meter>();
            test::<e::centijoule, a::square_meter, re::centijoule_per_square_meter>();
            test::<e::millijoule, a::square_meter, re::millijoule_per_square_meter>();
            test::<e::microjoule, a::square_meter, re::microjoule_per_square_meter>();
            test::<e::nanojoule, a::square_meter, re::nanojoule_per_square_meter>();
            test::<e::picojoule, a::square_meter, re::picojoule_per_square_meter>();
            test::<e::femtojoule, a::square_meter, re::femtojoule_per_square_meter>();
            test::<e::attojoule, a::square_meter, re::attojoule_per_square_meter>();
            test::<e::zeptojoule, a::square_meter, re::zeptojoule_per_square_meter>();
            test::<e::yoctojoule, a::square_meter, re::yoctojoule_per_square_meter>();

            test::<e::gigajoule, a::square_centimeter, re::gigajoule_per_square_centimeter>();
            test::<e::megajoule, a::square_centimeter, re::megajoule_per_square_centimeter>();
            test::<e::kilojoule, a::square_centimeter, re::kilojoule_per_square_centimeter>();
            test::<e::hectojoule, a::square_centimeter, re::hectojoule_per_square_centimeter>();
            test::<e::decajoule, a::square_centimeter, re::decajoule_per_square_centimeter>();
            test::<e::joule, a::square_centimeter, re::joule_per_square_centimeter>();
            test::<e::decijoule, a::square_centimeter, re::decijoule_per_square_centimeter>();
            test::<e::centijoule, a::square_centimeter, re::centijoule_per_square_centimeter>();
            test::<e::millijoule, a::square_centimeter, re::millijoule_per_square_centimeter>();
            test::<e::microjoule, a::square_centimeter, re::microjoule_per_square_centimeter>();
            test::<e::nanojoule, a::square_centimeter, re::nanojoule_per_square_centimeter>();

            test::<e::gigajoule, a::square_millimeter, re::gigajoule_per_square_millimeter>();
            test::<e::megajoule, a::square_millimeter, re::megajoule_per_square_millimeter>();
            test::<e::kilojoule, a::square_millimeter, re::kilojoule_per_square_millimeter>();
            test::<e::hectojoule, a::square_millimeter, re::hectojoule_per_square_millimeter>();
            test::<e::decajoule, a::square_millimeter, re::decajoule_per_square_millimeter>();
            test::<e::joule, a::square_millimeter, re::joule_per_square_millimeter>();
            test::<e::decijoule, a::square_millimeter, re::decijoule_per_square_millimeter>();
            test::<e::centijoule, a::square_millimeter, re::centijoule_per_square_millimeter>();
            test::<e::millijoule, a::square_millimeter, re::millijoule_per_square_millimeter>();
            test::<e::microjoule, a::square_millimeter, re::microjoule_per_square_millimeter>();
            test::<e::nanojoule, a::square_millimeter, re::nanojoule_per_square_millimeter>();

            fn test<E: e::Conversion<V>, A: a::Conversion<V>, RE: re::Conversion<V>>() {
                Test::assert_approx_eq(
                    &RadiantExposure::new::<RE>(V::one()),
                    &(Energy::new::<E>(V::one()) / Area::new::<A>(V::one())));
            }
        }
    }
}
<|endoftext|>


<commit_before>use std::io::prelude::*;
use std::fs::{File, create_dir_all, read_dir, copy};
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use walkdir::WalkDir;

use errors::{Result, ResultExt};

pub fn is_path_in_directory(parent: &Path, path: &Path) -> Result<bool> {
    let canonical_path = path.canonicalize().map_err(|e| format!("Failed to canonicalize {}: {}", path.display(), e))?;
    let canonical_parent = parent.canonicalize().map_err(|e| format!("Failed to canonicalize {}: {}", parent.display(), e))?;
    return Ok(canonical_path.starts_with(canonical_parent));
}

/// Create a file with the content given
pub fn create_file(path: &Path, content: &str) -> Result<()> {
    let mut file = File::create(&path)?;
    file.write_all(content.as_bytes())?;
    Ok(())
}

/// Create a directory at the given path if it doesn't exist already
pub fn ensure_directory_exists(path: &Path) -> Result<()> {
    if !path.exists() {
        create_directory(path)?;
    }
    Ok(())
}

/// Very similar to `create_dir` from the std except it checks if the folder
/// exists before creating it
pub fn create_directory(path: &Path) -> Result<()> {
    if !path.exists() {
        create_dir_all(path)
            .chain_err(|| format!("Was not able to create folder {}", path.display()))?;
    }
    Ok(())
}

/// Return the content of a file, with error handling added
pub fn read_file(path: &Path) -> Result<String> {
    let mut content = String::new();
    File::open(path)
        .chain_err(|| format!("Failed to open '{:?}'", path.display()))?
        .read_to_string(&mut content)?;

    // Remove utf-8 BOM if any.
    if content.starts_with("\u{feff}") {
        content.drain(..3);
    }

    Ok(content)
}

/// Looks into the current folder for the path and see if there's anything that is not a .md
/// file. Those will be copied next to the rendered .html file
pub fn find_related_assets(path: &Path) -> Vec<PathBuf> {
    let mut assets = vec![];

    for entry in read_dir(path).unwrap().filter_map(|e| e.ok()) {
        let entry_path = entry.path();
        if entry_path.is_file() {
            match entry_path.extension() {
                Some(e) => match e.to_str() {
                    Some("md") => continue,
                    _ => assets.push(entry_path.to_path_buf()),
                },
                None => continue,
            }
        }
    }

    assets
}

/// Copy a file but takes into account where to start the copy as
/// there might be folders we need to create on the way
pub fn copy_file(src: &Path, dest: &PathBuf, base_path: &PathBuf) -> Result<()> {
    let relative_path = src.strip_prefix(base_path).unwrap();
    let target_path = dest.join(relative_path);

    if let Some(parent_directory) = target_path.parent() {
        create_dir_all(parent_directory)?;
    }

    copy(src, target_path)?;
    Ok(())
}

pub fn copy_directory(src: &PathBuf, dest: &PathBuf) -> Result<()> {
    for entry in WalkDir::new(src).into_iter().filter_map(|e| e.ok()) {
        let relative_path = entry.path().strip_prefix(src).unwrap();
        let target_path = dest.join(relative_path);

        if entry.path().is_dir() {
            if !target_path.exists() {
                create_directory(&target_path)?;
            }
        } else {
            copy_file(entry.path(), dest, src)?;
        }
    }
    Ok(())
}

pub fn get_file_time(path: &Path) -> Option<SystemTime> {
    return path.metadata().ok().and_then(|meta| {
        Some(match (meta.created().ok(), meta.modified().ok()) {
            (Some(tc), Some(tm)) => tc.max(tm),
            (Some(tc), None) => tc,
            (None, Some(tm)) => tm,
            (None, None) => return None,
        })
    });
}

/// Compares source and target files' timestamps and returns true if the source file
/// has been created _or_ updated after the target file has
pub fn file_stale<PS, PT>(p_source: PS, p_target: PT) -> bool where PS: AsRef<Path>, PT: AsRef<Path> {
    let p_source = p_source.as_ref();
    let p_target = p_target.as_ref();

    if !p_target.exists() {
        return true;
    }

    let time_source = get_file_time(p_source);
    let time_target = get_file_time(p_target);

    time_source.and_then(|ts| time_target.map(|tt| ts > tt)).unwrap_or(true)
}

#[cfg(test)]
mod tests {
    use std::fs::File;

    use tempfile::tempdir;

    use super::find_related_assets;

    #[test]
    fn can_find_related_assets() {
        let tmp_dir = tempdir().expect("create temp dir");
        File::create(tmp_dir.path().join("index.md")).unwrap();
        File::create(tmp_dir.path().join("example.js")).unwrap();
        File::create(tmp_dir.path().join("graph.jpg")).unwrap();
        File::create(tmp_dir.path().join("fail.png")).unwrap();

        let assets = find_related_assets(tmp_dir.path());
        assert_eq!(assets.len(), 3);
        assert_eq!(assets.iter().filter(|p| p.extension().unwrap() != "md").count(), 3);
        assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "example.js").count(), 1);
        assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "graph.jpg").count(), 1);
        assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "fail.png").count(), 1);
    }
}
<commit_msg>Some clippy fixes<commit_after>use std::io::prelude::*;
use std::fs::{File, create_dir_all, read_dir, copy};
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use walkdir::WalkDir;

use errors::{Result, ResultExt};

pub fn is_path_in_directory(parent: &Path, path: &Path) -> Result<bool> {
    let canonical_path = path.canonicalize().map_err(|e| format!("Failed to canonicalize {}: {}", path.display(), e))?;
    let canonical_parent = parent.canonicalize().map_err(|e| format!("Failed to canonicalize {}: {}", parent.display(), e))?;

    Ok(canonical_path.starts_with(canonical_parent))
}

/// Create a file with the content given
pub fn create_file(path: &Path, content: &str) -> Result<()> {
    let mut file = File::create(&path)?;
    file.write_all(content.as_bytes())?;
    Ok(())
}

/// Create a directory at the given path if it doesn't exist already
pub fn ensure_directory_exists(path: &Path) -> Result<()> {
    if !path.exists() {
        create_directory(path)?;
    }
    Ok(())
}

/// Very similar to `create_dir` from the std except it checks if the folder
/// exists before creating it
pub fn create_directory(path: &Path) -> Result<()> {
    if !path.exists() {
        create_dir_all(path)
            .chain_err(|| format!("Was not able to create folder {}", path.display()))?;
    }
    Ok(())
}

/// Return the content of a file, with error handling added
pub fn read_file(path: &Path) -> Result<String> {
    let mut content = String::new();
    File::open(path)
        .chain_err(|| format!("Failed to open '{:?}'", path.display()))?
        .read_to_string(&mut content)?;

    // Remove utf-8 BOM if any.
    if content.starts_with("\u{feff}") {
        content.drain(..3);
    }

    Ok(content)
}

/// Looks into the current folder for the path and see if there's anything that is not a .md
/// file. Those will be copied next to the rendered .html file
pub fn find_related_assets(path: &Path) -> Vec<PathBuf> {
    let mut assets = vec![];

    for entry in read_dir(path).unwrap().filter_map(|e| e.ok()) {
        let entry_path = entry.path();
        if entry_path.is_file() {
            match entry_path.extension() {
                Some(e) => match e.to_str() {
                    Some("md") => continue,
                    _ => assets.push(entry_path.to_path_buf()),
                },
                None => continue,
            }
        }
    }

    assets
}

/// Copy a file but takes into account where to start the copy as
/// there might be folders we need to create on the way
pub fn copy_file(src: &Path, dest: &PathBuf, base_path: &PathBuf) -> Result<()> {
    let relative_path = src.strip_prefix(base_path).unwrap();
    let target_path = dest.join(relative_path);

    if let Some(parent_directory) = target_path.parent() {
        create_dir_all(parent_directory)?;
    }

    copy(src, target_path)?;
    Ok(())
}

pub fn copy_directory(src: &PathBuf, dest: &PathBuf) -> Result<()> {
    for entry in WalkDir::new(src).into_iter().filter_map(|e| e.ok()) {
        let relative_path = entry.path().strip_prefix(src).unwrap();
        let target_path = dest.join(relative_path);

        if entry.path().is_dir() {
            if !target_path.exists() {
                create_directory(&target_path)?;
            }
        } else {
            copy_file(entry.path(), dest, src)?;
        }
    }
    Ok(())
}

pub fn get_file_time(path: &Path) -> Option<SystemTime> {
    path.metadata().ok().and_then(|meta| {
        Some(match (meta.created().ok(), meta.modified().ok()) {
            (Some(tc), Some(tm)) => tc.max(tm),
            (Some(tc), None) => tc,
            (None, Some(tm)) => tm,
            (None, None) => return None,
        })
    })
}

/// Compares source and target files' timestamps and returns true if the source file
/// has been created _or_ updated after the target file has
pub fn file_stale<PS, PT>(p_source: PS, p_target: PT) -> bool where PS: AsRef<Path>, PT: AsRef<Path> {
    let p_source = p_source.as_ref();
    let p_target = p_target.as_ref();

    if !p_target.exists() {
        return true;
    }

    let time_source = get_file_time(p_source);
    let time_target = get_file_time(p_target);

    time_source.and_then(|ts| time_target.map(|tt| ts > tt)).unwrap_or(true)
}

#[cfg(test)]
mod tests {
    use std::fs::File;

    use tempfile::tempdir;

    use super::find_related_assets;

    #[test]
    fn can_find_related_assets() {
        let tmp_dir = tempdir().expect("create temp dir");
        File::create(tmp_dir.path().join("index.md")).unwrap();
        File::create(tmp_dir.path().join("example.js")).unwrap();
        File::create(tmp_dir.path().join("graph.jpg")).unwrap();
        File::create(tmp_dir.path().join("fail.png")).unwrap();

        let assets = find_related_assets(tmp_dir.path());
        assert_eq!(assets.len(), 3);
        assert_eq!(assets.iter().filter(|p| p.extension().unwrap() != "md").count(), 3);
        assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "example.js").count(), 1);
        assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "graph.jpg").count(), 1);
        assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "fail.png").count(), 1);
    }
}
<|endoftext|>


<commit_before>use geom::size::Size2D;

trait ShareContext {
    // Creates a new context for GL object sharing.
    static fn new(size: Size2D<int>) -> self;

    // Flushes the context.
    fn flush(&self);

    // Returns the platform-specific ID that can be passed to other processes to access the shared
    // resources.
    fn id() -> int;
}

<commit_msg>Update for language changes<commit_after>use geom::size::Size2D;

pub trait ShareContext {
    // Creates a new context for GL object sharing.
    static fn new(size: Size2D<int>) -> self;

    // Flushes the context.
    fn flush(&self);

    // Returns the platform-specific ID that can be passed to other processes to access the shared
    // resources.
    fn id() -> int;
}

<|endoftext|>


<commit_before>

fn main() {
    println!("Hello, formatted print!");

    println!("Arg 1:{} and arg 2:{}", "first", "second");

    println!("Args can be named: {first} and {second}", first=1, second="two");

    println!("We can fmt binary! {:b}", 5);

    println!("We can align text with spaces or zeros: {number:>width$}, {number:>0width$}",
             number=3,
             width=6);

    let pi: f32 = 3.14159;

    let pi_3 = format!("{:.3}", pi);
    let pi_5 = format!("{:.5}", pi);

    println!("Pi to 3 places: {} and to 5 places: {}", pi_3, pi_5);
}
<commit_msg>format print each element of a vector<commit_after>
fn main() {
    println!("Hello, formatted print!");

    println!("Arg 1:{} and arg 2:{}", "first", "second");

    println!("Args can be named: {first} and {second}", first=1, second="two");

    println!("We can fmt binary! {:b}", 5);

    println!("We can align text with spaces or zeros: {number:>width$}, {number:>0width$}",
             number=3,
             width=6);

    let pi: f32 = 3.14159;

    let pi_3 = format!("{:.3}", pi);
    let pi_5 = format!("{:.5}", pi);

    println!("Pi to 3 places: {} and to 5 places: {}", pi_3, pi_5);

    // e, pi, & golden ratio
    let special_nums = vec![2.71828, 3.14159, 1.61803];

    // some iteration over a vector.
    for special_num in &special_nums {
        println!("This is a special number: {val:.3}", val=special_num);
    }

}
<|endoftext|>


<commit_before>extern crate glib;

extern crate gtk;
use gtk::{Inhibit, WidgetExt};

extern crate cairo;

use std::iter::FromIterator;

use std::rc::Rc;
use std::cell::RefCell;

use std::collections::vec_deque::{VecDeque};

use ::media::{Context, AudioBuffer};

pub struct AudioController {
    container: gtk::Container,
    drawingarea: gtk::DrawingArea,

    sample_buffer: VecDeque<f64>,
    buffer_duration: f64,
    offset: f64,
    relative_pos: f64,
    samples_nb: usize,

    channels: usize,
    sample_duration: f64,
}

impl AudioController {
    pub fn new(builder: &gtk::Builder) -> Rc<RefCell<Self>> {
        let this = Rc::new(RefCell::new(AudioController {
            container: builder.get_object("audio-container").unwrap(),
            drawingarea: builder.get_object("audio-drawingarea").unwrap(),

            sample_buffer: VecDeque::new(),
            buffer_duration: 0f64,
            relative_pos: 0f64,
            samples_nb: 0,

            channels: 0,
            offset: 0f64,
            sample_duration: 0f64,
        }));

        {
            let this_ref = this.borrow();
            let this_weak = Rc::downgrade(&this);
            this_ref.drawingarea.connect_draw(move |drawing_area, cairo_ctx| {
                let this = this_weak.upgrade()
                    .expect("Main controller is no longer available for select_media");
                let result = this.borrow().draw(drawing_area, cairo_ctx);
                result
            });
        }

        this
    }

    pub fn clear(&mut self) {
        self.sample_buffer.clear();
    }

    pub fn new_media(&mut self, ctx: &Context) {
        if !self.sample_buffer.is_empty() {
            println!("\nAudio stream: channels {}", self.channels);
            self.drawingarea.queue_draw();
            self.container.show();
        }
        else {
            self.container.hide();
        }
    }

    pub fn have_buffer(&mut self, mut buffer: AudioBuffer) {
        // First approximation: suppose the buffers come in ordered
        if self.sample_buffer.is_empty() {
            self.buffer_duration = 0f64;
            self.relative_pos = 0f64;
            self.offset = buffer.pts;
            self.channels = buffer.caps.channels;
            self.sample_duration = buffer.caps.sample_duration / 1_000_000_000f64;
            self.samples_nb = 0;

            println!("Sample duration: {}, offset: {}", self.sample_duration, self.offset);
        }

        self.samples_nb += buffer.samples_nb;
        self.buffer_duration = self.samples_nb as f64 * self.sample_duration;

        let mut samples_vecdeque = VecDeque::from_iter(buffer.samples.drain(..));
        self.sample_buffer.append(&mut samples_vecdeque);

        // TODO: this depends on the actual position in the stream
        /*while self.samples_nb > self.samples_max {
            let prev_buffer = self.sample_buffer.pop_front()
                .expect("Unconsistent samples nb in audio circular buffer");
            self.samples_nb -= prev_buffer.samples_nb as f64;
            match self.sample_buffer.front() {
                Some(first_buffer) => self.sample_offset = first_buffer.sample_offset as f64,
                None => (),
            };
        }*/
    }

    pub fn have_position(&mut self, position: f64) {
        let relative_pos = (position - self.offset) / 1_000_000_000f64;
        if self.relative_pos != relative_pos {
            self.relative_pos = relative_pos;
            self.drawingarea.queue_draw();
        }
    }

    fn draw(&self, drawing_area: &gtk::DrawingArea, cr: &cairo::Context) -> Inhibit {
        if self.sample_buffer.is_empty() {
            return Inhibit(false);
        }

        let display_window_duration = 2f64; // 2s TODO: adapt according to zoom
        let diplay_window_sample_nb_f = display_window_duration / self.sample_duration;
        let display_duration = self.buffer_duration.min(display_window_duration);
        let diplay_sample_nb = (display_duration / self.sample_duration) as usize;

        let allocation = drawing_area.get_allocation();
        let width_f = allocation.width as f64;
        cr.scale(
            width_f / display_window_duration,
            allocation.height as f64 / 2f64,
        );
        cr.set_line_width(0.003f64);

        let half_duration = display_duration / 2f64;
        let first_display_pos = if self.relative_pos > (self.buffer_duration - half_duration) {
            self.buffer_duration - display_duration
        } else if self.relative_pos > half_duration {
            self.relative_pos - half_duration
        } else {
            0f64
        };

        // Define the first sample as a multiple of sample_step
        // In order to avoid flickering when origin changes between redraws
        // TODO: use a something more linear to adapt sample step depending
        // on the (diplay_window_sample_nb_f / width_f) ratio
        let pixel_step = 1f64;
        let sample_step_f = if diplay_window_sample_nb_f > width_f / pixel_step {
            diplay_window_sample_nb_f / width_f * pixel_step
        } else {
            1f64
        };
        let sample_step = sample_step_f.trunc() as usize;
        let first_sample = (first_display_pos / self.sample_duration / sample_step_f).trunc() as usize * sample_step;
        let first_idx = first_sample * self.channels;
        let last_idx = first_idx + diplay_sample_nb * self.channels;

        let duration_step = sample_step_f * self.sample_duration;
        let idx_step = sample_step * self.channels;

        let colors = vec![(0.9f64, 0.9f64, 0.9f64), (0.9f64, 0f64, 0f64)];
        for channel in 0..self.channels {
            let color = if channel < 2 {
                colors[channel]
            } else {
                (0f64, 0f64, 0.2f64 * channel as f64)
            };
            cr.set_source_rgb(color.0, color.1, color.2);

            let mut sample_relative_ts = 0f64;
            let mut sample_idx = first_idx + channel;
            while sample_idx < last_idx {
                let y = self.sample_buffer[sample_idx];
                if sample_idx > 0 {
                    cr.line_to(sample_relative_ts, y);
                } else {
                    cr.move_to(sample_relative_ts, y);
                }

                sample_relative_ts += duration_step;
                sample_idx += idx_step;
            }

            cr.stroke();
        }

        // draw current pos
        let x = self.relative_pos - first_display_pos;
        if x < display_window_duration {
            cr.set_source_rgb(1f64, 1f64, 0f64);
            cr.set_line_width(0.005f64);
            cr.move_to(x, 0f64);
            cr.line_to(x, 2f64);
            cr.stroke();
        }

        Inhibit(false)
    }
}
<commit_msg>Adjust sample step to keep up with buffer stream (#9)<commit_after>extern crate glib;

extern crate gtk;
use gtk::{Inhibit, WidgetExt};

extern crate cairo;

use std::iter::FromIterator;

use std::rc::Rc;
use std::cell::RefCell;

use std::collections::vec_deque::{VecDeque};

use ::media::{Context, AudioBuffer};

pub struct AudioController {
    container: gtk::Container,
    drawingarea: gtk::DrawingArea,

    sample_buffer: VecDeque<f64>,
    buffer_duration: f64,
    offset: f64,
    relative_pos: f64,
    samples_nb: usize,
    sample_pixel_step: f64,
    iter_since_adjust: usize,
    max_iter_before_adjust: usize,

    channels: usize,
    sample_duration: f64,
}

impl AudioController {
    pub fn new(builder: &gtk::Builder) -> Rc<RefCell<Self>> {
        let this = Rc::new(RefCell::new(AudioController {
            container: builder.get_object("audio-container").unwrap(),
            drawingarea: builder.get_object("audio-drawingarea").unwrap(),

            sample_buffer: VecDeque::new(),
            buffer_duration: 0f64,
            relative_pos: 0f64,
            samples_nb: 0,
            sample_pixel_step: 0f64,
            iter_since_adjust: 0,
            max_iter_before_adjust: 5,

            channels: 0,
            offset: 0f64,
            sample_duration: 0f64,
        }));

        {
            let this_ref = this.borrow();
            let this_weak = Rc::downgrade(&this);
            this_ref.drawingarea.connect_draw(move |drawing_area, cairo_ctx| {
                let this = this_weak.upgrade()
                    .expect("Main controller is no longer available for select_media");
                let result = this.borrow_mut().draw(drawing_area, cairo_ctx);
                result
            });
        }

        this
    }

    pub fn clear(&mut self) {
        self.sample_buffer.clear();
    }

    pub fn new_media(&mut self, ctx: &Context) {
        if !self.sample_buffer.is_empty() {
            println!("\nAudio stream: channels {}", self.channels);
            self.drawingarea.queue_draw();
            self.container.show();
        }
        else {
            self.container.hide();
        }
    }

    pub fn have_buffer(&mut self, mut buffer: AudioBuffer) {
        // First approximation: suppose the buffers come in ordered
        if self.sample_buffer.is_empty() {
            self.buffer_duration = 0f64;
            self.offset = buffer.pts;
            self.relative_pos = 0f64;
            self.samples_nb = 0;
            self.sample_pixel_step = 1f64;
            self.iter_since_adjust = 0;

            self.channels = buffer.caps.channels;
            self.sample_duration = buffer.caps.sample_duration / 1_000_000_000f64;
        }

        self.samples_nb += buffer.samples_nb;
        self.buffer_duration = self.samples_nb as f64 * self.sample_duration;

        let mut samples_vecdeque = VecDeque::from_iter(buffer.samples.drain(..));
        self.sample_buffer.append(&mut samples_vecdeque);

        // TODO: this depends on the actual position in the stream
        /*while self.samples_nb > self.samples_max {
            let prev_buffer = self.sample_buffer.pop_front()
                .expect("Unconsistent samples nb in audio circular buffer");
            self.samples_nb -= prev_buffer.samples_nb as f64;
            match self.sample_buffer.front() {
                Some(first_buffer) => self.sample_offset = first_buffer.sample_offset as f64,
                None => (),
            };
        }*/
    }

    pub fn have_position(&mut self, position: f64) {
        let relative_pos = (position - self.offset) / 1_000_000_000f64;
        if self.relative_pos != relative_pos {
            self.relative_pos = relative_pos;
            self.drawingarea.queue_draw();
        }
    }

    fn draw(&mut self, drawing_area: &gtk::DrawingArea, cr: &cairo::Context) -> Inhibit {
        if self.sample_buffer.is_empty() {
            return Inhibit(false);
        }

        let display_window_duration = 2f64; // 2s TODO: adapt according to zoom
        let diplay_window_sample_nb_f = display_window_duration / self.sample_duration;
        let display_duration = self.buffer_duration.min(display_window_duration);
        let diplay_sample_nb = (display_duration / self.sample_duration) as usize;

        let allocation = drawing_area.get_allocation();
        let width_f = allocation.width as f64;
        cr.scale(
            width_f / display_window_duration,
            allocation.height as f64 / 2f64,
        );
        cr.set_line_width(0.002f64);

        let half_duration = display_duration / 2f64;
        // Take this opportunity to adjust sample pixel step in order
        // to accomodate to computation requirements
        self.iter_since_adjust += 1;
        let first_display_pos = if self.relative_pos > self.buffer_duration - half_duration {
            // TODO: don't adjust sample_pixel when reaching the end of file (need total duration)
            if self.iter_since_adjust > self.max_iter_before_adjust
                && self.relative_pos > self.buffer_duration - 0.25f64 * display_duration
            {
                self.sample_pixel_step += 1f64;
                self.iter_since_adjust = 0;
            }
            self.buffer_duration - display_duration
        } else if self.relative_pos > half_duration {
            if self.iter_since_adjust > self.max_iter_before_adjust
                && self.sample_pixel_step > 1f64 && self.relative_pos > self.buffer_duration - 0.25f64 * display_duration
            {
                self.sample_pixel_step -= 1f64;
                self.iter_since_adjust = 0;
            }
            self.relative_pos - half_duration
        } else {
            0f64
        };

        let sample_step_f = if diplay_window_sample_nb_f > width_f / self.sample_pixel_step {
            (diplay_window_sample_nb_f / width_f * self.sample_pixel_step).trunc()
        } else {
            1f64
        };
        let sample_step = sample_step_f as usize;
        // Define the first sample as a multiple of sample_step
        // In order to avoid flickering when origin changes between redraws
        let first_sample = (first_display_pos / self.sample_duration / sample_step_f).trunc() as usize * sample_step;
        let first_idx = first_sample * self.channels;
        let last_idx = first_idx + diplay_sample_nb * self.channels;

        let duration_step = sample_step_f * self.sample_duration;
        let idx_step = sample_step * self.channels;

        let colors = vec![(0.9f64, 0.9f64, 0.9f64), (0.9f64, 0f64, 0f64)];
        for channel in 0..self.channels {
            let color = if channel < 2 {
                colors[channel]
            } else {
                (0f64, 0f64, 0.2f64 * channel as f64)
            };
            cr.set_source_rgb(color.0, color.1, color.2);

            let mut sample_relative_ts = 0f64;
            let mut sample_idx = first_idx + channel;
            while sample_idx < last_idx {
                let y = self.sample_buffer[sample_idx];
                if sample_idx > 0 {
                    cr.line_to(sample_relative_ts, y);
                } else {
                    cr.move_to(sample_relative_ts, y);
                }

                sample_relative_ts += duration_step;
                sample_idx += idx_step;
            }

            cr.stroke();
        }

        // draw current pos
        let x = self.relative_pos - first_display_pos;
        cr.set_source_rgb(1f64, 1f64, 0f64);
        cr.set_line_width(0.004f64);
        cr.move_to(x, 0f64);
        cr.line_to(x, 2f64);
        cr.stroke();

        Inhibit(false)
    }
}
<|endoftext|>