<commit_before>use utils;
use errors::*;
use temp;
use env_var;
use dist;
#[cfg(windows)]
use msi;

use std::path::{Path, PathBuf};
use std::process::Command;
use std::ffi::OsStr;
use std::fs;
use std::io;
use std::env;

const REL_MANIFEST_DIR: &'static str = "rustlib";

pub struct InstallPrefix {
	path: PathBuf,
	install_type: InstallType,
}

#[derive(Eq, PartialEq, Copy, Clone)]
pub enum InstallType {
	// Must be uninstalled by deleting the entire directory
	Owned,
	// Must be uninstalled via `uninstall.sh` on linux or `msiexec /x` on windows
	Shared,
}

pub enum Uninstaller {
	Sh(PathBuf),
	Msi(String),
}

impl Uninstaller {
	pub fn run(&self) -> Result<()> {
		match *self {
			Uninstaller::Sh(ref path) => {
				let mut cmd = Command::new("sudo");
				cmd.arg("sh").arg(path);
				utils::cmd_status("uninstall.sh", cmd)
			},
			Uninstaller::Msi(ref id) => {
				let mut cmd = Command::new("msiexec");
				cmd.arg("/x").arg(id);
				utils::cmd_status("msiexec", cmd)
			},
		}
	}
}

pub enum InstallMethod<'a> {
	Copy(&'a Path),
	Link(&'a Path),
	Installer(&'a Path, &'a temp::Cfg),
	Dist(&'a str, Option<&'a Path>, dist::DownloadCfg<'a>),
}

impl<'a> InstallMethod<'a> {
	pub fn install_type_possible(&self, install_type: InstallType) -> bool {
		match *self {
			InstallMethod::Copy(_)|InstallMethod::Link(_) => install_type == InstallType::Owned,
			InstallMethod::Installer(_, _)|InstallMethod::Dist(_,_,_) => true,
		}
	}
	pub fn run(self, prefix: &InstallPrefix, notify_handler: &NotifyHandler) -> Result<()> {
		if prefix.is_installed_here() {
			// Don't uninstall first for Dist method
			match self {
				InstallMethod::Dist(_,_,_) => {},
				_ => { try!(prefix.uninstall(notify_handler)); },
			}
		}

		if !self.install_type_possible(prefix.install_type) {
			return Err(Error::InstallTypeNotPossible);
		}

		match self {
			InstallMethod::Copy(src) => {
				utils::copy_dir(src, &prefix.path, notify_handler)
			},
			InstallMethod::Link(src) => {
				utils::symlink_dir(&prefix.path, src, notify_handler)
			},
			InstallMethod::Installer(src, temp_cfg) => {
				let temp_dir = try!(temp_cfg.new_directory());
				match src.extension().and_then(OsStr::to_str) {
					Some("gz") => InstallMethod::tar_gz(src, &temp_dir, prefix),
					Some("msi") => InstallMethod::msi(src, &temp_dir, prefix),
					_ => Err(Error::InvalidFileExtension),
				}
			},
			InstallMethod::Dist(toolchain, update_hash, dl_cfg) => {
				if let Some(installer) = try!(dist::download_dist(toolchain, update_hash, dl_cfg)) {
					try!(InstallMethod::Installer(&*installer, dl_cfg.temp_cfg).run(prefix, dl_cfg.notify_handler));
				}
				Ok(())
			}
		}
	}

	fn tar_gz(src: &Path, work_dir: &Path, prefix: &InstallPrefix) -> Result<()> {
		let installer_dir = Path::new(try!(Path::new(src.file_stem().unwrap()).file_stem()
			.ok_or(Error::InvalidFileExtension)));

		let mut cmd = Command::new("tar");
		cmd
			.arg("xzf").arg(src)
			.arg("-C").arg(work_dir);

		try!(utils::cmd_status("tar", cmd));

		let mut cmd = Command::new("sh");
		cmd
			.arg(installer_dir.join("install.sh"))
			.arg("--prefix").arg(&prefix.path);

		if prefix.install_type != InstallType::Shared {
			cmd.arg("--disable-ldconfig");
		}

		let result = utils::cmd_status("sh", cmd);

		if result.is_err() && prefix.install_type == InstallType::Owned {
			let _ = fs::remove_dir_all(&prefix.path);
		}

		result
	}
	fn msi(src: &Path, work_dir: &Path, prefix: &InstallPrefix) -> Result<()> {
		let msi_owned = || -> Result<()> {
			let target_arg = utils::prefix_arg("TARGETDIR=", work_dir);

			// Extract the MSI to the subfolder
			let mut cmd = Command::new("msiexec");
			cmd
				.arg("/a").arg(src)
				.arg("/qn")
				.arg(&target_arg);

			try!(utils::cmd_status("msiexec", cmd));

			// Find the root Rust folder within the subfolder
			let root_dir = try!(try!(utils::read_dir("install", work_dir))
				.filter_map(io::Result::ok)
				.map(|e| e.path())
				.filter(|p| utils::is_directory(&p))
				.next()
				.ok_or(Error::InvalidInstaller));

			// Rename and move it to the toolchain directory
			utils::rename_dir("install", &root_dir, &prefix.path)
		};

		let msi_shared = || -> Result<()> {
			let target_arg = utils::prefix_arg("TARGETDIR=", &prefix.path);

			// Extract the MSI to the subfolder
			let mut cmd = Command::new("msiexec");
			cmd
				.arg("/i").arg(src)
				.arg("/qn")
				.arg(&target_arg);

			utils::cmd_status("msiexec", cmd)
		};

		match prefix.install_type {
			InstallType::Owned => {
				let result = msi_owned();
				if result.is_err() {
					let _ = fs::remove_dir_all(&prefix.path);
				}
				result
			},
			InstallType::Shared => {
				msi_shared()
			}
		}
	}
}

pub fn bin_path(name: &str) -> PathBuf {
	let mut path = PathBuf::from("bin");
	path.push(name.to_owned() + env::consts::EXE_SUFFIX);
	path
}

impl InstallPrefix {
	pub fn from(path: PathBuf, install_type: InstallType) -> Self {
		InstallPrefix {
			path: path,
			install_type: install_type,
		}
	}
	pub fn path(&self) -> &Path {
		&self.path
	}
	pub fn manifest_dir(&self) -> PathBuf {
		let mut path = self.path.clone();
		path.push("bin");
		path.push(REL_MANIFEST_DIR);
		path
	}
	pub fn manifest_file(&self, name: &str) -> PathBuf {
		let mut path = self.manifest_dir();
		path.push(name);
		path
	}
	pub fn binary_file(&self, name: &str) -> PathBuf {
		let mut path = self.path.clone();
		path.push(bin_path(name));
		path
	}
	pub fn doc_path(&self, relative: &str) -> Result<PathBuf> {
		let parts = vec!["share", "doc", "rust", "html"];
		let mut doc_dir = self.path.clone();
		for part in parts {
			doc_dir.push(part);
		}
		doc_dir.push(relative);

		Ok(doc_dir)
	}
	pub fn is_installed_here(&self) -> bool {
		utils::is_directory(&self.manifest_dir())
	}
	pub fn get_uninstall_sh(&self) -> Option<PathBuf> {
		let path = self.manifest_file("uninstall.sh");
		if utils::is_file(&path) {
			Some(path)
		} else {
			None
		}
	}

	#[cfg(windows)]
	pub fn get_uninstall_msi(&self, notify_handler: &NotifyHandler) -> Option<String> {
		let canon_path = utils::canonicalize_path(&self.path, notify_handler);

		if let Ok(installers) = msi::all_installers() {
			for installer in &installers {
				if let Ok(loc) = installer.install_location() {
					let path = utils::canonicalize_path(&loc, notify_handler);

					if path == canon_path {
						return Some(installer.product_id().to_owned());
					}
				}
			}
		}

		None
	}
	#[cfg(not(windows))]
	pub fn get_uninstall_msi(&self, notify_handler: &NotifyHandler) -> Option<String> {
		None
	}
	pub fn get_uninstaller(&self, notify_handler: &NotifyHandler) -> Option<Uninstaller> {
		self.get_uninstall_sh().map(Uninstaller::Sh).or_else(
			|| self.get_uninstall_msi(notify_handler).map(Uninstaller::Msi)
			)
	}
	pub fn uninstall(&self, notify_handler: &NotifyHandler) -> Result<()> {
		if self.is_installed_here() {
			match self.install_type {
				InstallType::Owned => {
					utils::remove_dir("install", &self.path, notify_handler)
				},
				InstallType::Shared => {
					if let Some(uninstaller) = self.get_uninstaller(notify_handler) {
						uninstaller.run()
					} else {
						Err(Error::NotInstalledHere)
					}
				},
			}
		} else {
			Err(Error::NotInstalledHere)
		}
	}
	pub fn install(&self, method: InstallMethod, notify_handler: &NotifyHandler) -> Result<()> {
		method.run(self, notify_handler)
	}

	pub fn with_ldpath<T, F: FnOnce() -> Result<T>>(&self, f: F) -> Result<T> {
		let new_path = self.path.join("lib");

		env_var::with_path("LD_LIBRARY_PATH", &new_path, || {
			env_var::with_path("DYLD_LIBRARY_PATH", &new_path, || {
				f()
			})
		})
	}

	pub fn with_env<T, F: FnOnce() -> Result<T>>(&self, f: F) -> Result<T> {
		let cargo_path = self.path.join("cargo");

		self.with_ldpath(|| {
			env_var::with_default("CARGO_HOME", cargo_path.as_ref(), || {
				f()
			})
		})
	}

	pub fn create_command(&self, binary: &str) -> Result<Command> {
		let binary_path = self.binary_file(binary);

		self.with_env(|| Ok(Command::new(binary_path)))
	}

	pub fn open_docs(&self, relative: &str) -> Result<()> {
		utils::open_browser(&try!(self.doc_path(relative)))
	}
}
<commit_msg>Fix unused parameter warning<commit_after>use utils;
use errors::*;
use temp;
use env_var;
use dist;
#[cfg(windows)]
use msi;

use std::path::{Path, PathBuf};
use std::process::Command;
use std::ffi::OsStr;
use std::fs;
use std::io;
use std::env;

const REL_MANIFEST_DIR: &'static str = "rustlib";

pub struct InstallPrefix {
	path: PathBuf,
	install_type: InstallType,
}

#[derive(Eq, PartialEq, Copy, Clone)]
pub enum InstallType {
	// Must be uninstalled by deleting the entire directory
	Owned,
	// Must be uninstalled via `uninstall.sh` on linux or `msiexec /x` on windows
	Shared,
}

pub enum Uninstaller {
	Sh(PathBuf),
	Msi(String),
}

impl Uninstaller {
	pub fn run(&self) -> Result<()> {
		match *self {
			Uninstaller::Sh(ref path) => {
				let mut cmd = Command::new("sudo");
				cmd.arg("sh").arg(path);
				utils::cmd_status("uninstall.sh", cmd)
			},
			Uninstaller::Msi(ref id) => {
				let mut cmd = Command::new("msiexec");
				cmd.arg("/x").arg(id);
				utils::cmd_status("msiexec", cmd)
			},
		}
	}
}

pub enum InstallMethod<'a> {
	Copy(&'a Path),
	Link(&'a Path),
	Installer(&'a Path, &'a temp::Cfg),
	Dist(&'a str, Option<&'a Path>, dist::DownloadCfg<'a>),
}

impl<'a> InstallMethod<'a> {
	pub fn install_type_possible(&self, install_type: InstallType) -> bool {
		match *self {
			InstallMethod::Copy(_)|InstallMethod::Link(_) => install_type == InstallType::Owned,
			InstallMethod::Installer(_, _)|InstallMethod::Dist(_,_,_) => true,
		}
	}
	pub fn run(self, prefix: &InstallPrefix, notify_handler: &NotifyHandler) -> Result<()> {
		if prefix.is_installed_here() {
			// Don't uninstall first for Dist method
			match self {
				InstallMethod::Dist(_,_,_) => {},
				_ => { try!(prefix.uninstall(notify_handler)); },
			}
		}

		if !self.install_type_possible(prefix.install_type) {
			return Err(Error::InstallTypeNotPossible);
		}

		match self {
			InstallMethod::Copy(src) => {
				utils::copy_dir(src, &prefix.path, notify_handler)
			},
			InstallMethod::Link(src) => {
				utils::symlink_dir(&prefix.path, src, notify_handler)
			},
			InstallMethod::Installer(src, temp_cfg) => {
				let temp_dir = try!(temp_cfg.new_directory());
				match src.extension().and_then(OsStr::to_str) {
					Some("gz") => InstallMethod::tar_gz(src, &temp_dir, prefix),
					Some("msi") => InstallMethod::msi(src, &temp_dir, prefix),
					_ => Err(Error::InvalidFileExtension),
				}
			},
			InstallMethod::Dist(toolchain, update_hash, dl_cfg) => {
				if let Some(installer) = try!(dist::download_dist(toolchain, update_hash, dl_cfg)) {
					try!(InstallMethod::Installer(&*installer, dl_cfg.temp_cfg).run(prefix, dl_cfg.notify_handler));
				}
				Ok(())
			}
		}
	}

	fn tar_gz(src: &Path, work_dir: &Path, prefix: &InstallPrefix) -> Result<()> {
		let installer_dir = Path::new(try!(Path::new(src.file_stem().unwrap()).file_stem()
			.ok_or(Error::InvalidFileExtension)));

		let mut cmd = Command::new("tar");
		cmd
			.arg("xzf").arg(src)
			.arg("-C").arg(work_dir);

		try!(utils::cmd_status("tar", cmd));

		let mut cmd = Command::new("sh");
		cmd
			.arg(installer_dir.join("install.sh"))
			.arg("--prefix").arg(&prefix.path);

		if prefix.install_type != InstallType::Shared {
			cmd.arg("--disable-ldconfig");
		}

		let result = utils::cmd_status("sh", cmd);

		if result.is_err() && prefix.install_type == InstallType::Owned {
			let _ = fs::remove_dir_all(&prefix.path);
		}

		result
	}
	fn msi(src: &Path, work_dir: &Path, prefix: &InstallPrefix) -> Result<()> {
		let msi_owned = || -> Result<()> {
			let target_arg = utils::prefix_arg("TARGETDIR=", work_dir);

			// Extract the MSI to the subfolder
			let mut cmd = Command::new("msiexec");
			cmd
				.arg("/a").arg(src)
				.arg("/qn")
				.arg(&target_arg);

			try!(utils::cmd_status("msiexec", cmd));

			// Find the root Rust folder within the subfolder
			let root_dir = try!(try!(utils::read_dir("install", work_dir))
				.filter_map(io::Result::ok)
				.map(|e| e.path())
				.filter(|p| utils::is_directory(&p))
				.next()
				.ok_or(Error::InvalidInstaller));

			// Rename and move it to the toolchain directory
			utils::rename_dir("install", &root_dir, &prefix.path)
		};

		let msi_shared = || -> Result<()> {
			let target_arg = utils::prefix_arg("TARGETDIR=", &prefix.path);

			// Extract the MSI to the subfolder
			let mut cmd = Command::new("msiexec");
			cmd
				.arg("/i").arg(src)
				.arg("/qn")
				.arg(&target_arg);

			utils::cmd_status("msiexec", cmd)
		};

		match prefix.install_type {
			InstallType::Owned => {
				let result = msi_owned();
				if result.is_err() {
					let _ = fs::remove_dir_all(&prefix.path);
				}
				result
			},
			InstallType::Shared => {
				msi_shared()
			}
		}
	}
}

pub fn bin_path(name: &str) -> PathBuf {
	let mut path = PathBuf::from("bin");
	path.push(name.to_owned() + env::consts::EXE_SUFFIX);
	path
}

impl InstallPrefix {
	pub fn from(path: PathBuf, install_type: InstallType) -> Self {
		InstallPrefix {
			path: path,
			install_type: install_type,
		}
	}
	pub fn path(&self) -> &Path {
		&self.path
	}
	pub fn manifest_dir(&self) -> PathBuf {
		let mut path = self.path.clone();
		path.push("bin");
		path.push(REL_MANIFEST_DIR);
		path
	}
	pub fn manifest_file(&self, name: &str) -> PathBuf {
		let mut path = self.manifest_dir();
		path.push(name);
		path
	}
	pub fn binary_file(&self, name: &str) -> PathBuf {
		let mut path = self.path.clone();
		path.push(bin_path(name));
		path
	}
	pub fn doc_path(&self, relative: &str) -> Result<PathBuf> {
		let parts = vec!["share", "doc", "rust", "html"];
		let mut doc_dir = self.path.clone();
		for part in parts {
			doc_dir.push(part);
		}
		doc_dir.push(relative);

		Ok(doc_dir)
	}
	pub fn is_installed_here(&self) -> bool {
		utils::is_directory(&self.manifest_dir())
	}
	pub fn get_uninstall_sh(&self) -> Option<PathBuf> {
		let path = self.manifest_file("uninstall.sh");
		if utils::is_file(&path) {
			Some(path)
		} else {
			None
		}
	}

	#[cfg(windows)]
	pub fn get_uninstall_msi(&self, notify_handler: &NotifyHandler) -> Option<String> {
		let canon_path = utils::canonicalize_path(&self.path, notify_handler);

		if let Ok(installers) = msi::all_installers() {
			for installer in &installers {
				if let Ok(loc) = installer.install_location() {
					let path = utils::canonicalize_path(&loc, notify_handler);

					if path == canon_path {
						return Some(installer.product_id().to_owned());
					}
				}
			}
		}

		None
	}
	#[cfg(not(windows))]
	pub fn get_uninstall_msi(&self, _: &NotifyHandler) -> Option<String> {
		None
	}
	pub fn get_uninstaller(&self, notify_handler: &NotifyHandler) -> Option<Uninstaller> {
		self.get_uninstall_sh().map(Uninstaller::Sh).or_else(
			|| self.get_uninstall_msi(notify_handler).map(Uninstaller::Msi)
			)
	}
	pub fn uninstall(&self, notify_handler: &NotifyHandler) -> Result<()> {
		if self.is_installed_here() {
			match self.install_type {
				InstallType::Owned => {
					utils::remove_dir("install", &self.path, notify_handler)
				},
				InstallType::Shared => {
					if let Some(uninstaller) = self.get_uninstaller(notify_handler) {
						uninstaller.run()
					} else {
						Err(Error::NotInstalledHere)
					}
				},
			}
		} else {
			Err(Error::NotInstalledHere)
		}
	}
	pub fn install(&self, method: InstallMethod, notify_handler: &NotifyHandler) -> Result<()> {
		method.run(self, notify_handler)
	}

	pub fn with_ldpath<T, F: FnOnce() -> Result<T>>(&self, f: F) -> Result<T> {
		let new_path = self.path.join("lib");

		env_var::with_path("LD_LIBRARY_PATH", &new_path, || {
			env_var::with_path("DYLD_LIBRARY_PATH", &new_path, || {
				f()
			})
		})
	}

	pub fn with_env<T, F: FnOnce() -> Result<T>>(&self, f: F) -> Result<T> {
		let cargo_path = self.path.join("cargo");

		self.with_ldpath(|| {
			env_var::with_default("CARGO_HOME", cargo_path.as_ref(), || {
				f()
			})
		})
	}

	pub fn create_command(&self, binary: &str) -> Result<Command> {
		let binary_path = self.binary_file(binary);

		self.with_env(|| Ok(Command::new(binary_path)))
	}

	pub fn open_docs(&self, relative: &str) -> Result<()> {
		utils::open_browser(&try!(self.doc_path(relative)))
	}
}
<|endoftext|>


<commit_before>#![ cfg_attr( not( feature = "use_std" ), no_std ) ]
#![ doc( html_logo_url = "https://raw.githubusercontent.com/Wandalen/wTools/master/asset/img/logo_v3_trans_square.png" ) ]
#![ doc( html_favicon_url = "https://raw.githubusercontent.com/Wandalen/wTools/alpha/asset/img/logo_v3_trans_square_icon_small_v2.ico" ) ]
#![ doc( html_root_url = "https://docs.rs/wplot/latest/wplot/" ) ]
#![ warn( rust_2018_idioms ) ]
#![ warn( missing_debug_implementations ) ]
#![ warn( missing_docs ) ]

#![ feature( trace_macros ) ]

//!
//! Collection of general purpose tools to iterate. Currently it simply reexport itertools.
//!

#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/Readme.md" ) ) ]

pub use wmath as math;
pub use wtools::prelude::*;

/// Describe colors.
pub mod color;
/// Abstraction.
#[ cfg( feature = "use_std" ) ]
pub mod abs;
/// Concrete system.
#[ cfg( feature = "use_std" ) ]
pub mod sys;

// /// Describe change.
// pub mod change;
// /// Describe changer.
// pub mod changer;
// /// Describe colors.
// pub mod color;
// /// Main aggregating object.
// pub mod context;
// /// Context changer.
// pub mod context_changer;
//
// /// Draw changer.
// pub mod drawing;
// /// Draw changer.
// pub mod drawing_changer;
// /// ChangeInterface for drawing constructor.
// pub mod drawing_change_new;
//
// /// Identity of resource.
// pub mod identity;
// /// Registry.
// pub mod registry;
// /// Brush stroke.
// pub mod stroke_brush;
// /// ChangerInterface of brush stroke.
// pub mod stroke_brush_changer;
// /// ChangeInterface of brush stroke constructor.
// pub mod stroke_brush_change_new;
// /// ChangeInterface of brush stroke color.
// pub mod stroke_brush_change_color;
// /// Target to draw.
// pub mod target;

/// Namespace with dependencies.
pub mod dependency
{
  pub use ::image;
  pub use ::open;
  pub use ::wmath;
  pub use ::rgb;
}

/// Protected namespace of the module.
pub mod protected
{
  pub use super::orphan::*;
  #[ cfg( feature = "use_std" ) ]
  pub use super::
  {
    sys::orphan::*,
    abs::orphan::*,
    color::orphan::*,
  };
}

pub use protected::*;

/// Parented namespace of the module.
pub mod orphan
{
  pub use super::exposed::*;
}

/// Exposed namespace of the module.
pub mod exposed
{
  pub use super::prelude::*;
  #[ cfg( feature = "use_std" ) ]
  pub use super::
  {
    sys::exposed::*,
    abs::exposed::*,
    color::exposed::*,
  };
}

/// Prelude to use essentials: `use my_module::prelude::*`.
pub mod prelude
{
  #[ cfg( feature = "use_std" ) ]
  pub use super::
  {
    sys::prelude::*,
    abs::prelude::*,
    color::prelude::*,
  };
  pub use ::wmath::prelude::*;
}
<commit_msg>Fix module `wplot`, comment out unstable features<commit_after>#![ cfg_attr( not( feature = "use_std" ), no_std ) ]
#![ doc( html_logo_url = "https://raw.githubusercontent.com/Wandalen/wTools/master/asset/img/logo_v3_trans_square.png" ) ]
#![ doc( html_favicon_url = "https://raw.githubusercontent.com/Wandalen/wTools/alpha/asset/img/logo_v3_trans_square_icon_small_v2.ico" ) ]
#![ doc( html_root_url = "https://docs.rs/wplot/latest/wplot/" ) ]
#![ warn( rust_2018_idioms ) ]
#![ warn( missing_debug_implementations ) ]
#![ warn( missing_docs ) ]

// #![ feature( trace_macros ) ]

//!
//! Collection of general purpose tools to iterate. Currently it simply reexport itertools.
//!

#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/Readme.md" ) ) ]

pub use wmath as math;
pub use wtools::prelude::*;

/// Describe colors.
pub mod color;
/// Abstraction.
#[ cfg( feature = "use_std" ) ]
pub mod abs;
/// Concrete system.
#[ cfg( feature = "use_std" ) ]
pub mod sys;

// /// Describe change.
// pub mod change;
// /// Describe changer.
// pub mod changer;
// /// Describe colors.
// pub mod color;
// /// Main aggregating object.
// pub mod context;
// /// Context changer.
// pub mod context_changer;
//
// /// Draw changer.
// pub mod drawing;
// /// Draw changer.
// pub mod drawing_changer;
// /// ChangeInterface for drawing constructor.
// pub mod drawing_change_new;
//
// /// Identity of resource.
// pub mod identity;
// /// Registry.
// pub mod registry;
// /// Brush stroke.
// pub mod stroke_brush;
// /// ChangerInterface of brush stroke.
// pub mod stroke_brush_changer;
// /// ChangeInterface of brush stroke constructor.
// pub mod stroke_brush_change_new;
// /// ChangeInterface of brush stroke color.
// pub mod stroke_brush_change_color;
// /// Target to draw.
// pub mod target;

/// Namespace with dependencies.
pub mod dependency
{
  pub use ::image;
  pub use ::open;
  pub use ::wmath;
  pub use ::rgb;
}

/// Protected namespace of the module.
pub mod protected
{
  pub use super::orphan::*;
  #[ cfg( feature = "use_std" ) ]
  pub use super::
  {
    sys::orphan::*,
    abs::orphan::*,
    color::orphan::*,
  };
}

pub use protected::*;

/// Parented namespace of the module.
pub mod orphan
{
  pub use super::exposed::*;
}

/// Exposed namespace of the module.
pub mod exposed
{
  pub use super::prelude::*;
  #[ cfg( feature = "use_std" ) ]
  pub use super::
  {
    sys::exposed::*,
    abs::exposed::*,
    color::exposed::*,
  };
}

/// Prelude to use essentials: `use my_module::prelude::*`.
pub mod prelude
{
  #[ cfg( feature = "use_std" ) ]
  pub use super::
  {
    sys::prelude::*,
    abs::prelude::*,
    color::prelude::*,
  };
  pub use ::wmath::prelude::*;
}
<|endoftext|>


<commit_before>use crate::utils::schemas;
use eyre::Result;
use node_patch::{Address, Slot};
use schemars::{gen::SchemaSettings, JsonSchema};
use serde::Serialize;
use std::{any::type_name, collections::HashMap};
use thiserror::Error;

/// An enumeration of custom errors returned by this library
///
/// Where possible functions should return one of these errors to provide greater
/// context to the user, in particular regarding actions that can be taken to
/// resolve the error.
#[derive(Error, Debug, JsonSchema, Serialize)]
#[serde(tag = "type")]
#[schemars(deny_unknown_fields)]
pub enum Error {
    /// An identifier was supplied that does not match the pattern for the
    /// expected family of identifiers.
    #[error("Invalid universal identifier for family `{family}`: {id}")]
    InvalidUUID { family: String, id: String },

    /// Used to indicate that two values are not the same (rather than
    /// return `false`, this error allows for convenient early return via `?`).
    #[error("Values are not the same (type and/or their value differ).")]
    NotSame,

    /// Used to indicate that two values are not equal (rather than
    /// return `false`, this error allows for convenient early return via `?`).
    #[error("Values are not equal (type is equal but their value differs")]
    NotEqual,

    /// An address resolved to a type that is not able to be pointed to
    /// (does not have a [`Pointer`] variant)
    #[error("Address `{address}` resolved to a type that can not be pointed to `{type_name}`")]
    UnpointableType {
        #[schemars(schema_with = "address_schema")]
        address: Address,
        type_name: String,
    },

    /// The user attempted to use an an address (e.g. in a patch operation) that
    /// is invalid address for the type.
    ///
    /// Does not include the address because in places that this error is used
    /// that is usually modified e.g. `pop_front`.
    #[error("Invalid node address for type `{type_name}`: {details}")]
    InvalidAddress { type_name: String, details: String },

    /// The user attempted to apply a patch operation that is invalid for
    /// the type.
    #[error("Invalid patch operation `{op}` for type `{type_name}`")]
    InvalidPatchOperation { op: String, type_name: String },

    /// The user attempted to apply a patch operation with an invalid
    /// value for the type.
    #[error("Invalid patch value for type `{type_name}`")]
    InvalidPatchValue { type_name: String },

    /// The user attempted to use a slot with an invalid type for the
    /// type of the object (e.g. a `Slot::Name` on a `Vector`).
    #[error("Invalid slot type `{variant}` for type `{type_name}`")]
    InvalidSlotVariant { variant: String, type_name: String },

    /// The user attempted to use a slot with an invalid `Slot::Name`
    /// for the type (e.g a key for a `HashMap` that is not occupied).
    #[error("Invalid patch address name `{name}` for type `{type_name}`")]
    InvalidSlotName { name: String, type_name: String },

    /// The user attempted to use a slot with an invalid `Slot::Index`
    /// for the type (e.g an index that is greater than the size of a vector).
    #[error("Invalid patch address index `{index}` for type `{type_name}`")]
    InvalidSlotIndex { index: usize, type_name: String },

    /// The user attempted to open a document with an unknown format
    #[error("Unknown format `{format}`")]
    UnknownFormat { format: String },

    /// A kernel was asked to execute code in an incompatible programming language
    #[error("Incompatible programming language `{language}` for kernel type `{kernel_type}`")]
    IncompatibleLanguage {
        language: String,
        kernel_type: String,
    },

    /// The user attempted to call a method that is not implemented internally
    /// and so must be delegated to a plugin. However, none of the registered
    /// plugins implement this method. It may be that the user needs to do
    /// `stencila plugins refresh` to fetch the manifests for the plugins.
    /// Or it may be that the method name is just plain wrong.
    #[error("None of the registered plugins implement method `{method}`")]
    UndelegatableMethod { method: String },

    /// The user attempted to call a method that is not implemented internally
    /// and so must be delegated to a plugin. At least one of the plugins implements
    /// this method but not with the values for the supplied parameters e.g.
    /// `decode(content, format)` which `format = 'some-unknown-format'`.
    #[error("None of the registered plugins implement method `{method}` with given parameters")]
    UndelegatableCall {
        method: String,
        params: HashMap<String, serde_json::Value>,
    },

    /// The user attempted to call a method that is not implemented internally
    /// and so must be delegated to a plugin. There is a matching implementation
    /// for the call but plugin which implements it is not yet installed.
    #[error("Plugin `{plugin}` is not yet installed")]
    PluginNotInstalled { plugin: String },

    /// An error of unspecified type
    #[error("{message}")]
    Unspecified { message: String },
}

/// Generate the JSON Schema for the `addresses` property to avoid duplicated types.
fn address_schema(_generator: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
    schemas::typescript("Address", true)
}

/// Create an `UnpointableType` error
pub fn unpointable_type<Type: ?Sized>(address: &Address) -> Error {
    Error::UnpointableType {
        address: address.clone(),
        type_name: type_name::<Type>().into(),
    }
}

/// Create an `InvalidAddress` error
pub fn invalid_address<Type: ?Sized>(details: &str) -> Error {
    Error::InvalidAddress {
        type_name: type_name::<Type>().into(),
        details: details.into(),
    }
}

/// Create an `InvalidPatchOperation` error
pub fn invalid_patch_operation<Type: ?Sized>(op: &str) -> Error {
    Error::InvalidPatchOperation {
        op: op.into(),
        type_name: type_name::<Type>().into(),
    }
}

/// Create an `InvalidPatchValue` error
pub fn invalid_patch_value<Type: ?Sized>() -> Error {
    Error::InvalidPatchValue {
        type_name: type_name::<Type>().into(),
    }
}

/// Create an `InvalidSlotVariant` error
pub fn invalid_slot_variant<Type: ?Sized>(slot: Slot) -> Error {
    Error::InvalidSlotVariant {
        variant: slot.as_ref().into(),
        type_name: type_name::<Type>().into(),
    }
}

/// Create an `InvalidSlotName` error
pub fn invalid_slot_name<Type: ?Sized>(name: &str) -> Error {
    Error::InvalidSlotName {
        name: name.into(),
        type_name: type_name::<Type>().into(),
    }
}

/// Create an `InvalidSlotIndex` error
pub fn invalid_slot_index<Type: ?Sized>(index: usize) -> Error {
    Error::InvalidSlotIndex {
        index,
        type_name: type_name::<Type>().into(),
    }
}

/// Create an `IncompatibleLanguage` error
pub fn incompatible_language<Type: ?Sized>(language: &str) -> Error {
    Error::IncompatibleLanguage {
        language: language.to_string(),
        kernel_type: type_name::<Type>().into(),
    }
}

/// Get JSON Schema for the `Error` enum.
///
/// This needs to be generated slightly differently to the other schemas
/// in this library because of the way `thiserror` wants enums to be layed out.
pub fn schema() -> Result<serde_json::Value> {
    let settings = SchemaSettings::draft2019_09().with(|settings| {
        settings.option_add_null_type = false;
        settings.inline_subschemas = false;
    });
    let schema = settings.into_generator().into_root_schema_for::<Error>();
    let schema = serde_json::to_value(schema)?;
    Ok(schema)
}
<commit_msg>refactor(Errors): Remove variants that are now in `node-patch`<commit_after>use eyre::Result;
use schemars::{gen::SchemaSettings, JsonSchema};
use serde::Serialize;
use std::{any::type_name, collections::HashMap};
use thiserror::Error;

/// An enumeration of custom errors returned by this library
///
/// Where possible functions should return one of these errors to provide greater
/// context to the user, in particular regarding actions that can be taken to
/// resolve the error.
#[derive(Error, Debug, JsonSchema, Serialize)]
#[serde(tag = "type")]
#[schemars(deny_unknown_fields)]
pub enum Error {
    /// An identifier was supplied that does not match the pattern for the
    /// expected family of identifiers.
    #[error("Invalid universal identifier for family `{family}`: {id}")]
    InvalidUUID { family: String, id: String },

    /// The user attempted to open a document with an unknown format
    #[error("Unknown format `{format}`")]
    UnknownFormat { format: String },

    /// A kernel was asked to execute code in an incompatible programming language
    #[error("Incompatible programming language `{language}` for kernel type `{kernel_type}`")]
    IncompatibleLanguage {
        language: String,
        kernel_type: String,
    },

    /// The user attempted to call a method that is not implemented internally
    /// and so must be delegated to a plugin. However, none of the registered
    /// plugins implement this method. It may be that the user needs to do
    /// `stencila plugins refresh` to fetch the manifests for the plugins.
    /// Or it may be that the method name is just plain wrong.
    #[error("None of the registered plugins implement method `{method}`")]
    UndelegatableMethod { method: String },

    /// The user attempted to call a method that is not implemented internally
    /// and so must be delegated to a plugin. At least one of the plugins implements
    /// this method but not with the values for the supplied parameters e.g.
    /// `decode(content, format)` which `format = 'some-unknown-format'`.
    #[error("None of the registered plugins implement method `{method}` with given parameters")]
    UndelegatableCall {
        method: String,
        params: HashMap<String, serde_json::Value>,
    },

    /// The user attempted to call a method that is not implemented internally
    /// and so must be delegated to a plugin. There is a matching implementation
    /// for the call but plugin which implements it is not yet installed.
    #[error("Plugin `{plugin}` is not yet installed")]
    PluginNotInstalled { plugin: String },

    /// An error of unspecified type
    #[error("{message}")]
    Unspecified { message: String },
}

/// Create an `IncompatibleLanguage` error
pub fn incompatible_language<Type: ?Sized>(language: &str) -> Error {
    Error::IncompatibleLanguage {
        language: language.to_string(),
        kernel_type: type_name::<Type>().into(),
    }
}

/// Get JSON Schema for the `Error` enum.
///
/// This needs to be generated slightly differently to the other schemas
/// in this library because of the way `thiserror` wants enums to be layed out.
pub fn schema() -> Result<serde_json::Value> {
    let settings = SchemaSettings::draft2019_09().with(|settings| {
        settings.option_add_null_type = false;
        settings.inline_subschemas = false;
    });
    let schema = settings.into_generator().into_root_schema_for::<Error>();
    let schema = serde_json::to_value(schema)?;
    Ok(schema)
}
<|endoftext|>


<commit_before>//! Method macro combinators
//!
//! These macros make parsers as methods of structs
//! and that can take methods of structs to call
//! as parsers.
//!
//! There is a trick to make them easier to assemble,
//! combinators are defined like this:
//!
//! ```ignore
//! macro_rules! tag (
//!   ($i:expr, $inp: expr) => (
//!     {
//!       ...
//!     }
//!   );
//! );
//! ```
//!
//! But when used as methods in other combinators, are used
//! like this:
//!
//! ```ignore
//! method!(my_function<Parser<'a> >, self, tag!("abcd"));
//! ```
//!
//! Internally, other combinators will rewrite
//! that call to pass the input as second argument:
//!
//! ```ignore
//! macro_rules! method (
//!   ($name:ident<$a:ty>, $self_:ident, $submac:ident!( $($args:tt)* )) => (
//!     fn $name( $self_: $a, i: &[u8] ) -> IResult<&[u8], &[u8]> {
//!       $submac!(i, $($args)*)
//!     }
//!   );
//! );
//! ```
//!
//! The `method!` macro is similar to the `named!` macro in the macros module.
//! While `named!` will create a parser function, `method!` will create a parser
//! method on the struct it is defined in.
//!
//! Compared to the `named!` macro there are a few differences in how they are
//! invoked. A `method!` invocation always has to have the type of `self`
//! declared and it can't be a reference due to Rust's borrow lifetime
//! restrictions:
//!
//! ```ignore
//! //                  -`self`'s type-
//! method!(method_name<  Parser<'a> >, ...);
//! ```
//! `self`'s type always comes first.
//! The next difference is you have to input the self struct. Due to Rust's
//! macro hygiene the macro can't declare it on it's own.
//!
//! ```ignore
//! //                                                 -self-
//! method!(method_name<Parser<'a>, &'a str, &'a str>, self, ...);
//! ```
//! When making a parsing struct with parsing methods, due to the static borrow
//! checker,calling any parsing methods on self (or any other parsing struct)
//! will cause self to be moved for the rest of the method.To get around this
//! restriction all self is moved into the called method and then the called
//! method will return self to the caller.
//!
//! To call a method on self you need to use the `call_m!` macro. For example:
//!
//! ```ignore
//! struct<'a> Parser<'a> {
//!   parsed: &'a str,
//! }
//! impl<'a> Parser<'a> {
//!   // Constructor omitted for brevity
//!   method!(take4<Parser<'a>, &'a str, &'a str>, self, take!(4));
//!   method!(caller<Parser<'a>, &'a str, &'a str>, self, call_m!(self.take4));
//! }
//! ```
//! More complicated combinations still mostly look the same as their `named!`
//! counterparts:
//!
//! ```ignore
//!    method!(pub simple_chain<&mut Parser<'a>, &'a str, &'a str>, self,
//!      do_parse!(
//!             call_m!(self.tag_abc)                                        >>
//!             call_m!(self.tag_def)                                        >>
//!             call_m!(self.tag_ghi)                                        >>
//!       last: map!(call_m!(self.simple_peek), |parsed| sb.parsed = parsed) >>
//!       (last)
//!      )
//!    );
//! ```
//! The three additions to method definitions to remember are:
//! 1. Specify `self`'s type
//! 2. Pass `self` to the macro
//! 4. Call parser methods using the `call_m!` macro.

/// Makes a method from a parser combination
///
/// The must be set up because the compiler needs
/// the information
///
/// ```ignore
/// method!(my_function<Parser<'a> >( &[u8] ) -> &[u8], tag!("abcd"));
/// // first type parameter is `self`'s type, second is input, third is output
/// method!(my_function<Parser<'a>, &[u8], &[u8]>,     tag!("abcd"));
/// //prefix them with 'pub' to make the methods public
/// method!(pub my_function<Parser<'a>,&[u8], &[u8]>, tag!("abcd"));
/// ```
#[deprecated(since = "4.2.0", note = "Please use the nom-methods crate instead")]
#[macro_export(local_inner_macros)]
macro_rules! method (
  // Non-public immutable self
  ($name:ident<$a:ty>( $i:ty ) -> $o:ty, $self_:ident, $submac:ident!( $($args:tt)* )) => (
      #[allow(unused_variables)]
      fn $name( $self_: $a, i: $i ) -> ($a, $crate::IResult<$i,$o,($i,$crate::ErrorKind)>) {
        let result = $submac!(i, $($args)*);
        ($self_, result)
      }
  );
  ($name:ident<$a:ty,$i:ty,$o:ty,$e:ty>, $self_:ident, $submac:ident!( $($args:tt)* )) => (
    #[allow(unused_variables)]
    fn $name( $self_: $a, i: $i ) -> ($a, $crate::IResult<$i, $o, $e>) {
      let result = $submac!(i, $($args)*);
      ($self_, result)
    }
  );
  ($name:ident<$a:ty,$i:ty,$o:ty>, $self_:ident, $submac:ident!( $($args:tt)* )) => (
    #[allow(unused_variables)]
    fn $name( $self_: $a, i: $i ) -> ($a, $crate::IResult<$i,$o,($i, $crate::ErrorKind)>)  {
      let result = $submac!(i, $($args)*);
      ($self_, result)
    }
  );
  ($name:ident<$a:ty,$o:ty>, $self_:ident, $submac:ident!( $($args:tt)* )) => (
      #[allow(unused_variables)]
      fn $name( $self_: $a, i: &[u8] ) -> ($a, $crate::IResult<&[u8], $o, (&[u8], $crate::ErrorKind)>) {
        let result = $submac!(i, $($args)*);
        ($self_, result)
      }
  );
  ($name:ident<$a:ty>, $self_:ident, $submac:ident!( $($args:tt)* )) => (
      #[allow(unused_variables)]
      fn $name( $self_: $a, i: &[u8] ) -> ($a, $crate::IResult<&[u8], &[u8], (&[u8], $crate::ErrorKind)>) {
        let result = $submac!(i, $($args)*);
        ($self_, result)
      }
  );
  // Public immutable self
  (pub $name:ident<$a:ty>( $i:ty ) -> $o:ty, $self_:ident, $submac:ident!( $($args:tt)* )) => (
      #[allow(unused_variables)]
      pub fn $name( $self_: $a, i: $i ) -> ($a, $crate::IResult<$i,$o,($i, $crate::ErrorKind)>) {
        let result = $submac!(i, $($args)*);
        ($self_, result)
      }
  );
  (pub $name:ident<$a:ty,$i:ty,$o:ty,$e:ty>, $self_:ident, $submac:ident!( $($args:tt)* )) => (
      #[allow(unused_variables)]
      pub fn $name( $self_: $a, i: $i ) -> ($a, $crate::IResult<$i, $o, $e>) {
        let result = $submac!(i, $($args)*);
        ($self_, result)
      }
  );
  (pub $name:ident<$a:ty,$i:ty,$o:ty>, $self_:ident, $submac:ident!( $($args:tt)* )) => (
    #[allow(unused_variables)]
    pub fn $name( $self_: $a,i: $i ) -> ($a, $crate::IResult<$i,$o,($i, $crate::ErrorKind)>)  {
      let result = $submac!(i, $($args)*);
      ($self_, result)
    }
  );
  (pub $name:ident<$a:ty,$o:ty>, $self_:ident, $submac:ident!( $($args:tt)* )) => (
    #[allow(unused_variables)]
    pub fn $name( $self_: $a, i: &[u8] ) -> ($a, $crate::IResult<&[u8], $o, (&[u8], $crate::ErrorKind)>) {
      let result = $submac!(i, $($args)*);
      ($self_, result)
    }
  );
  (pub $name:ident<$a:ty>, $self_:ident, $submac:ident!( $($args:tt)* )) => (
    #[allow(unused_variables)]
    pub fn $name( $self_: $a, i: &[u8] ) -> ($a, $crate::IResult<&[u8], &[u8], (&[u8], $crate::ErrorKind)>) {
      let result = $submac!(i, $($args)*);
      ($self_, result)
    }
  );
  // Non-public mutable self
  ($name:ident<$a:ty>( $i:ty ) -> $o:ty, mut $self_:ident, $submac:ident!( $($args:tt)* )) => (
      #[allow(unused_variables)]
      fn $name( mut $self_: $a, i: $i ) -> ($a, $crate::IResult<$i,$o,($i, $crate::ErrorKind)>) {
        let result = $submac!(i, $($args)*);
        ($self_, result)
      }
  );
  ($name:ident<$a:ty,$i:ty,$o:ty,$e:ty>, mut $self_:ident, $submac:ident!( $($args:tt)* )) => (
      #[allow(unused_variables)]
      fn $name( mut $self_: $a, i: $i ) -> ($a, $crate::IResult<$i, $o, $e>) {
      let result = $submac!(i, $($args)*);
      ($self_, result)
      }
  );
  ($name:ident<$a:ty,$i:ty,$o:ty>, mut $self_:ident, $submac:ident!( $($args:tt)* )) => (
    #[allow(unused_variables)]
    fn $name( mut $self_: $a, i: $i ) -> ($a, $crate::IResult<$i,$o,($i, $crate::ErrorKind)>)  {
      let result = $submac!(i, $($args)*);
      ($self_, result)
    }
  );
  ($name:ident<$a:ty,$o:ty>, mut $self_:ident, $submac:ident!( $($args:tt)* )) => (
      #[allow(unused_variables)]
      fn $name( mut $self_: $a, i: &[u8] ) -> ($a, $crate::IResult<&[u8], $o, (&[u8], $crate::ErrorKind)>) {
        let result = $submac!(i, $($args)*);
        ($self_, result)
      }
  );
  ($name:ident<$a:ty>, mut $self_:ident, $submac:ident!( $($args:tt)* )) => (
      #[allow(unused_variables)]
      fn $name( mut $self_: $a, i: &[u8] ) -> ($a, $crate::IResult<&[u8], &[u8], (&[u8], $crate::ErrorKind)>) {
        let result = $submac!(i, $($args)*);
        ($self_, result)
      }
  );
  // Public mutable self
  (pub $name:ident<$a:ty>( $i:ty ) -> $o:ty, mut $self_:ident, $submac:ident!( $($args:tt)* )) => (
      #[allow(unused_variables)]
      pub fn $name( mut $self_: $a, i: $i ) -> ($a, $crate::IResult<$i,$o,($i, $crate::ErrorKind)>) {
        let result = $submac!(i, $($args)*);
        ($self_, result)
      }
  );
  (pub $name:ident<$a:ty,$i:ty,$o:ty,$e:ty>, mut $self_:ident, $submac:ident!( $($args:tt)* )) => (
      #[allow(unused_variables)]
      pub fn $name( mut $self_: $a, i: $i ) -> ($a, $crate::IResult<$i, $o, $e>) {
        let result = $submac!(i, $($args)*);
        ($self_, result)
      }
  );
  (pub $name:ident<$a:ty,$i:ty,$o:ty>, mut $self_:ident, $submac:ident!( $($args:tt)* )) => (
    #[allow(unused_variables)]
    pub fn $name( mut $self_: $a,i: $i ) -> ($a, $crate::IResult<$i,$o,($i, $crate::ErrorKind)>)  {
      let result = $submac!(i, $($args)*);
      ($self_, result)
    }
  );
  (pub $name:ident<$a:ty,$o:ty>, mut $self_:ident, $submac:ident!( $($args:tt)* )) => (
    #[allow(unused_variables)]
    pub fn $name( mut $self_: $a, i: &[u8] ) -> ($a, $crate::IResult<&[u8], $o, (&[u8], $crate::ErrorKind)>) {
      let result = $submac!(i, $($args)*);
      ($self_, result)
    }
  );
  (pub $name:ident<$a:ty>, mut $self_:ident, $submac:ident!( $($args:tt)* )) => (
    #[allow(unused_variables)]
    pub fn $name( mut $self_: $a, i: &[u8] ) -> ($a, $crate::IResult<&[u8], &[u8], (&[u8], $crate::ErrorKind)>) {
      let result = $submac!(i, $($args)*);
      ($self_, result)
    }
  );
);

/// Used to called methods then move self back into self
#[deprecated(since = "4.2.0", note = "Please use the nom-methods crate instead")]
#[macro_export(local_inner_macros)]
macro_rules! call_m (
  ($i:expr, $self_:ident.$method:ident) => (
    {
      let (tmp, res) = $self_.$method($i);
      $self_ = tmp;
      res
    }
  );
  ($i:expr, $self_:ident.$method:ident, $($args:expr),* ) => (
    {
      let (tmp, res) = $self_.$method($i, $($args),*);
      $self_ = tmp;
      res
    }
  );
);

/// emulate function currying for method calls on structs
/// `apply_m!(self.my_function, arg1, arg2, ...)` becomes `self.my_function(input, arg1, arg2, ...)`
///
/// Supports up to 6 arguments
#[deprecated(since = "4.2.0", note = "Please use the nom-methods crate instead")]
#[macro_export(local_inner_macros)]
macro_rules! apply_m (
  ($i:expr, $self_:ident.$method:ident, $($args:expr),* ) => ( { let (tmp, res) = $self_.$method( $i, $($args),* ); $self_ = tmp; res } );
);

#[cfg(test)]
#[allow(deprecated)]
mod tests {
  // reproduce the tag_s and take_s macros, because of module import order
  macro_rules! tag_s (
    ($i:expr, $tag: expr) => (
      {
        use $crate::lib::std::result::Result::*;
        use $crate::{Err,ErrorKind,Needed,IResult, need_more};

        let res: IResult<_,_> = if $tag.len() > $i.len() {
          need_more($i, Needed::Size($tag.len()))
        //} else if &$i[0..$tag.len()] == $tag {
        } else if ($i).starts_with($tag) {
          Ok((&$i[$tag.len()..], &$i[0..$tag.len()]))
        } else {
          Err(Err::Error(error_position!($i, ErrorKind::TagStr)))
        };
        res
      }
    );
  );

  macro_rules! take_s (
    ($i:expr, $count:expr) => (
      {
        use $crate::lib::std::result::Result::*;
        use $crate::{Needed,IResult,need_more};

        let cnt = $count as usize;
        let res: IResult<_,_> = if $i.chars().count() < cnt {
          need_more($i, Needed::Size(cnt))
        } else {
          let mut offset = $i.len();
          let mut count = 0;
          for (o, _) in $i.char_indices() {
            if count == cnt {
              offset = o;
              break;
            }
            count += 1;
          }
          Ok((&$i[offset..], &$i[..offset]))
        };
        res
      }
    );
  );

  struct Parser<'a> {
    bcd: &'a str,
  }

  impl<'a> Parser<'a> {
    pub fn new() -> Parser<'a> {
      Parser { bcd: "" }
    }

    method!(
      tag_abc<Parser<'a>, &'a str, &'a str>,
      self,
      tag_s!("áβç")
    );
    method!(tag_bcd<Parser<'a> >(&'a str) -> &'a str, self, tag_s!("βçδ"));
    method!(pub tag_hij<Parser<'a> >(&'a str) -> &'a str, self, tag_s!("λïJ"));
    method!(pub tag_ijk<Parser<'a>, &'a str, &'a str>, self, tag_s!("ïJƙ"));
    method!(take3<Parser<'a>, &'a str, &'a str>, self, take_s!(3));
    method!(pub simple_call<Parser<'a>, &'a str, &'a str>, mut self,
      call_m!(self.tag_abc)
    );
    method!(pub simple_peek<Parser<'a>, &'a str, &'a str>, mut self,
      peek!(call_m!(self.take3))
    );
    method!(pub simple_chain<Parser<'a>, &'a str, &'a str>, mut self,
      do_parse!(
         map!(call_m!(self.tag_bcd), |bcd| self.bcd = bcd) >>
         last: call_m!(self.simple_peek)                   >>
         (last)
      )
    );
    fn tag_stuff(mut self: Parser<'a>, input: &'a str, something: &'a str) -> (Parser<'a>, ::IResult<&'a str, &'a str>) {
      self.bcd = something;
      let (tmp, res) = self.tag_abc(input);
      self = tmp;
      (self, res)
    }
    method!(use_apply<Parser<'a>, &'a str, &'a str>, mut self, apply_m!(self.tag_stuff, "βçδ"));
  }

  #[test]
  fn test_method_call_abc() {
    let p = Parser::new();
    let input: &str = "áβçδèƒϱλïJƙ";
    let consumed: &str = "áβç";
    let leftover: &str = "δèƒϱλïJƙ";
    let (_, res) = p.tag_abc(input);
    match res {
      Ok((extra, output)) => {
        assert!(
          extra == leftover,
          "`Parser.tag_abc` consumed leftover input. leftover: {}",
          extra
        );
        assert!(
          output == consumed,
          "`Parser.tag_abc` doesnt return the string it consumed \
           on success. Expected `{}`, got `{}`.",
          consumed,
          output
        );
      }
      other => panic!(
        "`Parser.tag_abc` didn't succeed when it should have. \
         Got `{:?}`.",
        other
      ),
    }
  }

  #[test]
  fn test_method_call_bcd() {
    let p = Parser::new();
    let input: &str = "βçδèƒϱλïJƙ";
    let consumed: &str = "βçδ";
    let leftover: &str = "èƒϱλïJƙ";
    let (_, res) = p.tag_bcd(input);
    match res {
      Ok((extra, output)) => {
        assert!(
          extra == leftover,
          "`Parser.tag_bcd` consumed leftover input. leftover: {}",
          extra
        );
        assert!(
          output == consumed,
          "`Parser.tag_bcd` doesn't return the string it consumed \
           on success. Expected `{}`, got `{}`.",
          consumed,
          output
        );
      }
      other => panic!(
        "`Parser.tag_bcd` didn't succeed when it should have. \
         Got `{:?}`.",
        other
      ),
    }
  }

  #[test]
  fn test_method_call_hij() {
    let p = Parser::new();
    let input: &str = "λïJƙℓ₥ñôƥ9řƨ";
    let consumed: &str = "λïJ";
    let leftover: &str = "ƙℓ₥ñôƥ9řƨ";
    let (_, res) = p.tag_hij(input);
    match res {
      Ok((extra, output)) => {
        assert!(
          extra == leftover,
          "`Parser.tag_hij` consumed leftover input. leftover: {}",
          extra
        );
        assert!(
          output == consumed,
          "`Parser.tag_hij` doesn't return the string it consumed \
           on success. Expected `{}`, got `{}`.",
          consumed,
          output
        );
      }
      other => panic!(
        "`Parser.tag_hij` didn't succeed when it should have. \
         Got `{:?}`.",
        other
      ),
    }
  }

  #[test]
  fn test_method_call_ijk() {
    let p = Parser::new();
    let input: &str = "ïJƙℓ₥ñôƥ9řƨ";
    let consumed: &str = "ïJƙ";
    let leftover: &str = "ℓ₥ñôƥ9řƨ";
    let (_, res) = p.tag_ijk(input);
    match res {
      Ok((extra, output)) => {
        assert!(
          extra == leftover,
          "`Parser.tag_ijk` consumed leftover input. leftover: {}",
          extra
        );
        assert!(
          output == consumed,
          "`Parser.tag_ijk` doesn't return the string it consumed \
           on success. Expected `{}`, got `{}`.",
          consumed,
          output
        );
      }
      other => panic!(
        "`Parser.tag_ijk` didn't succeed when it should have. \
         Got `{:?}`.",
        other
      ),
    }
  }
  #[test]
  fn test_method_simple_call() {
    let p = Parser::new();
    let input: &str = "áβçδèƒϱλïJƙ";
    let consumed: &str = "áβç";
    let leftover: &str = "δèƒϱλïJƙ";
    let (_, res) = p.simple_call(input);
    match res {
      Ok((extra, output)) => {
        assert!(
          extra == leftover,
          "`Parser.simple_call` consumed leftover input. leftover: {}",
          extra
        );
        assert!(
          output == consumed,
          "`Parser.simple_call` doesn't return the string it consumed \
           on success. Expected `{}`, got `{}`.",
          consumed,
          output
        );
      }
      other => panic!(
        "`Parser.simple_call` didn't succeed when it should have. \
         Got `{:?}`.",
        other
      ),
    }
  }

  #[test]
  fn test_apply_m() {
    let mut p = Parser::new();
    let input: &str = "áβçδèƒϱλïJƙ";
    let consumed: &str = "áβç";
    let leftover: &str = "δèƒϱλïJƙ";
    let (tmp, res) = p.use_apply(input);
    p = tmp;
    match res {
      Ok((extra, output)) => {
        assert!(
          extra == leftover,
          "`Parser.use_apply` consumed leftover input. leftover: {}",
          extra
        );
        assert!(
          output == consumed,
          "`Parser.use_apply` doesn't return the string it was supposed to \
           on success. Expected `{}`, got `{}`.",
          leftover,
          output
        );
        assert!(
          p.bcd == "βçδ",
          "Parser.use_apply didn't modify the parser field correctly: {}",
          p.bcd
        );
      }
      other => panic!(
        "`Parser.use_apply` didn't succeed when it should have. \
         Got `{:?}`.",
        other
      ),
    }
  }

  #[test]
  fn test_method_call_peek() {
    let p = Parser::new();
    let input: &str = "ж¥ƺáβçδèƒϱλïJƙ";
    let consumed: &str = "ж¥ƺ";
    let (_, res) = p.simple_peek(input);
    match res {
      Ok((extra, output)) => {
        assert!(
          extra == input,
          "`Parser.simple_peek` consumed leftover input. leftover: {}",
          extra
        );
        assert!(
          output == consumed,
          "`Parser.simple_peek` doesn't return the string it consumed \
           on success. Expected `{}`, got `{}`.",
          consumed,
          output
        );
      }
      other => panic!(
        "`Parser.simple_peek` didn't succeed when it should have. \
         Got `{:?}`.",
        other
      ),
    }
  }

  #[test]
  fn test_method_call_chain() {
    let mut p = Parser::new();
    let input: &str = "βçδδèƒϱλïJƙℓ";
    let leftover: &str = "δèƒϱλïJƙℓ";
    let output: &str = "δèƒ";
    let (tmp, res) = p.simple_chain(input);
    p = tmp;
    match res {
      Ok((extra, out)) => {
        assert!(
          extra == leftover,
          "`Parser.simple_chain` consumed leftover input. leftover: {}",
          extra
        );
        assert!(
          out == output,
          "`Parser.simple_chain` doesn't return the string it was supposed to \
           on success. Expected `{}`, got `{}`.",
          output,
          out
        );
        assert!(
          p.bcd == "βçδ",
          "Parser.simple_chain didn't modify the parser field correctly: {}",
          p.bcd
        );
      }
      other => panic!(
        "`Parser.simple_chain` didn't succeed when it should have. \
         Got `{:?}`.",
        other
      ),
    }
  }
}
<commit_msg>remove the methods combinators<commit_after>/// do not use: method combinators moved to the nom-methods crate
#[macro_export]
macro_rules! method (
  ($($args:tt)*) => (compile_error!("method combinators moved to the nom-methods crate"));
);

/// do not use: method combinators moved to the nom-methods crate
#[macro_export]
macro_rules! call_m (
  ($($args:tt)*) => (compile_error!("method combinators moved to the nom-methods crate"));
);

/// do not use: method combinators moved to the nom-methods crate
#[macro_export]
macro_rules! apply_m (
  ($($args:tt)*) => (compile_error!("method combinators moved to the nom-methods crate"));
);

<|endoftext|>


<commit_before>use chrono::{DateTime, Utc};
use hyper::Client;
use regex::Regex;
use scheduled_executor::TaskGroup;
use serde_json;
use serde_json::Value;

use std::collections::HashMap;
use std::f64;
use std::io::Read;

use cache::Cache;
use config::Config;
use error::*;
use metadata::{Broker, ClusterId, TopicName};
use utils::insert_at;

#[derive(PartialEq, Serialize, Deserialize, Debug, Copy, Clone)]
pub struct PartitionMetrics {
    pub size_bytes: f64,
}

impl Default for PartitionMetrics {
    fn default() -> PartitionMetrics {
        PartitionMetrics { size_bytes: 0f64 }
    }
}

#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)]
pub struct TopicBrokerMetrics {
    pub m_rate_15: f64,
    pub b_rate_15: f64,
    pub partitions: Vec<PartitionMetrics>,
}

impl Default for TopicBrokerMetrics {
    fn default() -> Self {
        TopicBrokerMetrics {
            m_rate_15: 0f64,
            b_rate_15: 0f64,
            partitions: Vec::new(),
        }
    }
}

#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)]
pub struct TopicMetrics {
    pub brokers: HashMap<i32, TopicBrokerMetrics>,
}

impl TopicMetrics {
    pub fn new() -> TopicMetrics {
        TopicMetrics {
            brokers: HashMap::new(),
        }
    }

    pub fn aggregate_broker_metrics(&self) -> TopicBrokerMetrics {
        self.brokers.iter().fold(
            TopicBrokerMetrics::default(),
            |mut acc, (_, broker_metrics)| {
                acc.m_rate_15 += broker_metrics.m_rate_15;
                acc.b_rate_15 += broker_metrics.b_rate_15;
                acc
            },
        )
    }
}

impl Default for TopicMetrics {
    fn default() -> Self {
        TopicMetrics::new()
    }
}

fn format_jolokia_path(hostname: &str, port: i32, filter: &str) -> String {
    format!("http://{}:{}/jolokia/read/{}?ignoreErrors=true&includeStackTrace=false&maxCollectionSize=0",
            hostname, port, filter)
}

fn fetch_metrics_json(hostname: &str, port: i32, filter: &str) -> Result<Value> {
    let client = Client::new();
    let url = format_jolokia_path(hostname, port, filter);
    let mut response = client.get(&url).send().chain_err(|| "Connection error")?;

    let mut body = String::new();
    response
        .read_to_string(&mut body)
        .chain_err(|| "Could not read response to string")?;

    let value = serde_json::from_str(&body).chain_err(|| "Failed to parse JSON")?;

    Ok(value)
}

fn jolokia_response_get_value(json_response: &Value) -> Result<&serde_json::Map<String, Value>> {
    let obj = match json_response.as_object() {
        Some(obj) => obj,
        None => bail!("The provided Value is not a JSON object"),
    };
    if let Some(v) = obj.get("value") {
        if let Some(value_obj) = v.as_object() {
            return Ok(value_obj);
        } else {
            bail!("'value' is not a JSON object");
        }
    } else {
        bail!("Missing value");
    }
}

fn parse_broker_rate_metrics(jolokia_json_response: &Value) -> Result<HashMap<TopicName, f64>> {
    let value_map = jolokia_response_get_value(jolokia_json_response)
        .chain_err(|| "Failed to extract 'value' from jolokia response.")?;
    let mut metrics = HashMap::new();
    let re = Regex::new(r"topic=([^,]+),").unwrap();

    for (mbean_name, value) in value_map.iter() {
        let topic = match re.captures(mbean_name) {
            Some(cap) => cap.get(1).unwrap().as_str(),
            None => "__TOTAL__",
        };
        if let Value::Object(ref obj) = *value {
            match obj.get("FifteenMinuteRate") {
                Some(&Value::Number(ref rate)) => {
                    metrics.insert(topic.to_owned(), rate.as_f64().unwrap_or(-1f64))
                }
                None => bail!("Can't find key in metric"),
                _ => bail!("Unexpected metric type"),
            };
        }
    }
    Ok(metrics)
}

fn parse_partition_size_metrics(
    jolokia_json_response: &Value,
) -> Result<HashMap<TopicName, Vec<PartitionMetrics>>> {
    let value_map = jolokia_response_get_value(jolokia_json_response)
        .chain_err(|| "Failed to extract 'value' from jolokia response.")?;
    let topic_re = Regex::new(r"topic=([^,]+),").unwrap();
    let partition_re = Regex::new(r"partition=([^,]+),").unwrap();

    let mut metrics = HashMap::new();
    for (mbean_name, value) in value_map.iter() {
        let topic = topic_re
            .captures(mbean_name)
            .and_then(|cap| cap.get(1).map(|m| m.as_str()));
        let partition = partition_re
            .captures(mbean_name)
            .and_then(|cap| cap.get(1).map(|m| m.as_str()))
            .and_then(|p_str| p_str.parse::<u32>().ok());
        if topic.is_none() || partition.is_none() {
            bail!("Can't parse topic and partition metadata from metric name");
        }
        if let Value::Object(ref obj) = *value {
            match obj.get("Value") {
                Some(&Value::Number(ref size)) => {
                    let partition_metrics = PartitionMetrics {
                        size_bytes: size.as_f64().unwrap_or(-1f64),
                    };
                    insert_at(
                        metrics
                            .entry(topic.unwrap().to_owned())
                            .or_insert_with(Vec::new),
                        partition.unwrap() as usize,
                        partition_metrics,
                        PartitionMetrics::default(),
                    );
                }
                None => bail!("Can't find key in metric"),
                _ => bail!("Unexpected metric type"),
            };
        }
    }
    Ok(metrics)
}

fn log_elapsed_time(task_name: &str, start: DateTime<Utc>) {
    debug!(
        "{} completed in: {:.3}ms",
        task_name,
        Utc::now()
            .signed_duration_since(start)
            .num_microseconds()
            .unwrap() as f64
            / 1000f64
    );
}

pub struct MetricsFetchTaskGroup {
    cache: Cache,
    config: Config,
}

impl MetricsFetchTaskGroup {
    pub fn new(cache: &Cache, config: &Config) -> MetricsFetchTaskGroup {
        MetricsFetchTaskGroup {
            cache: cache.alias(),
            config: config.clone(),
        }
    }

    fn fetch_metrics(&self, cluster_id: &ClusterId, broker: &Broker, port: i32) -> Result<()> {
        let start = Utc::now();
        let byte_rate_json = fetch_metrics_json(
            &broker.hostname,
            port,
            "kafka.server:name=BytesInPerSec,*,type=BrokerTopicMetrics/FifteenMinuteRate",
        )
        .chain_err(|| format!("Failed to fetch byte rate metrics from {}", broker.hostname))?;

        let byte_rate_metrics = parse_broker_rate_metrics(&byte_rate_json)
            .chain_err(|| "Failed to parse byte rate broker metrics")?;

        let msg_rate_json = fetch_metrics_json(
            &broker.hostname,
            port,
            "kafka.server:name=MessagesInPerSec,*,type=BrokerTopicMetrics/FifteenMinuteRate",
        )
        .chain_err(|| {
            format!(
                "Failed to fetch message rate metrics from {}",
                broker.hostname
            )
        })?;

        let msg_rate_metrics = parse_broker_rate_metrics(&msg_rate_json)
            .chain_err(|| "Failed to parse message rate broker metrics")?;
        let partition_metrics_json = fetch_metrics_json(
            &broker.hostname,
            port,
            "kafka.log:name=Size,*,type=Log/Value",
        )
        .chain_err(|| {
            format!(
                "Failed to fetch partition size metrics from {}",
                broker.hostname
            )
        })?;

        let pt_size_metrics = parse_partition_size_metrics(&partition_metrics_json)
            .chain_err(|| "Failed to parse partition size broker metrics")?;

        let topics = byte_rate_metrics
            .keys()
            .chain(msg_rate_metrics.keys())
            .chain(pt_size_metrics.keys());

        for topic in topics {
            let mut topic_metrics = self
                .cache
                .metrics
                .get(&(cluster_id.clone(), topic.clone()))
                .unwrap_or_default();

            let b_rate_15 = *byte_rate_metrics.get(topic).unwrap_or(&-1f64);
            let m_rate_15 = *msg_rate_metrics.get(topic).unwrap_or(&-1f64);
            let partitions = pt_size_metrics.get(topic).cloned().unwrap_or_else(Vec::new);
            topic_metrics.brokers.insert(
                broker.id,
                TopicBrokerMetrics {
                    m_rate_15,
                    b_rate_15,
                    partitions,
                },
            );

            self.cache
                .metrics
                .insert((cluster_id.clone(), topic.clone()), topic_metrics)
                .chain_err(|| "Failed to insert to metrics")?;
        }
        log_elapsed_time("metrics fetch", start);
        Ok(())
    }
}

impl TaskGroup for MetricsFetchTaskGroup {
    type TaskId = (ClusterId, Broker, i32);

    fn get_tasks(&self) -> Vec<Self::TaskId> {
        self.cache.brokers.lock_iter(|iter| {
            let mut tasks = Vec::new();
            for (cluster_id, brokers) in iter {
                let port = self
                    .config
                    .cluster(cluster_id)
                    .and_then(|cluster_config| cluster_config.jolokia_port);
                if port.is_some() {
                    for broker in brokers {
                        tasks.push((cluster_id.clone(), broker.clone(), port.unwrap()));
                    }
                }
            }
            debug!("New metrics tasks: {:?}", tasks);
            tasks
        })
    }

    fn execute(&self, task_id: (ClusterId, Broker, i32)) {
        debug!("Starting fetch for {}: {}", task_id.0, task_id.1.id);
        if let Err(e) = self.fetch_metrics(&task_id.0, &task_id.1, task_id.2) {
            format_error_chain!(e);
        }
    }
}
<commit_msg>Cache topic metrics only once per update<commit_after>use chrono::{DateTime, Utc};
use hyper::Client;
use regex::Regex;
use scheduled_executor::TaskGroup;
use serde_json;
use serde_json::Value;

use std::collections::{HashMap, HashSet};
use std::f64;
use std::io::Read;

use cache::Cache;
use config::Config;
use error::*;
use metadata::{Broker, ClusterId, TopicName};
use utils::insert_at;

#[derive(PartialEq, Serialize, Deserialize, Debug, Copy, Clone)]
pub struct PartitionMetrics {
    pub size_bytes: f64,
}

impl Default for PartitionMetrics {
    fn default() -> PartitionMetrics {
        PartitionMetrics { size_bytes: 0f64 }
    }
}

#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)]
pub struct TopicBrokerMetrics {
    pub m_rate_15: f64,
    pub b_rate_15: f64,
    pub partitions: Vec<PartitionMetrics>,
}

impl Default for TopicBrokerMetrics {
    fn default() -> Self {
        TopicBrokerMetrics {
            m_rate_15: 0f64,
            b_rate_15: 0f64,
            partitions: Vec::new(),
        }
    }
}

#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)]
pub struct TopicMetrics {
    pub brokers: HashMap<i32, TopicBrokerMetrics>,
}

impl TopicMetrics {
    pub fn new() -> TopicMetrics {
        TopicMetrics {
            brokers: HashMap::new(),
        }
    }

    pub fn aggregate_broker_metrics(&self) -> TopicBrokerMetrics {
        self.brokers.iter().fold(
            TopicBrokerMetrics::default(),
            |mut acc, (_, broker_metrics)| {
                acc.m_rate_15 += broker_metrics.m_rate_15;
                acc.b_rate_15 += broker_metrics.b_rate_15;
                acc
            },
        )
    }
}

impl Default for TopicMetrics {
    fn default() -> Self {
        TopicMetrics::new()
    }
}

fn format_jolokia_path(hostname: &str, port: i32, filter: &str) -> String {
    format!("http://{}:{}/jolokia/read/{}?ignoreErrors=true&includeStackTrace=false&maxCollectionSize=0",
            hostname, port, filter)
}

fn fetch_metrics_json(hostname: &str, port: i32, filter: &str) -> Result<Value> {
    let client = Client::new();
    let url = format_jolokia_path(hostname, port, filter);
    let mut response = client.get(&url).send().chain_err(|| "Connection error")?;

    let mut body = String::new();
    response
        .read_to_string(&mut body)
        .chain_err(|| "Could not read response to string")?;

    let value = serde_json::from_str(&body).chain_err(|| "Failed to parse JSON")?;

    Ok(value)
}

fn jolokia_response_get_value(json_response: &Value) -> Result<&serde_json::Map<String, Value>> {
    let obj = match json_response.as_object() {
        Some(obj) => obj,
        None => bail!("The provided Value is not a JSON object"),
    };
    if let Some(v) = obj.get("value") {
        if let Some(value_obj) = v.as_object() {
            return Ok(value_obj);
        } else {
            bail!("'value' is not a JSON object");
        }
    } else {
        bail!("Missing value");
    }
}

fn parse_broker_rate_metrics(jolokia_json_response: &Value) -> Result<HashMap<TopicName, f64>> {
    let value_map = jolokia_response_get_value(jolokia_json_response)
        .chain_err(|| "Failed to extract 'value' from jolokia response.")?;
    let mut metrics = HashMap::new();
    let re = Regex::new(r"topic=([^,]+),").unwrap();

    for (mbean_name, value) in value_map.iter() {
        let topic = match re.captures(mbean_name) {
            Some(cap) => cap.get(1).unwrap().as_str(),
            None => "__TOTAL__",
        };
        if let Value::Object(ref obj) = *value {
            match obj.get("FifteenMinuteRate") {
                Some(&Value::Number(ref rate)) => {
                    metrics.insert(topic.to_owned(), rate.as_f64().unwrap_or(-1f64))
                }
                None => bail!("Can't find key in metric"),
                _ => bail!("Unexpected metric type"),
            };
        }
    }
    Ok(metrics)
}

fn parse_partition_size_metrics(
    jolokia_json_response: &Value,
) -> Result<HashMap<TopicName, Vec<PartitionMetrics>>> {
    let value_map = jolokia_response_get_value(jolokia_json_response)
        .chain_err(|| "Failed to extract 'value' from jolokia response.")?;
    let topic_re = Regex::new(r"topic=([^,]+),").unwrap();
    let partition_re = Regex::new(r"partition=([^,]+),").unwrap();

    let mut metrics = HashMap::new();
    for (mbean_name, value) in value_map.iter() {
        let topic = topic_re
            .captures(mbean_name)
            .and_then(|cap| cap.get(1).map(|m| m.as_str()));
        let partition = partition_re
            .captures(mbean_name)
            .and_then(|cap| cap.get(1).map(|m| m.as_str()))
            .and_then(|p_str| p_str.parse::<u32>().ok());
        if topic.is_none() || partition.is_none() {
            bail!("Can't parse topic and partition metadata from metric name");
        }
        if let Value::Object(ref obj) = *value {
            match obj.get("Value") {
                Some(&Value::Number(ref size)) => {
                    let partition_metrics = PartitionMetrics {
                        size_bytes: size.as_f64().unwrap_or(-1f64),
                    };
                    insert_at(
                        metrics
                            .entry(topic.unwrap().to_owned())
                            .or_insert_with(Vec::new),
                        partition.unwrap() as usize,
                        partition_metrics,
                        PartitionMetrics::default(),
                    );
                }
                None => bail!("Can't find key in metric"),
                _ => bail!("Unexpected metric type"),
            };
        }
    }
    Ok(metrics)
}

fn log_elapsed_time(task_name: &str, start: DateTime<Utc>) {
    debug!(
        "{} completed in: {:.3}ms",
        task_name,
        Utc::now()
            .signed_duration_since(start)
            .num_microseconds()
            .unwrap() as f64
            / 1000f64
    );
}

pub struct MetricsFetchTaskGroup {
    cache: Cache,
    config: Config,
}

impl MetricsFetchTaskGroup {
    pub fn new(cache: &Cache, config: &Config) -> MetricsFetchTaskGroup {
        MetricsFetchTaskGroup {
            cache: cache.alias(),
            config: config.clone(),
        }
    }

    fn fetch_metrics(&self, cluster_id: &ClusterId, broker: &Broker, port: i32) -> Result<()> {
        let start = Utc::now();
        let byte_rate_json = fetch_metrics_json(
            &broker.hostname,
            port,
            "kafka.server:name=BytesInPerSec,*,type=BrokerTopicMetrics/FifteenMinuteRate",
        )
        .chain_err(|| format!("Failed to fetch byte rate metrics from {}", broker.hostname))?;

        let byte_rate_metrics = parse_broker_rate_metrics(&byte_rate_json)
            .chain_err(|| "Failed to parse byte rate broker metrics")?;

        let msg_rate_json = fetch_metrics_json(
            &broker.hostname,
            port,
            "kafka.server:name=MessagesInPerSec,*,type=BrokerTopicMetrics/FifteenMinuteRate",
        )
        .chain_err(|| {
            format!(
                "Failed to fetch message rate metrics from {}",
                broker.hostname
            )
        })?;

        let msg_rate_metrics = parse_broker_rate_metrics(&msg_rate_json)
            .chain_err(|| "Failed to parse message rate broker metrics")?;
        let partition_metrics_json = fetch_metrics_json(
            &broker.hostname,
            port,
            "kafka.log:name=Size,*,type=Log/Value",
        )
        .chain_err(|| {
            format!(
                "Failed to fetch partition size metrics from {}",
                broker.hostname
            )
        })?;

        let pt_size_metrics = parse_partition_size_metrics(&partition_metrics_json)
            .chain_err(|| "Failed to parse partition size broker metrics")?;

        let topics = byte_rate_metrics
            .keys()
            .chain(msg_rate_metrics.keys())
            .chain(pt_size_metrics.keys())
            .collect::<HashSet<_>>();

        for topic in topics {
            let mut topic_metrics = self
                .cache
                .metrics
                .get(&(cluster_id.clone(), topic.clone()))
                .unwrap_or_default();

            let b_rate_15 = *byte_rate_metrics.get(topic).unwrap_or(&-1f64);
            let m_rate_15 = *msg_rate_metrics.get(topic).unwrap_or(&-1f64);
            let partitions = pt_size_metrics.get(topic).cloned().unwrap_or_else(Vec::new);
            topic_metrics.brokers.insert(
                broker.id,
                TopicBrokerMetrics {
                    m_rate_15,
                    b_rate_15,
                    partitions,
                },
            );

            self.cache
                .metrics
                .insert((cluster_id.clone(), topic.clone()), topic_metrics)
                .chain_err(|| "Failed to insert to metrics")?;
        }
        log_elapsed_time("metrics fetch", start);
        Ok(())
    }
}

impl TaskGroup for MetricsFetchTaskGroup {
    type TaskId = (ClusterId, Broker, i32);

    fn get_tasks(&self) -> Vec<Self::TaskId> {
        self.cache.brokers.lock_iter(|iter| {
            let mut tasks = Vec::new();
            for (cluster_id, brokers) in iter {
                let port = self
                    .config
                    .cluster(cluster_id)
                    .and_then(|cluster_config| cluster_config.jolokia_port);
                if port.is_some() {
                    for broker in brokers {
                        tasks.push((cluster_id.clone(), broker.clone(), port.unwrap()));
                    }
                }
            }
            debug!("New metrics tasks: {:?}", tasks);
            tasks
        })
    }

    fn execute(&self, task_id: (ClusterId, Broker, i32)) {
        debug!("Starting fetch for {}: {}", task_id.0, task_id.1.id);
        if let Err(e) = self.fetch_metrics(&task_id.0, &task_id.1, task_id.2) {
            format_error_chain!(e);
        }
    }
}
<|endoftext|>