<commit_before>// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Dynamic library facilities.
//!
//! A simple wrapper over the platform's dynamic library facilities

#![unstable(feature = "std_misc")]
#![allow(missing_docs)]

use prelude::v1::*;

use ffi::CString;
use mem;
use os;
use str;

#[allow(missing_copy_implementations)]
pub struct DynamicLibrary {
    handle: *mut u8
}

impl Drop for DynamicLibrary {
    fn drop(&mut self) {
        match dl::check_for_errors_in(|| {
            unsafe {
                dl::close(self.handle)
            }
        }) {
            Ok(()) => {},
            Err(str) => panic!("{}", str)
        }
    }
}

impl DynamicLibrary {
    // FIXME (#12938): Until DST lands, we cannot decompose &str into
    // & and str, so we cannot usefully take ToCStr arguments by
    // reference (without forcing an additional & around &str). So we
    // are instead temporarily adding an instance for &Path, so that
    // we can take ToCStr as owned. When DST lands, the &Path instance
    // should be removed, and arguments bound by ToCStr should be
    // passed by reference. (Here: in the `open` method.)

    /// Lazily open a dynamic library. When passed None it gives a
    /// handle to the calling process
    pub fn open(filename: Option<&Path>) -> Result<DynamicLibrary, String> {
        let maybe_library = dl::open(filename.map(|path| path.as_vec()));

        // The dynamic library must not be constructed if there is
        // an error opening the library so the destructor does not
        // run.
        match maybe_library {
            Err(err) => Err(err),
            Ok(handle) => Ok(DynamicLibrary { handle: handle })
        }
    }

    /// Prepends a path to this process's search path for dynamic libraries
    pub fn prepend_search_path(path: &Path) {
        let mut search_path = DynamicLibrary::search_path();
        search_path.insert(0, path.clone());
        let newval = DynamicLibrary::create_path(search_path.as_slice());
        os::setenv(DynamicLibrary::envvar(),
                   str::from_utf8(newval.as_slice()).unwrap());
    }

    /// From a slice of paths, create a new vector which is suitable to be an
    /// environment variable for this platforms dylib search path.
    pub fn create_path(path: &[Path]) -> Vec<u8> {
        let mut newvar = Vec::new();
        for (i, path) in path.iter().enumerate() {
            if i > 0 { newvar.push(DynamicLibrary::separator()); }
            newvar.push_all(path.as_vec());
        }
        return newvar;
    }

    /// Returns the environment variable for this process's dynamic library
    /// search path
    pub fn envvar() -> &'static str {
        if cfg!(windows) {
            "PATH"
        } else if cfg!(target_os = "macos") {
            "DYLD_LIBRARY_PATH"
        } else {
            "LD_LIBRARY_PATH"
        }
    }

    fn separator() -> u8 {
        if cfg!(windows) {b';'} else {b':'}
    }

    /// Returns the current search path for dynamic libraries being used by this
    /// process
    pub fn search_path() -> Vec<Path> {
        let mut ret = Vec::new();
        match os::getenv_as_bytes(DynamicLibrary::envvar()) {
            Some(env) => {
                for portion in
                        env.as_slice()
                           .split(|a| *a == DynamicLibrary::separator()) {
                    ret.push(Path::new(portion));
                }
            }
            None => {}
        }
        return ret;
    }

    /// Access the value at the symbol of the dynamic library
    pub unsafe fn symbol<T>(&self, symbol: &str) -> Result<*mut T, String> {
        // This function should have a lifetime constraint of 'a on
        // T but that feature is still unimplemented

        let raw_string = CString::from_slice(symbol.as_bytes());
        let maybe_symbol_value = dl::check_for_errors_in(|| {
            dl::symbol(self.handle, raw_string.as_ptr())
        });

        // The value must not be constructed if there is an error so
        // the destructor does not run.
        match maybe_symbol_value {
            Err(err) => Err(err),
            Ok(symbol_value) => Ok(mem::transmute(symbol_value))
        }
    }
}

#[cfg(all(test, not(target_os = "ios")))]
mod test {
    use super::*;
    use prelude::v1::*;
    use libc;
    use mem;

    #[test]
    #[cfg_attr(any(windows, target_os = "android"), ignore)] // FIXME #8818, #10379
    fn test_loading_cosine() {
        // The math library does not need to be loaded since it is already
        // statically linked in
        let none: Option<&Path> = None; // appease the typechecker
        let libm = match DynamicLibrary::open(none) {
            Err(error) => panic!("Could not load self as module: {}", error),
            Ok(libm) => libm
        };

        let cosine: extern fn(libc::c_double) -> libc::c_double = unsafe {
            match libm.symbol("cos") {
                Err(error) => panic!("Could not load function cos: {}", error),
                Ok(cosine) => mem::transmute::<*mut u8, _>(cosine)
            }
        };

        let argument = 0.0;
        let expected_result = 1.0;
        let result = cosine(argument);
        if result != expected_result {
            panic!("cos({}) != {} but equaled {} instead", argument,
                   expected_result, result)
        }
    }

    #[test]
    #[cfg(any(target_os = "linux",
              target_os = "macos",
              target_os = "freebsd",
              target_os = "dragonfly",
              target_os = "openbsd"))]
    fn test_errors_do_not_crash() {
        // Open /dev/null as a library to get an error, and make sure
        // that only causes an error, and not a crash.
        let path = Path::new("/dev/null");
        match DynamicLibrary::open(Some(&path)) {
            Err(_) => {}
            Ok(_) => panic!("Successfully opened the empty library.")
        }
    }
}

#[cfg(any(target_os = "linux",
          target_os = "android",
          target_os = "macos",
          target_os = "ios",
          target_os = "freebsd",
          target_os = "dragonfly",
          target_os = "openbsd"))]
mod dl {
    use prelude::v1::*;

    use ffi::{self, CString};
    use str;
    use libc;
    use ptr;

    pub fn open(filename: Option<&[u8]>) -> Result<*mut u8, String> {
        check_for_errors_in(|| {
            unsafe {
                match filename {
                    Some(filename) => open_external(filename),
                    None => open_internal(),
                }
            }
        })
    }

    const LAZY: libc::c_int = 1;

    unsafe fn open_external(filename: &[u8]) -> *mut u8 {
        let s = CString::from_slice(filename);
        dlopen(s.as_ptr(), LAZY) as *mut u8
    }

    unsafe fn open_internal() -> *mut u8 {
        dlopen(ptr::null(), LAZY) as *mut u8
    }

    pub fn check_for_errors_in<T, F>(f: F) -> Result<T, String> where
        F: FnOnce() -> T,
    {
        use sync::{StaticMutex, MUTEX_INIT};
        static LOCK: StaticMutex = MUTEX_INIT;
        unsafe {
            // dlerror isn't thread safe, so we need to lock around this entire
            // sequence
            let _guard = LOCK.lock();
            let _old_error = dlerror();

            let result = f();

            let last_error = dlerror() as *const _;
            let ret = if ptr::null() == last_error {
                Ok(result)
            } else {
                let s = ffi::c_str_to_bytes(&last_error);
                Err(str::from_utf8(s).unwrap().to_string())
            };

            ret
        }
    }

    pub unsafe fn symbol(handle: *mut u8,
                         symbol: *const libc::c_char) -> *mut u8 {
        dlsym(handle as *mut libc::c_void, symbol) as *mut u8
    }
    pub unsafe fn close(handle: *mut u8) {
        dlclose(handle as *mut libc::c_void); ()
    }

    #[cfg(not(target_os = "openbsd"))]
    #[link_name = "dl"]
    extern {
        fn dlopen(filename: *const libc::c_char,
                  flag: libc::c_int) -> *mut libc::c_void;
        fn dlerror() -> *mut libc::c_char;
        fn dlsym(handle: *mut libc::c_void,
                 symbol: *const libc::c_char) -> *mut libc::c_void;
        fn dlclose(handle: *mut libc::c_void) -> libc::c_int;
    }

    #[cfg(target_os = "openbsd")]
    extern {
        fn dlopen(filename: *const libc::c_char,
                  flag: libc::c_int) -> *mut libc::c_void;
        fn dlerror() -> *mut libc::c_char;
        fn dlsym(handle: *mut libc::c_void,
                 symbol: *const libc::c_char) -> *mut libc::c_void;
        fn dlclose(handle: *mut libc::c_void) -> libc::c_int;
    }
}

#[cfg(target_os = "windows")]
mod dl {
    use iter::IteratorExt;
    use libc;
    use libc::consts::os::extra::ERROR_CALL_NOT_IMPLEMENTED;
    use ops::FnOnce;
    use os;
    use option::Option::{self, Some, None};
    use ptr;
    use result::Result;
    use result::Result::{Ok, Err};
    use slice::SliceExt;
    use str::StrExt;
    use str;
    use string::String;
    use vec::Vec;
    use sys::c::compat::kernel32::SetThreadErrorMode;

    pub fn open(filename: Option<&[u8]>) -> Result<*mut u8, String> {
        // disable "dll load failed" error dialog.
        let mut use_thread_mode = true;
        let prev_error_mode = unsafe {
            // SEM_FAILCRITICALERRORS 0x01
            let new_error_mode = 1;
            let mut prev_error_mode = 0;
            // Windows >= 7 supports thread error mode.
            let result = SetThreadErrorMode(new_error_mode, &mut prev_error_mode);
            if result == 0 {
                let err = os::errno();
                if err as libc::c_int == ERROR_CALL_NOT_IMPLEMENTED {
                    use_thread_mode = false;
                    // SetThreadErrorMode not found. use fallback solution: SetErrorMode()
                    // Note that SetErrorMode is process-wide so this can cause race condition!
                    // However, since even Windows APIs do not care of such problem (#20650),
                    // we just assume SetErrorMode race is not a great deal.
                    prev_error_mode = SetErrorMode(new_error_mode);
                }
            }
            prev_error_mode
        };

        unsafe {
            SetLastError(0);
        }

        let result = match filename {
            Some(filename) => {
                let filename_str = str::from_utf8(filename).unwrap();
                let mut filename_str: Vec<u16> = filename_str.utf16_units().collect();
                filename_str.push(0);
                let result = unsafe {
                    LoadLibraryW(filename_str.as_ptr() as *const libc::c_void)
                };
                // beware: Vec/String may change errno during drop!
                // so we get error here.
                if result == ptr::null_mut() {
                    let errno = os::errno();
                    Err(os::error_string(errno))
                } else {
                    Ok(result as *mut u8)
                }
            }
            None => {
                let mut handle = ptr::null_mut();
                let succeeded = unsafe {
                    GetModuleHandleExW(0 as libc::DWORD, ptr::null(), &mut handle)
                };
                if succeeded == libc::FALSE {
                    let errno = os::errno();
                    Err(os::error_string(errno))
                } else {
                    Ok(handle as *mut u8)
                }
            }
        };

        unsafe {
            if use_thread_mode {
                SetThreadErrorMode(prev_error_mode, ptr::null_mut());
            } else {
                SetErrorMode(prev_error_mode);
            }
        }

        result
    }

    pub fn check_for_errors_in<T, F>(f: F) -> Result<T, String> where
        F: FnOnce() -> T,
    {
        unsafe {
            SetLastError(0);

            let result = f();

            let error = os::errno();
            if 0 == error {
                Ok(result)
            } else {
                Err(format!("Error code {}", error))
            }
        }
    }

    pub unsafe fn symbol(handle: *mut u8, symbol: *const libc::c_char) -> *mut u8 {
        GetProcAddress(handle as *mut libc::c_void, symbol) as *mut u8
    }
    pub unsafe fn close(handle: *mut u8) {
        FreeLibrary(handle as *mut libc::c_void); ()
    }

    #[allow(non_snake_case)]
    extern "system" {
        fn SetLastError(error: libc::size_t);
        fn LoadLibraryW(name: *const libc::c_void) -> *mut libc::c_void;
        fn GetModuleHandleExW(dwFlags: libc::DWORD, name: *const u16,
                              handle: *mut *mut libc::c_void) -> libc::BOOL;
        fn GetProcAddress(handle: *mut libc::c_void,
                          name: *const libc::c_char) -> *mut libc::c_void;
        fn FreeLibrary(handle: *mut libc::c_void);
        fn SetErrorMode(uMode: libc::c_uint) -> libc::c_uint;
    }
}
<commit_msg>openbsd: remove specific block and cleanup linkage<commit_after>// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Dynamic library facilities.
//!
//! A simple wrapper over the platform's dynamic library facilities

#![unstable(feature = "std_misc")]
#![allow(missing_docs)]

use prelude::v1::*;

use ffi::CString;
use mem;
use os;
use str;

#[allow(missing_copy_implementations)]
pub struct DynamicLibrary {
    handle: *mut u8
}

impl Drop for DynamicLibrary {
    fn drop(&mut self) {
        match dl::check_for_errors_in(|| {
            unsafe {
                dl::close(self.handle)
            }
        }) {
            Ok(()) => {},
            Err(str) => panic!("{}", str)
        }
    }
}

impl DynamicLibrary {
    // FIXME (#12938): Until DST lands, we cannot decompose &str into
    // & and str, so we cannot usefully take ToCStr arguments by
    // reference (without forcing an additional & around &str). So we
    // are instead temporarily adding an instance for &Path, so that
    // we can take ToCStr as owned. When DST lands, the &Path instance
    // should be removed, and arguments bound by ToCStr should be
    // passed by reference. (Here: in the `open` method.)

    /// Lazily open a dynamic library. When passed None it gives a
    /// handle to the calling process
    pub fn open(filename: Option<&Path>) -> Result<DynamicLibrary, String> {
        let maybe_library = dl::open(filename.map(|path| path.as_vec()));

        // The dynamic library must not be constructed if there is
        // an error opening the library so the destructor does not
        // run.
        match maybe_library {
            Err(err) => Err(err),
            Ok(handle) => Ok(DynamicLibrary { handle: handle })
        }
    }

    /// Prepends a path to this process's search path for dynamic libraries
    pub fn prepend_search_path(path: &Path) {
        let mut search_path = DynamicLibrary::search_path();
        search_path.insert(0, path.clone());
        let newval = DynamicLibrary::create_path(search_path.as_slice());
        os::setenv(DynamicLibrary::envvar(),
                   str::from_utf8(newval.as_slice()).unwrap());
    }

    /// From a slice of paths, create a new vector which is suitable to be an
    /// environment variable for this platforms dylib search path.
    pub fn create_path(path: &[Path]) -> Vec<u8> {
        let mut newvar = Vec::new();
        for (i, path) in path.iter().enumerate() {
            if i > 0 { newvar.push(DynamicLibrary::separator()); }
            newvar.push_all(path.as_vec());
        }
        return newvar;
    }

    /// Returns the environment variable for this process's dynamic library
    /// search path
    pub fn envvar() -> &'static str {
        if cfg!(windows) {
            "PATH"
        } else if cfg!(target_os = "macos") {
            "DYLD_LIBRARY_PATH"
        } else {
            "LD_LIBRARY_PATH"
        }
    }

    fn separator() -> u8 {
        if cfg!(windows) {b';'} else {b':'}
    }

    /// Returns the current search path for dynamic libraries being used by this
    /// process
    pub fn search_path() -> Vec<Path> {
        let mut ret = Vec::new();
        match os::getenv_as_bytes(DynamicLibrary::envvar()) {
            Some(env) => {
                for portion in
                        env.as_slice()
                           .split(|a| *a == DynamicLibrary::separator()) {
                    ret.push(Path::new(portion));
                }
            }
            None => {}
        }
        return ret;
    }

    /// Access the value at the symbol of the dynamic library
    pub unsafe fn symbol<T>(&self, symbol: &str) -> Result<*mut T, String> {
        // This function should have a lifetime constraint of 'a on
        // T but that feature is still unimplemented

        let raw_string = CString::from_slice(symbol.as_bytes());
        let maybe_symbol_value = dl::check_for_errors_in(|| {
            dl::symbol(self.handle, raw_string.as_ptr())
        });

        // The value must not be constructed if there is an error so
        // the destructor does not run.
        match maybe_symbol_value {
            Err(err) => Err(err),
            Ok(symbol_value) => Ok(mem::transmute(symbol_value))
        }
    }
}

#[cfg(all(test, not(target_os = "ios")))]
mod test {
    use super::*;
    use prelude::v1::*;
    use libc;
    use mem;

    #[test]
    #[cfg_attr(any(windows, target_os = "android"), ignore)] // FIXME #8818, #10379
    fn test_loading_cosine() {
        // The math library does not need to be loaded since it is already
        // statically linked in
        let none: Option<&Path> = None; // appease the typechecker
        let libm = match DynamicLibrary::open(none) {
            Err(error) => panic!("Could not load self as module: {}", error),
            Ok(libm) => libm
        };

        let cosine: extern fn(libc::c_double) -> libc::c_double = unsafe {
            match libm.symbol("cos") {
                Err(error) => panic!("Could not load function cos: {}", error),
                Ok(cosine) => mem::transmute::<*mut u8, _>(cosine)
            }
        };

        let argument = 0.0;
        let expected_result = 1.0;
        let result = cosine(argument);
        if result != expected_result {
            panic!("cos({}) != {} but equaled {} instead", argument,
                   expected_result, result)
        }
    }

    #[test]
    #[cfg(any(target_os = "linux",
              target_os = "macos",
              target_os = "freebsd",
              target_os = "dragonfly",
              target_os = "openbsd"))]
    fn test_errors_do_not_crash() {
        // Open /dev/null as a library to get an error, and make sure
        // that only causes an error, and not a crash.
        let path = Path::new("/dev/null");
        match DynamicLibrary::open(Some(&path)) {
            Err(_) => {}
            Ok(_) => panic!("Successfully opened the empty library.")
        }
    }
}

#[cfg(any(target_os = "linux",
          target_os = "android",
          target_os = "macos",
          target_os = "ios",
          target_os = "freebsd",
          target_os = "dragonfly",
          target_os = "openbsd"))]
mod dl {
    use prelude::v1::*;

    use ffi::{self, CString};
    use str;
    use libc;
    use ptr;

    pub fn open(filename: Option<&[u8]>) -> Result<*mut u8, String> {
        check_for_errors_in(|| {
            unsafe {
                match filename {
                    Some(filename) => open_external(filename),
                    None => open_internal(),
                }
            }
        })
    }

    const LAZY: libc::c_int = 1;

    unsafe fn open_external(filename: &[u8]) -> *mut u8 {
        let s = CString::from_slice(filename);
        dlopen(s.as_ptr(), LAZY) as *mut u8
    }

    unsafe fn open_internal() -> *mut u8 {
        dlopen(ptr::null(), LAZY) as *mut u8
    }

    pub fn check_for_errors_in<T, F>(f: F) -> Result<T, String> where
        F: FnOnce() -> T,
    {
        use sync::{StaticMutex, MUTEX_INIT};
        static LOCK: StaticMutex = MUTEX_INIT;
        unsafe {
            // dlerror isn't thread safe, so we need to lock around this entire
            // sequence
            let _guard = LOCK.lock();
            let _old_error = dlerror();

            let result = f();

            let last_error = dlerror() as *const _;
            let ret = if ptr::null() == last_error {
                Ok(result)
            } else {
                let s = ffi::c_str_to_bytes(&last_error);
                Err(str::from_utf8(s).unwrap().to_string())
            };

            ret
        }
    }

    pub unsafe fn symbol(handle: *mut u8,
                         symbol: *const libc::c_char) -> *mut u8 {
        dlsym(handle as *mut libc::c_void, symbol) as *mut u8
    }
    pub unsafe fn close(handle: *mut u8) {
        dlclose(handle as *mut libc::c_void); ()
    }

    extern {
        fn dlopen(filename: *const libc::c_char,
                  flag: libc::c_int) -> *mut libc::c_void;
        fn dlerror() -> *mut libc::c_char;
        fn dlsym(handle: *mut libc::c_void,
                 symbol: *const libc::c_char) -> *mut libc::c_void;
        fn dlclose(handle: *mut libc::c_void) -> libc::c_int;
    }
}

#[cfg(target_os = "windows")]
mod dl {
    use iter::IteratorExt;
    use libc;
    use libc::consts::os::extra::ERROR_CALL_NOT_IMPLEMENTED;
    use ops::FnOnce;
    use os;
    use option::Option::{self, Some, None};
    use ptr;
    use result::Result;
    use result::Result::{Ok, Err};
    use slice::SliceExt;
    use str::StrExt;
    use str;
    use string::String;
    use vec::Vec;
    use sys::c::compat::kernel32::SetThreadErrorMode;

    pub fn open(filename: Option<&[u8]>) -> Result<*mut u8, String> {
        // disable "dll load failed" error dialog.
        let mut use_thread_mode = true;
        let prev_error_mode = unsafe {
            // SEM_FAILCRITICALERRORS 0x01
            let new_error_mode = 1;
            let mut prev_error_mode = 0;
            // Windows >= 7 supports thread error mode.
            let result = SetThreadErrorMode(new_error_mode, &mut prev_error_mode);
            if result == 0 {
                let err = os::errno();
                if err as libc::c_int == ERROR_CALL_NOT_IMPLEMENTED {
                    use_thread_mode = false;
                    // SetThreadErrorMode not found. use fallback solution: SetErrorMode()
                    // Note that SetErrorMode is process-wide so this can cause race condition!
                    // However, since even Windows APIs do not care of such problem (#20650),
                    // we just assume SetErrorMode race is not a great deal.
                    prev_error_mode = SetErrorMode(new_error_mode);
                }
            }
            prev_error_mode
        };

        unsafe {
            SetLastError(0);
        }

        let result = match filename {
            Some(filename) => {
                let filename_str = str::from_utf8(filename).unwrap();
                let mut filename_str: Vec<u16> = filename_str.utf16_units().collect();
                filename_str.push(0);
                let result = unsafe {
                    LoadLibraryW(filename_str.as_ptr() as *const libc::c_void)
                };
                // beware: Vec/String may change errno during drop!
                // so we get error here.
                if result == ptr::null_mut() {
                    let errno = os::errno();
                    Err(os::error_string(errno))
                } else {
                    Ok(result as *mut u8)
                }
            }
            None => {
                let mut handle = ptr::null_mut();
                let succeeded = unsafe {
                    GetModuleHandleExW(0 as libc::DWORD, ptr::null(), &mut handle)
                };
                if succeeded == libc::FALSE {
                    let errno = os::errno();
                    Err(os::error_string(errno))
                } else {
                    Ok(handle as *mut u8)
                }
            }
        };

        unsafe {
            if use_thread_mode {
                SetThreadErrorMode(prev_error_mode, ptr::null_mut());
            } else {
                SetErrorMode(prev_error_mode);
            }
        }

        result
    }

    pub fn check_for_errors_in<T, F>(f: F) -> Result<T, String> where
        F: FnOnce() -> T,
    {
        unsafe {
            SetLastError(0);

            let result = f();

            let error = os::errno();
            if 0 == error {
                Ok(result)
            } else {
                Err(format!("Error code {}", error))
            }
        }
    }

    pub unsafe fn symbol(handle: *mut u8, symbol: *const libc::c_char) -> *mut u8 {
        GetProcAddress(handle as *mut libc::c_void, symbol) as *mut u8
    }
    pub unsafe fn close(handle: *mut u8) {
        FreeLibrary(handle as *mut libc::c_void); ()
    }

    #[allow(non_snake_case)]
    extern "system" {
        fn SetLastError(error: libc::size_t);
        fn LoadLibraryW(name: *const libc::c_void) -> *mut libc::c_void;
        fn GetModuleHandleExW(dwFlags: libc::DWORD, name: *const u16,
                              handle: *mut *mut libc::c_void) -> libc::BOOL;
        fn GetProcAddress(handle: *mut libc::c_void,
                          name: *const libc::c_char) -> *mut libc::c_void;
        fn FreeLibrary(handle: *mut libc::c_void);
        fn SetErrorMode(uMode: libc::c_uint) -> libc::c_uint;
    }
}
<|endoftext|>


<commit_before>extern crate mdbook;
#[macro_use]
extern crate clap;
extern crate log;
extern crate env_logger;
extern crate open;

// Dependencies for the Watch feature
#[cfg(feature = "watch")]
extern crate notify;
#[cfg(feature = "watch")]
extern crate time;
#[cfg(feature = "watch")]
extern crate crossbeam;

use std::env;
use std::error::Error;
use std::ffi::OsStr;
use std::io::{self, Write};
use std::path::{Path, PathBuf};

use clap::{App, ArgMatches, SubCommand, AppSettings};

// Uses for the Watch feature
#[cfg(feature = "watch")]
use notify::Watcher;
#[cfg(feature = "watch")]
use std::time::Duration;
#[cfg(feature = "watch")]
use std::sync::mpsc::channel;

use mdbook::MDBook;

const NAME: &'static str = "mdbook";

fn main() {
    env_logger::init().unwrap();

    // Create a list of valid arguments and sub-commands
    let matches = App::new(NAME)
                    .about("Create a book in form of a static website from markdown files")
                    .author("Mathieu David <mathieudavid@mathieudavid.org>")
                    // Get the version from our Cargo.toml using clap's crate_version!() macro
                    .version(&*format!("v{}", crate_version!()))
                    .setting(AppSettings::SubcommandRequired)
                    .after_help("For more information about a specific command, try `mdbook <command> --help`\nSource code for mdbook available at: https://github.com/azerupi/mdBook")
                    .subcommand(SubCommand::with_name("init")
                        .about("Create boilerplate structure and files in the directory")
                        // the {n} denotes a newline which will properly aligned in all help messages
                        .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'")
                        .arg_from_usage("--theme 'Copies the default theme into your source folder'")
                        .arg_from_usage("--force 'skip confirmation prompts'"))
                    .subcommand(SubCommand::with_name("build")
                        .about("Build the book from the markdown files")
                        .arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
                        .arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'")
                        .arg_from_usage("--no-create 'Will not create non-existent files linked from SUMMARY.md'")
                        .arg_from_usage("--curly-quotes 'Convert straight quotes to curly quotes, except for those that occur in code blocks and code spans'")
                        .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'"))
                    .subcommand(SubCommand::with_name("watch")
                        .about("Watch the files for changes")
                        .arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
                        .arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'")
                        .arg_from_usage("--curly-quotes 'Convert straight quotes to curly quotes, except for those that occur in code blocks and code spans'")
                        .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'"))
                    .subcommand(SubCommand::with_name("serve")
                        .about("Serve the book at http://localhost:3000. Rebuild and reload on change.")
                        .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'")
                        .arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'")
                        .arg_from_usage("--curly-quotes 'Convert straight quotes to curly quotes, except for those that occur in code blocks and code spans'")
                        .arg_from_usage("-p, --port=[port] 'Use another port{n}(Defaults to 3000)'")
                        .arg_from_usage("-w, --websocket-port=[ws-port] 'Use another port for the websocket connection (livereload){n}(Defaults to 3001)'")
                        .arg_from_usage("-i, --interface=[interface] 'Interface to listen on{n}(Defaults to localhost)'")
                        .arg_from_usage("-a, --address=[address] 'Address that the browser can reach the websocket server from{n}(Defaults to the interface address)'")
                        .arg_from_usage("-o, --open 'Open the book server in a web browser'"))
                    .subcommand(SubCommand::with_name("test")
                        .about("Test that code samples compile"))
                    .get_matches();

    // Check which subcomamnd the user ran...
    let res = match matches.subcommand() {
        ("init", Some(sub_matches)) => init(sub_matches),
        ("build", Some(sub_matches)) => build(sub_matches),
        #[cfg(feature = "watch")]
        ("watch", Some(sub_matches)) => watch(sub_matches),
        #[cfg(feature = "serve")]
        ("serve", Some(sub_matches)) => serve::serve(sub_matches),
        ("test", Some(sub_matches)) => test(sub_matches),
        (_, _) => unreachable!(),
    };

    if let Err(e) = res {
        writeln!(&mut io::stderr(), "An error occured:\n{}", e).ok();
        ::std::process::exit(101);
    }
}

// Simple function that user comfirmation
fn confirm() -> bool {
    io::stdout().flush().unwrap();
    let mut s = String::new();
    io::stdin().read_line(&mut s).ok();
    match &*s.trim() {
        "Y" | "y" | "yes" | "Yes" => true,
        _ => false,
    }
}

// Init command implementation
fn init(args: &ArgMatches) -> Result<(), Box<Error>> {

    let book_dir = get_book_dir(args);
    let mut book = MDBook::new(&book_dir);

    // Call the function that does the initialization
    book.init()?;

    // If flag `--theme` is present, copy theme to src
    if args.is_present("theme") {

        // Skip this if `--force` is present
        if !args.is_present("force") {
            // Print warning
            print!("\nCopying the default theme to {:?}", book.get_source());
            println!("could potentially overwrite files already present in that directory.");
            print!("\nAre you sure you want to continue? (y/n) ");

            // Read answer from user and exit if it's not 'yes'
            if !confirm() {
                println!("\nSkipping...\n");
                println!("All done, no errors...");
                ::std::process::exit(0);
            }
        }

        // Call the function that copies the theme
        book.copy_theme()?;
        println!("\nTheme copied.");

    }

    // Because of `src/book/mdbook.rs#L37-L39`, `dest` will always start with `root`
    let is_dest_inside_root = book.get_destination()
                                  .map(|p| p.starts_with(book.get_root()))
                                  .unwrap_or(false);

    if !args.is_present("force") && is_dest_inside_root {
        println!("\nDo you want a .gitignore to be created? (y/n)");

        if confirm() {
            book.create_gitignore();
            println!("\n.gitignore created.");
        }
    }

    println!("\nAll done, no errors...");

    Ok(())
}

// Build command implementation
fn build(args: &ArgMatches) -> Result<(), Box<Error>> {
    let book_dir = get_book_dir(args);
    let book = MDBook::new(&book_dir).read_config()?;

    let mut book = match args.value_of("dest-dir") {
        Some(dest_dir) => book.with_destination(dest_dir),
        None => book,
    };

    if args.is_present("no-create") {
        book.create_missing = false;
    }

    if args.is_present("curly-quotes") {
        book = book.with_curly_quotes(true);
    }

    book.build()?;

    if let Some(d) = book.get_destination() {
        if args.is_present("open") {
            open(d.join("index.html"));
        }
    }

    Ok(())
}

// Watch command implementation
#[cfg(feature = "watch")]
fn watch(args: &ArgMatches) -> Result<(), Box<Error>> {
    let book_dir = get_book_dir(args);
    let book = MDBook::new(&book_dir).read_config()?;

    let mut book = match args.value_of("dest-dir") {
        Some(dest_dir) => book.with_destination(dest_dir),
        None => book,
    };

    if args.is_present("curly-quotes") {
        book = book.with_curly_quotes(true);
    }

    if args.is_present("open") {
        book.build()?;
        if let Some(d) = book.get_destination() {
            open(d.join("index.html"));
        }
    }

    trigger_on_change(&mut book, |path, book| {
        println!("File changed: {:?}\nBuilding book...\n", path);
        if let Err(e) = book.build() {
            println!("Error while building: {:?}", e);
        }
        println!("");
    });

    Ok(())
}

#[cfg(feature = "serve")]
mod serve {
    extern crate iron;
    extern crate staticfile;
    extern crate ws;

    use std;
    use std::path::Path;
    use std::error::Error;
    use self::iron::{Iron, AfterMiddleware, IronResult, IronError, Request, Response, status, Set, Chain};
    use clap::ArgMatches;
    use mdbook::MDBook;

    use {get_book_dir, open, trigger_on_change};

    struct ErrorRecover;

    impl AfterMiddleware for ErrorRecover {
        fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
            match err.response.status {
                // each error will result in 404 response
                Some(_) => Ok(err.response.set(status::NotFound)),
                _ => Err(err),
            }
        }
    }

    // Watch command implementation
    pub fn serve(args: &ArgMatches) -> Result<(), Box<Error>> {
        const RELOAD_COMMAND: &'static str = "reload";

        let book_dir = get_book_dir(args);
        let book = MDBook::new(&book_dir).read_config()?;

        let mut book = match args.value_of("dest-dir") {
            Some(dest_dir) => book.with_destination(Path::new(dest_dir)),
            None => book,
        };

        if let None = book.get_destination() {
            println!("The HTML renderer is not set up, impossible to serve the files.");
            std::process::exit(2);
        }

        if args.is_present("curly-quotes") {
            book = book.with_curly_quotes(true);
        }

        let port = args.value_of("port").unwrap_or("3000");
        let ws_port = args.value_of("websocket-port").unwrap_or("3001");
        let interface = args.value_of("interface").unwrap_or("localhost");
        let public_address = args.value_of("address").unwrap_or(interface);
        let open_browser = args.is_present("open");

        let address = format!("{}:{}", interface, port);
        let ws_address = format!("{}:{}", interface, ws_port);

        book.set_livereload(format!(r#"
        <script type="text/javascript">
            var socket = new WebSocket("ws://{}:{}");
            socket.onmessage = function (event) {{
                if (event.data === "{}") {{
                    socket.close();
                    location.reload(true); // force reload from server (not from cache)
                }}
            }};

            window.onbeforeunload = function() {{
                socket.close();
            }}
        </script>
    "#,
                                    public_address,
                                    ws_port,
                                    RELOAD_COMMAND));

        book.build()?;

        let mut chain = Chain::new(staticfile::Static::new(book.get_destination()
                                                               .expect("destination is present, checked before")));
        chain.link_after(ErrorRecover);
        let _iron = Iron::new(chain).http(&*address).unwrap();

        let ws_server = ws::WebSocket::new(|_| |_| Ok(())).unwrap();

        let broadcaster = ws_server.broadcaster();

        std::thread::spawn(move || { ws_server.listen(&*ws_address).unwrap(); });

        let serving_url = format!("http://{}", address);
        println!("\nServing on: {}", serving_url);

        if open_browser {
            open(serving_url);
        }

        trigger_on_change(&mut book, move |path, book| {
            println!("File changed: {:?}\nBuilding book...\n", path);
            match book.build() {
                Err(e) => println!("Error while building: {:?}", e),
                _ => broadcaster.send(RELOAD_COMMAND).unwrap(),
            }
            println!("");
        });

        Ok(())
    }
}

fn test(args: &ArgMatches) -> Result<(), Box<Error>> {
    let book_dir = get_book_dir(args);
    let mut book = MDBook::new(&book_dir).read_config()?;

    book.test()?;

    Ok(())
}

fn get_book_dir(args: &ArgMatches) -> PathBuf {
    if let Some(dir) = args.value_of("dir") {
        // Check if path is relative from current dir, or absolute...
        let p = Path::new(dir);
        if p.is_relative() {
            env::current_dir().unwrap().join(dir)
        } else {
            p.to_path_buf()
        }
    } else {
        env::current_dir().unwrap()
    }
}

fn open<P: AsRef<OsStr>>(path: P) {
    if let Err(e) = open::that(path) {
        println!("Error opening web browser: {}", e);
    }
}

// Calls the closure when a book source file is changed. This is blocking!
#[cfg(feature = "watch")]
fn trigger_on_change<F>(book: &mut MDBook, closure: F) -> ()
    where F: Fn(&Path, &mut MDBook) -> ()
{
    use notify::RecursiveMode::*;
    use notify::DebouncedEvent::*;

    // Create a channel to receive the events.
    let (tx, rx) = channel();

    let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) {
        Ok(w) => w,
        Err(e) => {
            println!("Error while trying to watch the files:\n\n\t{:?}", e);
            ::std::process::exit(0);
        },
    };

    // Add the source directory to the watcher
    if let Err(e) = watcher.watch(book.get_source(), Recursive) {
        println!("Error while watching {:?}:\n    {:?}", book.get_source(), e);
        ::std::process::exit(0);
    };

    // Add the theme directory to the watcher
    if let Some(t) = book.get_theme_path() {
        watcher.watch(t, Recursive).unwrap_or_default();
    }

    // Add the book.{json,toml} file to the watcher if it exists, because it's not
    // located in the source directory
    if watcher
           .watch(book.get_root().join("book.json"), NonRecursive)
           .is_err() {
        // do nothing if book.json is not found
    }
    if watcher
           .watch(book.get_root().join("book.toml"), NonRecursive)
           .is_err() {
        // do nothing if book.toml is not found
    }

    println!("\nListening for changes...\n");

    loop {
        match rx.recv() {
            Ok(event) => {
                match event {
                    NoticeWrite(path) |
                    NoticeRemove(path) |
                    Create(path) |
                    Write(path) |
                    Remove(path) |
                    Rename(_, path) => {
                        closure(&path, book);
                    },
                    _ => {},
                }
            },
            Err(e) => {
                println!("An error occured: {:?}", e);
            },
        }
    }
}
<commit_msg>the binary now uses error-chain<commit_after>extern crate mdbook;
#[macro_use]
extern crate clap;
extern crate log;
extern crate env_logger;
extern crate open;

// Dependencies for the Watch feature
#[cfg(feature = "watch")]
extern crate notify;
#[cfg(feature = "watch")]
extern crate time;
#[cfg(feature = "watch")]
extern crate crossbeam;

use std::env;
use std::ffi::OsStr;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use mdbook::errors::*;

use clap::{App, ArgMatches, SubCommand, AppSettings};

// Uses for the Watch feature
#[cfg(feature = "watch")]
use notify::Watcher;
#[cfg(feature = "watch")]
use std::time::Duration;
#[cfg(feature = "watch")]
use std::sync::mpsc::channel;

use mdbook::MDBook;

const NAME: &'static str = "mdbook";

fn main() {
    env_logger::init().unwrap();

    // Create a list of valid arguments and sub-commands
    let matches = App::new(NAME)
                    .about("Create a book in form of a static website from markdown files")
                    .author("Mathieu David <mathieudavid@mathieudavid.org>")
                    // Get the version from our Cargo.toml using clap's crate_version!() macro
                    .version(&*format!("v{}", crate_version!()))
                    .setting(AppSettings::SubcommandRequired)
                    .after_help("For more information about a specific command, try `mdbook <command> --help`\nSource code for mdbook available at: https://github.com/azerupi/mdBook")
                    .subcommand(SubCommand::with_name("init")
                        .about("Create boilerplate structure and files in the directory")
                        // the {n} denotes a newline which will properly aligned in all help messages
                        .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'")
                        .arg_from_usage("--theme 'Copies the default theme into your source folder'")
                        .arg_from_usage("--force 'skip confirmation prompts'"))
                    .subcommand(SubCommand::with_name("build")
                        .about("Build the book from the markdown files")
                        .arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
                        .arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'")
                        .arg_from_usage("--no-create 'Will not create non-existent files linked from SUMMARY.md'")
                        .arg_from_usage("--curly-quotes 'Convert straight quotes to curly quotes, except for those that occur in code blocks and code spans'")
                        .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'"))
                    .subcommand(SubCommand::with_name("watch")
                        .about("Watch the files for changes")
                        .arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
                        .arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'")
                        .arg_from_usage("--curly-quotes 'Convert straight quotes to curly quotes, except for those that occur in code blocks and code spans'")
                        .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'"))
                    .subcommand(SubCommand::with_name("serve")
                        .about("Serve the book at http://localhost:3000. Rebuild and reload on change.")
                        .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'")
                        .arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'")
                        .arg_from_usage("--curly-quotes 'Convert straight quotes to curly quotes, except for those that occur in code blocks and code spans'")
                        .arg_from_usage("-p, --port=[port] 'Use another port{n}(Defaults to 3000)'")
                        .arg_from_usage("-w, --websocket-port=[ws-port] 'Use another port for the websocket connection (livereload){n}(Defaults to 3001)'")
                        .arg_from_usage("-i, --interface=[interface] 'Interface to listen on{n}(Defaults to localhost)'")
                        .arg_from_usage("-a, --address=[address] 'Address that the browser can reach the websocket server from{n}(Defaults to the interface address)'")
                        .arg_from_usage("-o, --open 'Open the book server in a web browser'"))
                    .subcommand(SubCommand::with_name("test")
                        .about("Test that code samples compile"))
                    .get_matches();

    // Check which subcomamnd the user ran...
    let res = match matches.subcommand() {
        ("init", Some(sub_matches)) => init(sub_matches),
        ("build", Some(sub_matches)) => build(sub_matches),
        #[cfg(feature = "watch")]
        ("watch", Some(sub_matches)) => watch(sub_matches),
        #[cfg(feature = "serve")]
        ("serve", Some(sub_matches)) => serve::serve(sub_matches),
        ("test", Some(sub_matches)) => test(sub_matches),
        (_, _) => unreachable!(),
    };

    if let Err(e) = res {
        writeln!(&mut io::stderr(), "An error occured:\n{}", e).ok();
        ::std::process::exit(101);
    }
}

// Simple function that user comfirmation
fn confirm() -> bool {
    io::stdout().flush().unwrap();
    let mut s = String::new();
    io::stdin().read_line(&mut s).ok();
    match &*s.trim() {
        "Y" | "y" | "yes" | "Yes" => true,
        _ => false,
    }
}

// Init command implementation
fn init(args: &ArgMatches) -> Result<()> {

    let book_dir = get_book_dir(args);
    let mut book = MDBook::new(&book_dir);

    // Call the function that does the initialization
    book.init()?;

    // If flag `--theme` is present, copy theme to src
    if args.is_present("theme") {

        // Skip this if `--force` is present
        if !args.is_present("force") {
            // Print warning
            print!("\nCopying the default theme to {:?}", book.get_source());
            println!("could potentially overwrite files already present in that directory.");
            print!("\nAre you sure you want to continue? (y/n) ");

            // Read answer from user and exit if it's not 'yes'
            if !confirm() {
                println!("\nSkipping...\n");
                println!("All done, no errors...");
                ::std::process::exit(0);
            }
        }

        // Call the function that copies the theme
        book.copy_theme()?;
        println!("\nTheme copied.");

    }

    // Because of `src/book/mdbook.rs#L37-L39`, `dest` will always start with `root`
    let is_dest_inside_root = book.get_destination()
                                  .map(|p| p.starts_with(book.get_root()))
                                  .unwrap_or(false);

    if !args.is_present("force") && is_dest_inside_root {
        println!("\nDo you want a .gitignore to be created? (y/n)");

        if confirm() {
            book.create_gitignore();
            println!("\n.gitignore created.");
        }
    }

    println!("\nAll done, no errors...");

    Ok(())
}

// Build command implementation
fn build(args: &ArgMatches) -> Result<()> {
    let book_dir = get_book_dir(args);
    let book = MDBook::new(&book_dir).read_config()?;

    let mut book = match args.value_of("dest-dir") {
        Some(dest_dir) => book.with_destination(dest_dir),
        None => book,
    };

    if args.is_present("no-create") {
        book.create_missing = false;
    }

    if args.is_present("curly-quotes") {
        book = book.with_curly_quotes(true);
    }

    book.build()?;

    if let Some(d) = book.get_destination() {
        if args.is_present("open") {
            open(d.join("index.html"));
        }
    }

    Ok(())
}

// Watch command implementation
#[cfg(feature = "watch")]
fn watch(args: &ArgMatches) -> Result<()> {
    let book_dir = get_book_dir(args);
    let book = MDBook::new(&book_dir).read_config()?;

    let mut book = match args.value_of("dest-dir") {
        Some(dest_dir) => book.with_destination(dest_dir),
        None => book,
    };

    if args.is_present("curly-quotes") {
        book = book.with_curly_quotes(true);
    }

    if args.is_present("open") {
        book.build()?;
        if let Some(d) = book.get_destination() {
            open(d.join("index.html"));
        }
    }

    trigger_on_change(&mut book, |path, book| {
        println!("File changed: {:?}\nBuilding book...\n", path);
        if let Err(e) = book.build() {
            println!("Error while building: {:?}", e);
        }
        println!("");
    });

    Ok(())
}

#[cfg(feature = "serve")]
mod serve {
    extern crate iron;
    extern crate staticfile;
    extern crate ws;

    use std;
    use std::path::Path;
    use mdbook::errors::*;
    use self::iron::{Iron, AfterMiddleware, IronResult, IronError, Request, Response, status, Set, Chain};
    use clap::ArgMatches;
    use mdbook::MDBook;

    use {get_book_dir, open, trigger_on_change};

    struct ErrorRecover;

    impl AfterMiddleware for ErrorRecover {
        fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
            match err.response.status {
                // each error will result in 404 response
                Some(_) => Ok(err.response.set(status::NotFound)),
                _ => Err(err),
            }
        }
    }

    // Watch command implementation
    pub fn serve(args: &ArgMatches) -> Result<()> {
        const RELOAD_COMMAND: &'static str = "reload";

        let book_dir = get_book_dir(args);
        let book = MDBook::new(&book_dir).read_config()?;

        let mut book = match args.value_of("dest-dir") {
            Some(dest_dir) => book.with_destination(Path::new(dest_dir)),
            None => book,
        };

        if let None = book.get_destination() {
            println!("The HTML renderer is not set up, impossible to serve the files.");
            std::process::exit(2);
        }

        if args.is_present("curly-quotes") {
            book = book.with_curly_quotes(true);
        }

        let port = args.value_of("port").unwrap_or("3000");
        let ws_port = args.value_of("websocket-port").unwrap_or("3001");
        let interface = args.value_of("interface").unwrap_or("localhost");
        let public_address = args.value_of("address").unwrap_or(interface);
        let open_browser = args.is_present("open");

        let address = format!("{}:{}", interface, port);
        let ws_address = format!("{}:{}", interface, ws_port);

        book.set_livereload(format!(r#"
        <script type="text/javascript">
            var socket = new WebSocket("ws://{}:{}");
            socket.onmessage = function (event) {{
                if (event.data === "{}") {{
                    socket.close();
                    location.reload(true); // force reload from server (not from cache)
                }}
            }};

            window.onbeforeunload = function() {{
                socket.close();
            }}
        </script>
    "#,
                                    public_address,
                                    ws_port,
                                    RELOAD_COMMAND));

        book.build()?;

        let mut chain = Chain::new(staticfile::Static::new(book.get_destination()
                                                               .expect("destination is present, checked before")));
        chain.link_after(ErrorRecover);
        let _iron = Iron::new(chain).http(&*address).unwrap();

        let ws_server = ws::WebSocket::new(|_| |_| Ok(())).unwrap();

        let broadcaster = ws_server.broadcaster();

        std::thread::spawn(move || { ws_server.listen(&*ws_address).unwrap(); });

        let serving_url = format!("http://{}", address);
        println!("\nServing on: {}", serving_url);

        if open_browser {
            open(serving_url);
        }

        trigger_on_change(&mut book, move |path, book| {
            println!("File changed: {:?}\nBuilding book...\n", path);
            match book.build() {
                Err(e) => println!("Error while building: {:?}", e),
                _ => broadcaster.send(RELOAD_COMMAND).unwrap(),
            }
            println!("");
        });

        Ok(())
    }
}

fn test(args: &ArgMatches) -> Result<()> {
    let book_dir = get_book_dir(args);
    let mut book = MDBook::new(&book_dir).read_config()?;

    book.test()?;

    Ok(())
}

fn get_book_dir(args: &ArgMatches) -> PathBuf {
    if let Some(dir) = args.value_of("dir") {
        // Check if path is relative from current dir, or absolute...
        let p = Path::new(dir);
        if p.is_relative() {
            env::current_dir().unwrap().join(dir)
        } else {
            p.to_path_buf()
        }
    } else {
        env::current_dir().unwrap()
    }
}

fn open<P: AsRef<OsStr>>(path: P) {
    if let Err(e) = open::that(path) {
        println!("Error opening web browser: {}", e);
    }
}

// Calls the closure when a book source file is changed. This is blocking!
#[cfg(feature = "watch")]
fn trigger_on_change<F>(book: &mut MDBook, closure: F) -> ()
    where F: Fn(&Path, &mut MDBook) -> ()
{
    use notify::RecursiveMode::*;
    use notify::DebouncedEvent::*;

    // Create a channel to receive the events.
    let (tx, rx) = channel();

    let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) {
        Ok(w) => w,
        Err(e) => {
            println!("Error while trying to watch the files:\n\n\t{:?}", e);
            ::std::process::exit(0);
        },
    };

    // Add the source directory to the watcher
    if let Err(e) = watcher.watch(book.get_source(), Recursive) {
        println!("Error while watching {:?}:\n    {:?}", book.get_source(), e);
        ::std::process::exit(0);
    };

    // Add the theme directory to the watcher
    if let Some(t) = book.get_theme_path() {
        watcher.watch(t, Recursive).unwrap_or_default();
    }

    // Add the book.{json,toml} file to the watcher if it exists, because it's not
    // located in the source directory
    if watcher
           .watch(book.get_root().join("book.json"), NonRecursive)
           .is_err() {
        // do nothing if book.json is not found
    }
    if watcher
           .watch(book.get_root().join("book.toml"), NonRecursive)
           .is_err() {
        // do nothing if book.toml is not found
    }

    println!("\nListening for changes...\n");

    loop {
        match rx.recv() {
            Ok(event) => {
                match event {
                    NoticeWrite(path) |
                    NoticeRemove(path) |
                    Create(path) |
                    Write(path) |
                    Remove(path) |
                    Rename(_, path) => {
                        closure(&path, book);
                    },
                    _ => {},
                }
            },
            Err(e) => {
                println!("An error occured: {:?}", e);
            },
        }
    }
}
<|endoftext|>


<commit_before>use serde;
use serde_json::Value;

#[derive(Deserialize)]
pub struct DidChangeTextDocumentParams {
    #[serde(rename="textDocument")]
    pub text_document: VersionedTextDocumentIdentifier,
    #[serde(rename="contentChanges")]
    pub content_changes: Vec<TextDocumentContentChangeEvent>,
}

#[derive(Deserialize)]
pub struct TextDocumentIdentifier {
    pub uri: String,
}

#[derive(Deserialize)]
pub struct VersionedTextDocumentIdentifier {
    pub version: u64,
    pub uri: String,
}

#[derive(Deserialize)]
pub struct TextDocumentItem {
    /**
     * The text document's URI.
     */
    pub uri: String,

    /**
     * The text document's language identifier.
     */
    #[serde(rename="languageId")]
    pub language_id: String,

    /**
     * The version number of this document (it will strictly increase after each
     * change, including undo/redo).
     */
    pub version: u64,

    /**
     * The content of the opened text document.
     */
    pub text: String,
}

#[derive(Deserialize)]
pub struct DidOpenTextDocumentParams {
    /**
     * The document that was opened.
     */
    #[serde(rename="textDocument")]
    pub text_document: TextDocumentItem,
}

#[derive(Deserialize)]
pub struct TextDocumentContentChangeEvent {
    pub range: Option<Range>,
    #[serde(rename="rangeLength")]
    pub range_length: Option<u64>,
    pub text: String,
}

#[derive(Copy, Clone, Default, Deserialize, Serialize)]
pub struct Position {
    pub line: u64,
    pub character: u64,
}

#[derive(Copy, Clone, Default, Deserialize, Serialize)]
pub struct Range {
    pub start: Position,
    pub end: Position,
}

#[derive(Deserialize)]
pub struct TextDocumentPositionParams {
    #[serde(rename="textDocument")]
    pub text_document: TextDocumentIdentifier,
    pub position: Position,
}

#[derive(Deserialize)]
pub struct InitializeParams {
    /**
     * The process Id of the parent process that started
     * the server.
     */
    #[serde(rename="processId")]
    pub process_id: u64,

    /**
     * The rootPath of the workspace. Is null
     * if no folder is open.
     */
    #[serde(rename="rootPath")]
    pub root_path: Option<String>,

    /**
     * The capabilities provided by the client (editor)
     */
    pub capabilities: ClientCapabilities,
}

#[derive(Deserialize)]
pub struct ClientCapabilities { _dummy: Option<()> }

#[derive(Default, Serialize)]
pub struct InitializeResult {
    pub capabilities: ServerCapabilities,
}

#[derive(Default, Serialize)]
pub struct InitializeError {
    /**
     * Indicates whether the client should retry to send the
     * initilize request after showing the message provided
     * in the ResponseError.
     */
    pub retry: bool,
}

#[derive(Default, Serialize)]
pub struct ServerCapabilities {
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="textDocumentSync")]
    pub text_document_sync: Option<TextDocumentSyncKind>,
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="completionProvider")]
    pub completion_provider: Option<CompletionOptions>,
}

#[derive(Clone, Copy)]
pub enum TextDocumentSyncKind {
    None = 0,
    Full = 1,
    Incremental = 2,
}

impl serde::Serialize for TextDocumentSyncKind {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
        where S: serde::Serializer
    {
        serializer.serialize_u8(*self as u8)
    }
}

#[derive(Default, Serialize)]
pub struct CompletionOptions {
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="resolveProvider")]
    pub resolve_provider: Option<bool>,
    #[serde(rename="triggerCharacters")]
    pub trigger_characters: Vec<String>,
}

#[derive(Default, Serialize)]
pub struct TextEdit {
    pub range: Range,
    #[serde(rename="newText")]
    pub new_text: String,
}

/**
 * Represents a collection of [completion items](#CompletionItem) to be presented
 * in the editor.
 */
#[derive(Default, Serialize)]
pub struct CompletionList {
    /**
     * This list it not complete. Further typing should result in recomputing
     * this list.
     */
    #[serde(rename="isIncomplete")]
    pub is_incomplete: bool,
    /**
     * The completion items.
     */
    pub items: Vec<CompletionItem>,
}

#[derive(Default, Serialize)]
pub struct CompletionItem {
    /**
     * The label of this completion item. By default
     * also the text that is inserted when selecting
     * this completion.
     */
    pub label: String,
    /**
     * The kind of this completion item. Based of the kind
     * an icon is chosen by the editor.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    pub kind: Option<CompletionItemKind>,
    /**
     * A human-readable string with additional information
     * about this item, like type or symbol information.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    pub detail: Option<String>,
    /**
     * A human-readable string that represents a doc-comment.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    pub documentation: Option<String>,
    /**
     * A string that shoud be used when comparing this item
     * with other items. When `falsy` the label is used.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="sortText")]
    pub sort_text: Option<String>,
    /**
     * A string that should be used when filtering a set of
     * completion items. When `falsy` the label is used.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="filterText")]
    pub filter_text: Option<String>,
    /**
     * A string that should be inserted a document when selecting
     * this completion. When `falsy` the label is used.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="insertText")]
    pub insert_text: Option<String>,
    /**
     * An edit which is applied to a document when selecting
     * this completion. When an edit is provided the value of
     * insertText is ignored.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="textEdit")]
    pub text_edit: Option<TextEdit>,
    /**
     * An data entry field that is preserved on a completion item between
     * a completion and a completion resolve request.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    pub data: Option<Value>,
}

/**
 * The kind of a completion entry.
 */
#[derive(Clone, Copy)]
pub enum CompletionItemKind {
    Text = 1,
    Method = 2,
    Function = 3,
    Constructor = 4,
    Field = 5,
    Variable = 6,
    Class = 7,
    Interface = 8,
    Module = 9,
    Property = 10,
    Unit = 11,
    Value = 12,
    Enum = 13,
    Keyword = 14,
    Snippet = 15,
    Color = 16,
    File = 17,
    Reference = 18,
}

impl serde::Serialize for CompletionItemKind {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
        where S: serde::Serializer
    {
        serializer.serialize_u8(*self as u8)
    }
}

#[derive(Serialize)]
pub struct ShowMessageParams {
    /**
     * The message type. See {@link MessageType}
     */
    #[serde(rename="type")]
    pub typ: MessageType,

    /**
     * The actual message
     */
    pub message: String,
}

#[derive(Serialize)]
pub struct LogMessageParams {
    /**
     * The message type. See {@link MessageType}
     */
    #[serde(rename="type")]
    pub typ: MessageType,

    /**
     * The actual message
     */
    pub message: String,
}

#[derive(Clone, Copy)]
pub enum MessageType {
    /**
     * An error message.
     */
    Error = 1,
    /**
     * A warning message.
     */
    Warning = 2,
    /**
     * An information message.
     */
    Info = 3,
    /**
     * A log message.
     */
    Log = 4,
}

impl serde::Serialize for MessageType {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
        where S: serde::Serializer
    {
        serializer.serialize_u8(*self as u8)
    }
}

#[derive(Default, Serialize)]
pub struct PublishDiagnosticsParams {
    /**
     * The URI for which diagnostic information is reported.
     */
    pub uri: String,

    /**
     * An array of diagnostic information items.
     */
    pub diagnostics: Vec<Diagnostic>,
}

#[derive(Default, Serialize)]
pub struct Diagnostic {
    /**
     * The range at which the message applies
     */
    pub range: Range,

    /**
     * The diagnostic's severity. Can be omitted. If omitted it is up to the
     * client to interpret diagnostics as error, warning, info or hint.
     */
    pub severity: Option<DiagnosticSeverity>,

    /**
     * The diagnostic's code. Can be omitted.
     */
    pub code: String, // number | string;

    /**
     * A human-readable string describing the source of this
     * diagnostic, e.g. 'typescript' or 'super lint'.
     */
    pub source: Option<String>,

    /**
     * The diagnostic's message.
     */
    pub message: String,
}

#[derive(Clone, Copy)]
pub enum DiagnosticSeverity {
    /**
     * Reports an error.
     */
    Error = 1,
    /**
     * Reports a warning.
     */
    Warning = 2,
    /**
     * Reports an information.
     */
    Information = 3,
    /**
     * Reports a hint.
     */
    Hint = 4,
}

impl serde::Serialize for DiagnosticSeverity {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
        where S: serde::Serializer
    {
        serializer.serialize_u8(*self as u8)
    }
}
<commit_msg>feat: Fill out the ServerCapabilities struct and add Hover<commit_after>use serde;
use serde_json::Value;

#[derive(Deserialize)]
pub struct DidChangeTextDocumentParams {
    #[serde(rename="textDocument")]
    pub text_document: VersionedTextDocumentIdentifier,
    #[serde(rename="contentChanges")]
    pub content_changes: Vec<TextDocumentContentChangeEvent>,
}

#[derive(Deserialize)]
pub struct TextDocumentIdentifier {
    pub uri: String,
}

#[derive(Deserialize)]
pub struct VersionedTextDocumentIdentifier {
    pub version: u64,
    pub uri: String,
}

#[derive(Deserialize)]
pub struct TextDocumentItem {
    /**
     * The text document's URI.
     */
    pub uri: String,

    /**
     * The text document's language identifier.
     */
    #[serde(rename="languageId")]
    pub language_id: String,

    /**
     * The version number of this document (it will strictly increase after each
     * change, including undo/redo).
     */
    pub version: u64,

    /**
     * The content of the opened text document.
     */
    pub text: String,
}

#[derive(Deserialize)]
pub struct DidOpenTextDocumentParams {
    /**
     * The document that was opened.
     */
    #[serde(rename="textDocument")]
    pub text_document: TextDocumentItem,
}

#[derive(Deserialize)]
pub struct TextDocumentContentChangeEvent {
    pub range: Option<Range>,
    #[serde(rename="rangeLength")]
    pub range_length: Option<u64>,
    pub text: String,
}

#[derive(Copy, Clone, Default, Deserialize, Serialize)]
pub struct Position {
    pub line: u64,
    pub character: u64,
}

#[derive(Copy, Clone, Default, Deserialize, Serialize)]
pub struct Range {
    pub start: Position,
    pub end: Position,
}

#[derive(Deserialize)]
pub struct TextDocumentPositionParams {
    #[serde(rename="textDocument")]
    pub text_document: TextDocumentIdentifier,
    pub position: Position,
}

#[derive(Deserialize)]
pub struct InitializeParams {
    /**
     * The process Id of the parent process that started
     * the server.
     */
    #[serde(rename="processId")]
    pub process_id: u64,

    /**
     * The rootPath of the workspace. Is null
     * if no folder is open.
     */
    #[serde(rename="rootPath")]
    pub root_path: Option<String>,

    /**
     * The capabilities provided by the client (editor)
     */
    pub capabilities: ClientCapabilities,
}

#[derive(Deserialize)]
pub struct ClientCapabilities {
    _dummy: Option<()>,
}

#[derive(Default, Serialize)]
pub struct InitializeResult {
    pub capabilities: ServerCapabilities,
}

#[derive(Default, Serialize)]
pub struct InitializeError {
    /**
     * Indicates whether the client should retry to send the
     * initilize request after showing the message provided
     * in the ResponseError.
     */
    pub retry: bool,
}

#[derive(Default, Serialize)]
pub struct ServerCapabilities {
    /**
     * Defines how text documents are synced.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="textDocumentSync")]
    pub text_document_sync: Option<TextDocumentSyncKind>,
    /**
     * The server provides hover support.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="hoverProvider")]
    pub hover_provider: Option<bool>,
    /**
     * The server provides completion support.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="completionProvider")]
    pub completion_provider: Option<CompletionOptions>,
    /**
     * The server provides signature help support.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="signatureHelpProvider")]
    pub signature_help_provider: Option<SignatureHelpOptions>,
    /**
     * The server provides goto definition support.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="definitionProvider")]
    pub definition_provider: Option<bool>,
    /**
     * The server provides find references support.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="referencesProvider")]
    pub references_provider: Option<bool>,
    /**
     * The server provides document highlight support.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="documentHighlightProvider")]
    pub document_highlight_provider: Option<bool>,
    /**
     * The server provides document symbol support.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="documentSymbolProvider")]
    pub document_symbol_provider: Option<bool>,
    /**
     * The server provides workspace symbol support.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="workspaceSymbolProvider")]
    pub workspace_symbol_provider: Option<bool>,
    /**
     * The server provides code actions.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="codeActionProvider")]
    pub code_action_provider: Option<bool>,
    /**
     * The server provides code lens.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="codeLensProvider")]
    pub code_lens_provider: Option<CodeLensOptions>,
    /**
     * The server provides document formatting.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="documentFormattingProvider")]
    pub document_formatting_provider: Option<bool>,
    /**
     * The server provides document range formatting.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="documentRangeFormattingProvider")]
    pub document_range_formatting_provider: Option<bool>,
    /**
     * The server provides document formatting on typing.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="documentOnTypeFormattingProvider")]
    pub document_on_type_formatting_provider: Option<DocumentOnTypeFormattingOptions>,
    /**
     * The server provides rename support.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="renameProvider")]
    pub rename_provider: Option<bool>,
}

#[derive(Clone, Copy)]
pub enum TextDocumentSyncKind {
    None = 0,
    Full = 1,
    Incremental = 2,
}

impl serde::Serialize for TextDocumentSyncKind {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
        where S: serde::Serializer
    {
        serializer.serialize_u8(*self as u8)
    }
}

#[derive(Default, Serialize)]
pub struct CompletionOptions {
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="resolveProvider")]
    pub resolve_provider: Option<bool>,
    #[serde(rename="triggerCharacters")]
    pub trigger_characters: Vec<String>,
}

/**
 * Signature help options.
 */
#[derive(Serialize)]
pub struct SignatureHelpOptions {
    /**
     * The characters that trigger signature help automatically.
     */
    #[serde(skip_serializing_if="Vec::is_empty")]
    #[serde(rename="triggerCharacters")]
    pub trigger_characters: Vec<String>,
}

/**
 * Code Lens options.
 */
#[derive(Serialize)]
pub struct CodeLensOptions {
    /**
     * Code lens has a resolve provider as well.
     */
    #[serde(rename="resolveProvider")]
    pub resolve_provider: Option<bool>,
}

/**
 * Format document on type options
 */
#[derive(Serialize)]
pub struct DocumentOnTypeFormattingOptions {
    /**
     * A character on which formatting should be triggered, like `}`.
     */
    #[serde(rename="firstTriggerCharacter")]
    pub first_trigger_character: String,
    /**
     * More trigger characters.
     */
    #[serde(skip_serializing_if="Vec::is_empty")]
    #[serde(rename="moreTriggerCharacter")]
    pub more_trigger_character: Vec<String>,
}

#[derive(Default, Serialize)]
pub struct TextEdit {
    pub range: Range,
    #[serde(rename="newText")]
    pub new_text: String,
}

/**
 * Represents a collection of [completion items](#CompletionItem) to be presented
 * in the editor.
 */
#[derive(Default, Serialize)]
pub struct CompletionList {
    /**
     * This list it not complete. Further typing should result in recomputing
     * this list.
     */
    #[serde(rename="isIncomplete")]
    pub is_incomplete: bool,
    /**
     * The completion items.
     */
    pub items: Vec<CompletionItem>,
}

#[derive(Default, Serialize)]
pub struct CompletionItem {
    /**
     * The label of this completion item. By default
     * also the text that is inserted when selecting
     * this completion.
     */
    pub label: String,
    /**
     * The kind of this completion item. Based of the kind
     * an icon is chosen by the editor.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    pub kind: Option<CompletionItemKind>,
    /**
     * A human-readable string with additional information
     * about this item, like type or symbol information.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    pub detail: Option<String>,
    /**
     * A human-readable string that represents a doc-comment.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    pub documentation: Option<String>,
    /**
     * A string that shoud be used when comparing this item
     * with other items. When `falsy` the label is used.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="sortText")]
    pub sort_text: Option<String>,
    /**
     * A string that should be used when filtering a set of
     * completion items. When `falsy` the label is used.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="filterText")]
    pub filter_text: Option<String>,
    /**
     * A string that should be inserted a document when selecting
     * this completion. When `falsy` the label is used.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="insertText")]
    pub insert_text: Option<String>,
    /**
     * An edit which is applied to a document when selecting
     * this completion. When an edit is provided the value of
     * insertText is ignored.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="textEdit")]
    pub text_edit: Option<TextEdit>,
    /**
     * An data entry field that is preserved on a completion item between
     * a completion and a completion resolve request.
     */
    #[serde(skip_serializing_if="Option::is_none")]
    pub data: Option<Value>,
}

/**
 * The kind of a completion entry.
 */
#[derive(Clone, Copy)]
pub enum CompletionItemKind {
    Text = 1,
    Method = 2,
    Function = 3,
    Constructor = 4,
    Field = 5,
    Variable = 6,
    Class = 7,
    Interface = 8,
    Module = 9,
    Property = 10,
    Unit = 11,
    Value = 12,
    Enum = 13,
    Keyword = 14,
    Snippet = 15,
    Color = 16,
    File = 17,
    Reference = 18,
}

impl serde::Serialize for CompletionItemKind {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
        where S: serde::Serializer
    {
        serializer.serialize_u8(*self as u8)
    }
}

/**
 * The result of a hove request.
 */
#[derive(Serialize)]
pub struct Hover {
    /**
     * The hover's content
     */
    pub contents: Vec<MarkedString>,

    /**
     * An optional range
     */
    pub range: Option<Range>,
}

pub enum MarkedString {
    String(String),
    LanguageString {
        language: String,
        value: String,
    },
}

impl serde::Serialize for MarkedString {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
        where S: serde::Serializer
    {
        match *self {
            MarkedString::String(ref s) => serializer.serialize_str(s),
            MarkedString::LanguageString { ref language, ref value } => {
                #[derive(Serialize)]
                struct Variant<'s> {
                    language: &'s str,
                    value: &'s str,
                }
                Variant {
                        language: language,
                        value: value,
                    }
                    .serialize(serializer)
            }
        }
    }
}

#[derive(Serialize)]
pub struct ShowMessageParams {
    /**
     * The message type. See {@link MessageType}
     */
    #[serde(rename="type")]
    pub typ: MessageType,

    /**
     * The actual message
     */
    pub message: String,
}

#[derive(Serialize)]
pub struct LogMessageParams {
    /**
     * The message type. See {@link MessageType}
     */
    #[serde(rename="type")]
    pub typ: MessageType,

    /**
     * The actual message
     */
    pub message: String,
}

#[derive(Clone, Copy)]
pub enum MessageType {
    /**
     * An error message.
     */
    Error = 1,
    /**
     * A warning message.
     */
    Warning = 2,
    /**
     * An information message.
     */
    Info = 3,
    /**
     * A log message.
     */
    Log = 4,
}

impl serde::Serialize for MessageType {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
        where S: serde::Serializer
    {
        serializer.serialize_u8(*self as u8)
    }
}

#[derive(Default, Serialize)]
pub struct PublishDiagnosticsParams {
    /**
     * The URI for which diagnostic information is reported.
     */
    pub uri: String,

    /**
     * An array of diagnostic information items.
     */
    pub diagnostics: Vec<Diagnostic>,
}

#[derive(Default, Serialize)]
pub struct Diagnostic {
    /**
     * The range at which the message applies
     */
    pub range: Range,

    /**
     * The diagnostic's severity. Can be omitted. If omitted it is up to the
     * client to interpret diagnostics as error, warning, info or hint.
     */
    pub severity: Option<DiagnosticSeverity>,

    /**
     * The diagnostic's code. Can be omitted.
     */
    pub code: String, // number | string;

    /**
     * A human-readable string describing the source of this
     * diagnostic, e.g. 'typescript' or 'super lint'.
     */
    pub source: Option<String>,

    /**
     * The diagnostic's message.
     */
    pub message: String,
}

#[derive(Clone, Copy)]
pub enum DiagnosticSeverity {
    /**
     * Reports an error.
     */
    Error = 1,
    /**
     * Reports a warning.
     */
    Warning = 2,
    /**
     * Reports an information.
     */
    Information = 3,
    /**
     * Reports a hint.
     */
    Hint = 4,
}

impl serde::Serialize for DiagnosticSeverity {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
        where S: serde::Serializer
    {
        serializer.serialize_u8(*self as u8)
    }
}
<|endoftext|>