summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDenys Séguret <cano.petrole@gmail.com>2023-01-03 18:16:39 +0100
committerGitHub <noreply@github.com>2023-01-03 18:16:39 +0100
commit3b4bff25b4971b24f0ec009858b112af0bde6dff (patch)
tree7523098730248e7c6d38298674bb2c20cdc7aafa
parent4fcd56b59c1fc2fdd5b87040d2e1f87c2e3370be (diff)
Nushell br function (#647)
Install the br function for nushell. To have this function installed on a previously installed version of broot, run `broot --install` Fix #375
-rw-r--r--Cargo.toml2
-rw-r--r--bacon.toml1
-rw-r--r--src/cli/mod.rs9
-rw-r--r--src/errors.rs61
-rw-r--r--src/shell_install/bash.rs14
-rw-r--r--src/shell_install/fish.rs4
-rw-r--r--src/shell_install/mod.rs76
-rw-r--r--src/shell_install/nushell.rs123
-rw-r--r--src/shell_install/util.rs32
-rw-r--r--website/broot_theme/css/base.css4
-rw-r--r--website/broot_theme/css/bootstrap-custom.min.css2
-rw-r--r--website/docs/css/extra.css4
12 files changed, 268 insertions, 64 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 6624b7d..f3ccc61 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,7 +12,7 @@ license = "MIT"
categories = ["command-line-utilities"]
readme = "README.md"
build = "build.rs"
-rust-version = "1.60"
+rust-version = "1.65"
exclude = ["website", "broot*.zip"]
[features]
diff --git a/bacon.toml b/bacon.toml
index a9f8f22..4ed8990 100644
--- a/bacon.toml
+++ b/bacon.toml
@@ -42,6 +42,7 @@ command = [
"-A", "clippy::module_inception",
"-A", "clippy::neg_multiply",
"-A", "clippy::vec_init_then_push",
+ "-A", "clippy::if_same_then_else",
]
need_stdout = false
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
index 6b56c36..7456337 100644
--- a/src/cli/mod.rs
+++ b/src/cli/mod.rs
@@ -72,7 +72,12 @@ pub fn run() -> Result<Option<Launchable>, ProgramError> {
// configuration
if specific_conf.is_none() && install_args.install != Some(false) {
let mut shell_install = ShellInstall::new(install_args.install == Some(true));
- shell_install.check()?;
+ // TODO clean the next few lines when inspect_err is stable
+ let res = shell_install.check();
+ if let Err(e) = &res {
+ shell_install.comment_error(e);
+ }
+ res?;
if shell_install.should_quit {
return Ok(None);
}
@@ -137,7 +142,7 @@ pub fn run() -> Result<Option<Launchable>, ProgramError> {
}
/// wait for user input, return `true` if they didn't answer 'n'
-pub fn ask_authorization() -> Result<bool, ProgramError> {
+pub fn ask_authorization() -> io::Result<bool> {
let mut answer = String::new();
io::stdin().read_line(&mut answer)?;
let answer = answer.trim();
diff --git a/src/errors.rs b/src/errors.rs
index e1571bb..5ae94d5 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -8,28 +8,59 @@ use {
};
custom_error! {pub ProgramError
- Io {source: io::Error} = "IO Error : {source}",
- Termimad {source: termimad::Error} = "Termimad Error : {source}",
+ AmbiguousVerbName {name: String} = "Ambiguous name: More than one verb matches {name:?}",
+ ArgParse {bad: String, valid: String} = "{bad:?} can't be parsed (valid values: {valid:?})",
Conf {source: ConfError} = "Bad configuration: {source}",
ConfFile {path:String, details: ConfError} = "Bad configuration file {path:?} : {details}",
- ArgParse {bad: String, valid: String} = "{bad:?} can't be parsed (valid values: {valid:?})",
- UnknownVerb {name: String} = "No verb matches {name:?}",
- AmbiguousVerbName {name: String} = "Ambiguous name: More than one verb matches {name:?}",
- UnmatchingVerbArgs {name: String} = "No matching argument found for verb {name:?}",
- TreeBuild {source: TreeBuildError} = "{source}",
- LaunchError {program: String, source: io::Error} = "Unable to launch {program}: {source}",
- UnknowShell {shell: String} = "Unknown shell: {shell}",
+ ImageError {source: ImageError } = "{source}",
InternalError {details: String} = "Internal error: {details}", // should not happen
InvalidGlobError {pattern: String} = "Invalid glob: {pattern}",
- Unrecognized {token: String} = "Unrecognized: {token}",
- NetError {source: NetError} = "{source}",
- ImageError {source: ImageError } = "{source}",
+ Io {source: io::Error} = "IO Error : {source}",
+ LaunchError {program: String, source: io::Error} = "Unable to launch {program}: {source}",
Lfs {details: String} = "Failed to fetch mounts: {details}",
- ZeroLenFile = "File seems empty",
+ NetError {source: NetError} = "{source}",
+ OpenError { source: opener::OpenError } = "Open error: {source}",
+ ShelInstall { source: ShellInstallError } = "{source}",
+ SyntectCrashed { details: String } = "Syntect crashed on {details:?}",
+ Termimad {source: termimad::Error} = "Termimad Error : {source}",
+ TreeBuild {source: TreeBuildError} = "{source}",
+ UnknowShell {shell: String} = "Unknown shell: {shell}",
+ UnknownVerb {name: String} = "No verb matches {name:?}",
UnmappableFile = "File can't be mapped",
+ UnmatchingVerbArgs {name: String} = "No matching argument found for verb {name:?}",
UnprintableFile = "File can't be printed", // has characters that can't be printed without escaping
- SyntectCrashed { details: String } = "Syntect crashed on {details:?}",
- OpenError { source: opener::OpenError } = "Open error: {source}",
+ Unrecognized {token: String} = "Unrecognized: {token}",
+ ZeroLenFile = "File seems empty",
+}
+
+custom_error!{pub ShellInstallError
+ Io {source: io::Error, when: String} = "IO Error {source} on {when}",
+}
+impl ShellInstallError {
+ pub fn is_permission_denied(&self) -> bool {
+ match self {
+ Self::Io { source, .. } => {
+ if source.kind() == io::ErrorKind::PermissionDenied {
+ true
+ } else if cfg!(windows) && source.raw_os_error().unwrap_or(0) == 1314 {
+ // https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--1300-1699-
+ true
+ } else {
+ false
+ }
+ }
+ }
+ }
+}
+pub trait IoToShellInstallError<Ok> {
+ fn context(self, f: &dyn Fn() -> String) -> Result<Ok, ShellInstallError>;
+}
+impl<Ok> IoToShellInstallError<Ok> for Result<Ok, io::Error> {
+ fn context(self, f: &dyn Fn() -> String) -> Result<Ok, ShellInstallError> {
+ self.map_err(|source| ShellInstallError::Io {
+ source, when: f()
+ })
+ }
}
custom_error! {pub TreeBuildError
diff --git a/src/shell_install/bash.rs b/src/shell_install/bash.rs
index 1ebae33..e730e03 100644
--- a/src/shell_install/bash.rs
+++ b/src/shell_install/bash.rs
@@ -13,12 +13,12 @@ use {
super::{util, ShellInstall},
crate::{
conf,
- errors::ProgramError,
+ errors::*,
},
directories::UserDirs,
lazy_regex::regex,
regex::Captures,
- std::{env, fs::OpenOptions, io::Write, path::PathBuf},
+ std::{env, path::PathBuf},
termimad::{
mad_print_inline,
},
@@ -117,7 +117,7 @@ fn get_sourcing_paths() -> Vec<PathBuf> {
/// check whether the shell function is installed, install
/// it if it wasn't refused before or if broot is launched
/// with --install.
-pub fn install(si: &mut ShellInstall) -> Result<(), ProgramError> {
+pub fn install(si: &mut ShellInstall) -> Result<(), ShellInstallError> {
let script_path = get_script_path();
si.write_script(&script_path, BASH_FUNC)?;
let link_path = get_link_path();
@@ -139,13 +139,7 @@ pub fn install(si: &mut ShellInstall) -> Result<(), ProgramError> {
&sourcing_path_str,
);
} else {
- let mut shellrc = OpenOptions::new()
- .write(true)
- .append(true)
- .open(sourcing_path)?;
- shellrc.write_all(b"\n")?;
- shellrc.write_all(source_line.as_bytes())?;
- shellrc.write_all(b"\n")?;
+ util::append_to_file(sourcing_path, format!("\n{source_line}\n"))?;
let is_zsh = sourcing_path_str.contains(".zshrc");
if is_zsh {
mad_print_inline!(
diff --git a/src/shell_install/fish.rs b/src/shell_install/fish.rs
index e56343e..720db20 100644
--- a/src/shell_install/fish.rs
+++ b/src/shell_install/fish.rs
@@ -15,7 +15,7 @@
use {
super::ShellInstall,
- crate::{conf, errors::ProgramError},
+ crate::{conf, errors::*},
directories::BaseDirs,
directories::ProjectDirs,
std::path::PathBuf,
@@ -94,7 +94,7 @@ fn get_script_path() -> PathBuf {
///
/// As fish isn't frequently used, we first check that it seems
/// to be installed. If not, we just do nothing.
-pub fn install(si: &mut ShellInstall) -> Result<(), ProgramError> {
+pub fn install(si: &mut ShellInstall) -> Result<(), ShellInstallError> {
let fish_dir = get_fish_dir();
if !fish_dir.exists() {
debug!("no fish config directory. Assuming fish isn't used.");
diff --git a/src/shell_install/mod.rs b/src/shell_install/mod.rs
index f3de0ac..356c714 100644
--- a/src/shell_install/mod.rs
+++ b/src/shell_install/mod.rs
@@ -2,11 +2,12 @@ use {
crate::{
cli::{self, ShellInstallState},
conf,
- errors::ProgramError,
+ errors::*,
skin,
},
std::{
- fs, io, os,
+ fs,
+ os,
path::{Path, PathBuf},
},
termimad::{mad_print_inline, MadSkin},
@@ -14,6 +15,7 @@ use {
mod bash;
mod fish;
+mod nushell;
mod util;
const MD_INSTALL_REQUEST: &str = r#"
@@ -34,6 +36,13 @@ If you want the `br` shell function, you may either
"#;
+const MD_PERMISSION_DENIED: &str = r#"
+Installation check resulted in **Permission Denied**.
+Please relaunch with elevated privilege.
+This is typically only needed once.
+Error details:
+"#;
+
const MD_INSTALL_DONE: &str = r#"
The **br** function has been successfully installed.
You may have to restart your shell or source your shell init files.
@@ -68,23 +77,29 @@ pub struct ShellInstall {
/// or test scripts when we don't want the user to be prompted
/// to install the function, or in case something doesn't properly
/// work in shell detections
-pub fn write_state(state: ShellInstallState) -> Result<(), ProgramError> {
+pub fn write_state(state: ShellInstallState) -> Result<(), ShellInstallError> {
let refused_path = get_refused_path();
let installed_path = get_installed_path();
if installed_path.exists() {
- fs::remove_file(&installed_path)?;
+ fs::remove_file(&installed_path)
+ .context(&|| format!("removing {:?}", &installed_path))?;
}
if refused_path.exists() {
- fs::remove_file(&refused_path)?;
+ fs::remove_file(&refused_path)
+ .context(&|| format!("removing {:?}", &refused_path))?;
}
match state {
ShellInstallState::Refused => {
- fs::create_dir_all(refused_path.parent().unwrap())?;
- fs::write(&refused_path, REFUSED_FILE_CONTENT)?;
+ fs::create_dir_all(refused_path.parent().unwrap())
+ .context(&|| format!("creating parents of {refused_path:?}"))?;
+ fs::write(&refused_path, REFUSED_FILE_CONTENT)
+ .context(&|| format!("writing in {refused_path:?}"))?;
}
ShellInstallState::Installed => {
- fs::create_dir_all(installed_path.parent().unwrap())?;
- fs::write(&installed_path, INSTALLED_FILE_CONTENT)?;
+ fs::create_dir_all(installed_path.parent().unwrap())
+ .context(&|| format!("creating parents of {installed_path:?}"))?;
+ fs::write(&installed_path, INSTALLED_FILE_CONTENT)
+ .context(&|| format!("writing in {installed_path:?}"))?;
}
_ => {}
}
@@ -116,6 +131,7 @@ impl ShellInstall {
match shell {
"bash" | "zsh" => println!("{}", bash::get_script()),
"fish" => println!("{}", fish::get_script()),
+ "nushell" => println!("{}", nushell::get_script()),
_ => {
return Err(ProgramError::UnknowShell {
shell: shell.to_string(),
@@ -128,7 +144,7 @@ impl ShellInstall {
/// check whether the shell function is installed, install
/// it if it wasn't refused before or if broot is launched
/// with --install.
- pub fn check(&mut self) -> Result<(), ProgramError> {
+ pub fn check(&mut self) -> Result<(), ShellInstallError> {
let installed_path = get_installed_path();
if self.force_install {
self.skin.print_text("You requested a clean (re)install.");
@@ -152,6 +168,7 @@ impl ShellInstall {
debug!("Starting install");
bash::install(self)?;
fish::install(self)?;
+ nushell::install(self)?;
self.should_quit = true;
if self.done {
self.skin.print_text(MD_INSTALL_DONE);
@@ -159,19 +176,28 @@ impl ShellInstall {
Ok(())
}
- pub fn remove(&self, path: &Path) -> io::Result<()> {
+ /// print some additional information on the error (typically before
+ /// the error itself is dumped)
+ pub fn comment_error(&self, err: &ShellInstallError) {
+ if err.is_permission_denied() {
+ self.skin.print_text(MD_PERMISSION_DENIED);
+ }
+ }
+
+ pub fn remove(&self, path: &Path) -> Result<(), ShellInstallError> {
// path.exists() doesn't work when the file is a link (it checks whether
// the link destination exists instead of checking the link exists
// so we first check whether the link exists
if fs::read_link(path).is_ok() || path.exists() {
mad_print_inline!(self.skin, "Removing `$0`.\n", path.to_string_lossy());
- fs::remove_file(path)?;
+ fs::remove_file(path)
+ .context(&|| format!("removing {path:?}"))?;
}
Ok(())
}
/// check whether we're allowed to install.
- fn can_do(&mut self) -> Result<bool, ProgramError> {
+ fn can_do(&mut self) -> Result<bool, ShellInstallError> {
if let Some(authorization) = self.authorization {
return Ok(authorization);
}
@@ -181,7 +207,8 @@ impl ShellInstall {
return Ok(false);
}
self.skin.print_text(MD_INSTALL_REQUEST);
- let proceed = cli::ask_authorization()?;
+ let proceed = cli::ask_authorization()
+ .context(&|| "asking user".to_string())?; // read_line failure
debug!("proceed: {:?}", proceed);
self.authorization = Some(proceed);
if !proceed {
@@ -192,7 +219,7 @@ impl ShellInstall {
}
/// write the script at the given path
- fn write_script(&self, script_path: &Path, content: &str) -> Result<(), ProgramError> {
+ fn write_script(&self, script_path: &Path, content: &str) -> Result<(), ShellInstallError> {
self.remove(script_path)?;
info!("Writing `br` shell function in `{:?}`", &script_path);
mad_print_inline!(
@@ -200,13 +227,16 @@ impl ShellInstall {
"Writing *br* shell function in `$0`.\n",
script_path.to_string_lossy(),
);
- fs::create_dir_all(script_path.parent().unwrap())?;
- fs::write(script_path, content)?;
+ fs::create_dir_all(script_path.parent().unwrap())
+ .context(&|| format!("creating parent dirs to {script_path:?}"))?;
+ fs::write(script_path, content)
+ .context(&|| format!("writing script in {script_path:?}"))?;
Ok(())
}
+
/// create a link
- fn create_link(&self, link_path: &Path, script_path: &Path) -> Result<(), ProgramError> {
+ fn create_link(&self, link_path: &Path, script_path: &Path) -> Result<(), ShellInstallError> {
info!("Creating link from {:?} to {:?}", &link_path, &script_path);
self.remove(link_path)?;
let link_path_str = link_path.to_string_lossy();
@@ -217,11 +247,15 @@ impl ShellInstall {
&link_path_str,
&script_path_str,
);
- fs::create_dir_all(link_path.parent().unwrap())?;
+ let parent = link_path.parent().unwrap();
+ fs::create_dir_all(parent)
+ .context(&|| format!("creating directory {parent:?}"))?;
#[cfg(unix)]
- os::unix::fs::symlink(script_path, link_path)?;
+ os::unix::fs::symlink(script_path, link_path)
+ .context(&|| format!("linking from {link_path:?} to {script_path:?}"))?;
#[cfg(windows)]
- os::windows::fs::symlink_file(&script_path, &link_path)?;
+ os::windows::fs::symlink_file(&script_path, &link_path)
+ .context(&|| format!("linking from {link_path:?} to {script_path:?}"))?;
Ok(())
}
}
diff --git a/src/shell_install/nushell.rs b/src/shell_install/nushell.rs
new file mode 100644
index 0000000..228a28f
--- /dev/null
+++ b/src/shell_install/nushell.rs
@@ -0,0 +1,123 @@
+//! The goal of this mod is to ensure the launcher shell function
+//! is available for nushell i.e. the `br` shell function can
+//! be used to launch broot (and thus make it possible to execute
+//! some commands, like `cd`, from the starting shell.
+//!
+//! In a correct installation, we have:
+//! - a function declaration script in ~/.local/share/broot/launcher/nushell/br/1
+//! - a link to that script in ~/.config/broot/launcher/nushell/br/1
+//! - a line to source the link in ~/.config/nushell/config.nu
+//! (exact paths depend on XDG variables)
+//!
+//! More info at
+//! https://github.com/Canop/broot/issues/375
+
+use {
+ super::{util, ShellInstall},
+ crate::{
+ conf,
+ errors::*,
+ },
+ directories::BaseDirs,
+ std::path::PathBuf,
+ termimad::{
+ mad_print_inline,
+ },
+};
+
+const NAME: &str = "nushell";
+const VERSION: &str = "1";
+
+const NU_FUNC: &str = r#"
+# This script was automatically generated by the broot program
+# More information can be found in https://github.com/Canop/broot
+# This function starts broot and executes the command
+# it produces, if any.
+# It's needed because some shell commands, like `cd`,
+# have no useful effect if executed in a subshell.
+def _br_cmd [] {
+ let cmd_file = ([ $nu.temp-path, $"broot-(random chars).tmp" ] | path join)
+ touch $cmd_file
+ ^broot --outcmd $cmd_file
+ let target_dir = (open $cmd_file | to text | str replace "^cd\\s+" "" | str trim)
+ rm -p -f $cmd_file
+
+ $target_dir
+}
+alias br = cd (_br_cmd)
+"#;
+
+pub fn get_script() -> &'static str {
+ NU_FUNC
+}
+
+/// return the path to the link to the function script
+fn get_link_path() -> PathBuf {
+ conf::dir().join("launcher").join(NAME).join("br")
+}
+
+/// return the root of
+fn get_nushell_dir() -> Option<PathBuf> {
+ BaseDirs::new()
+ .map(|base_dirs| base_dirs.config_dir().join("nushell"))
+ .filter(|dir| dir.exists())
+}
+
+/// return the path to the script containing the function.
+///
+/// In XDG_DATA_HOME (typically ~/.local/share on linux)
+fn get_script_path() -> PathBuf {
+ conf::app_dirs()
+ .data_dir()
+ .join("launcher")
+ .join(NAME)
+ .join(VERSION)
+}
+
+/// Check for nushell.
+///
+/// Check whether the shell function is installed, install
+/// it if it wasn't refused before or if broot is launched
+/// with --install.
+pub fn install(si: &mut ShellInstall) -> Result<(), ShellInstallError> {
+ debug!("install {NAME}");
+ let Some(nushell_dir) = get_nushell_dir() else {
+ debug!("no nushell config directory. Assuming nushell isn't used.");
+ return Ok(());
+ };
+ info!("nushell seems to be installed");
+ let script_path = get_script_path();
+ si.write_script(&script_path, NU_FUNC)?;
+ let link_path = get_link_path();
+ si.create_link(&link_path, &script_path)?;
+
+ let escaped_path = link_path.to_string_lossy().replace(' ', "\\ ");
+ let source_line = format!("source {}", &escaped_path);
+
+ let sourcing_path = nushell_dir.join("config.nu");
+ if !sourcing_path.exists() {
+ warn!("Unexpected lack of config.nu file");
+ return Ok(());
+ }
+ if sourcing_path.is_dir() {
+ warn!("config.nu file");
+ return Ok(());
+ }
+ let sourcing_path_str = sourcing_path.to_string_lossy();
+ if util::file_contains_line(&sourcing_path, &source_line)? {
+ mad_print_inline!(
+ &si.skin,
+ "`$0` already patched, no change made.\n",
+ &sourcing_path_str,
+ );
+ } else {
+ util::append_to_file(&sourcing_path, format!("\n{source_line}\n"))?;
+ mad_print_inline!(
+ &si.skin,
+ "`$0` successfully patched, you can make the function immediately available with `source $0`\n",
+ &sourcing_path_str,
+ );
+ }
+ si.done = true;
+ Ok(())
+}
diff --git a/src/shell_install/util.rs b/src/shell_install/util.rs
index 13ad5d0..34d11d1 100644
--- a/src/shell_install/util.rs
+++ b/src/shell_install/util.rs
@@ -1,14 +1,30 @@
-use std::{
- fs,
- io::{self, BufRead, BufReader},
- path::Path,
+use {
+ crate::errors::*,
+ std::{
+ fs::{self, OpenOptions},
+ io::{BufRead, BufReader, Write},
+ path::Path,
+ },
};
-
-pub fn file_contains_line(path: &Path, searched_line: &str) -> io::Result<bool> {
- for line in BufReader::new(fs::File::open(path)?).lines() {
- if line? == searched_line {
+pub fn file_contains_line(path: &Path, searched_line: &str) -> Result<bool, ShellInstallError> {
+ let file = fs::File::open(path)
+ .context(&|| format!("opening {path:?}"))?;
+ for line in BufReader::new(file).lines() {
+ let line = line.context(&|| format!("reading line in {path:?}"))?;
+ if line == searched_line {
return Ok(true);
}
}
Ok(false)
}
+
+pub fn append_to_file<S: AsRef<str>>(path: &Path, content: S) -> Result<(), ShellInstallError> {
+ let mut shellrc = OpenOptions::new()
+ .write(true)
+ .append(true)
+ .open(path)
+ .context(&|| format!("opening {path:?} for append"))?;
+ shellrc.write_all(content.as_ref().as_bytes())
+ .context(&|| format!("writing in {path:?}"))?;
+ Ok(())
+}
diff --git a/website/broot_theme/css/base.css b/website/broot_theme/css/base.css
index ab8471b..d569453 100644
--- a/website/broot_theme/css/base.css
+++ b/website/broot_theme/css/base.css
@@ -22,7 +22,7 @@ body > .container {
}
ul.nav .main {
- font-weight: bold;
+ /*font-weight: bold;*/
}
.col-md-3 {
@@ -177,7 +177,7 @@ footer {
.bs-sidebar .nav > .active > a,
.bs-sidebar .nav > .active:hover > a,
.bs-sidebar .nav > .active:focus > a {
- font-weight: bold;
+ /*font-weight: bold;*/
background-color: transparent;
border-right: 1px solid;
}
diff --git a/website/broot_theme/css/bootstrap-custom.min.css b/website/broot_theme/css/bootstrap-custom.min.css
index d85b1dc..a0576ba 100644
--- a/website/broot_theme/css/bootstrap-custom.min.css
+++ b/website/broot_theme/css/bootstrap-custom.min.css
@@ -1 +1 @@
-/*! normalize.css v2.1.3 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden],template{display:none}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a{background:transparent}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{margin:.67em 0;font-size:2em}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}hr{height:0;-moz-box-sizing:content-box;box-sizing:content-box}mark{color:#000;background:#ff0}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid #c0c0c0}legend{padding:0;border:0}button,input,select,textarea{margin:0;font-family:inherit;font-size:100%}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{padding:0;box-sizing:border-box}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:2cm .5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}*,*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.428571429;color:#555;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#2fa4e7;text-decoration:none}a:hover,a:focus{color:#157ab5;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}img{vertical-align:middle}.img-responsive{display:block;height:auto;max-width:100%}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;height:auto;max-width:100%;padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:500;line-height:1.1;color:#317eac}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{margin-top:20px;margin-bottom:10px}h1 small,h2 small,h3 small,h1 .small,h2 .small,h3 .small{font-size:65%}h4,h5,h6{margin-top:10px;margin-bottom:10px}h4 small,h5 small,h6 small,h4 .small,h5 .small,h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:200;line-height:1.4}@media(min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}.text-muted{color:#999}.text-primary{color:#2fa4e7}.text-primary:hover{color:#178acc}.text-warning{color:#c09853}.text-warning:hover{color:#a47e3c}.text-danger{color:#b94a48}.text-danger:hover{color:#953b39}.text-success{color:#468847}.text-success:hover{color:#356635}.text-info{color:#3a87ad}.text-info:hover{color:#2d6987}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}.list-inline>li:first-child{padding-left:0}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.428571429}dt{font-weight:bold}dd{margin-left:0}@media(min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{font-size:17.5px;font-weight:300;line-height:1.25}blockquote p:last-child{margin-bottom:0}blockquote small,blockquote .small{display:block;line-height:1.428571429;color:#999}blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small,blockquote.pull-right .small{text-align:right}blockquote.pull-right small:before,blockquote.pull-right .small:before{content:''}blockquote.pull-right small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.428571429}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;white-space:nowrap;background-color:#f9f2f4;border-radius:4px}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.428571429;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}@media(min-width:768px){.container{width:750px}}@media(min-width:992px){.container{width:970px}}@media(min-width:1200px){.container{width:1170px}}.row{margin-right:-15px;margin-left:-15px}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666666666666%}.col-xs-10{width:83.33333333333334%}.col-xs-9{width:75%}.col-xs-8{width:66.66666666666666%}.col-xs-7{width:58.333333333333336%}.col-xs-6{width:50%}.col-xs-5{width:41.66666666666667%}.col-xs-4{width:33.33333333333333%}.col-xs-3{width:25%}.col-xs-2{width:16.666666666666664%}.col-xs-1{width:8.333333333333332%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666666666666%}.col-xs-pull-10{right:83.33333333333334%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666666666666%}.col-xs-pull-7{right:58.333333333333336%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666666666667%}.col-xs-pull-4{right:33.33333333333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.666666666666664%}.col-xs-pull-1{right:8.333333333333332%}.col-xs-pull-0{right:0}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666666666666%}.col-xs-push-10{left:83.33333333333334%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666666666666%}.col-xs-push-7{left:58.333333333333336%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666666666667%}.col-xs-push-4{left:33.33333333333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.666666666666664%}.col-xs-push-1{left:8.333333333333332%}.col-xs-push-0{left:0}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666666666666%}.col-xs-offset-10{margin-left:83.33333333333334%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666666666666%}.col-xs-offset-7{margin-left:58.333333333333336%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666666666667%}.col-xs-offset-4{margin-left:33.33333333333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.666666666666664%}.col-xs-offset-1{margin-left:8.333333333333332%}.col-xs-offset-0{margin-left:0}@media(min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666666666666%}.col-sm-10{width:83.33333333333334%}.col-sm-9{width:75%}.col-sm-8{width:66.66666666666666%}.col-sm-7{width:58.333333333333336%}.col-sm-6{width:50%}.col-sm-5{width:41.66666666666667%}.col-sm-4{width:33.33333333333333%}.col-sm-3{width:25%}.col-sm-2{width:16.666666666666664%}.col-sm-1{width:8.333333333333332%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666666666666%}.col-sm-pull-10{right:83.33333333333334%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666666666666%}.col-sm-pull-7{right:58.333333333333336%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666666666667%}.col-sm-pull-4{right:33.33333333333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.666666666666664%}.col-sm-pull-1{right:8.333333333333332%}.col-sm-pull-0{right:0}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666666666666%}.col-sm-push-10{left:83.33333333333334%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666666666666%}.col-sm-push-7{left:58.333333333333336%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666666666667%}.col-sm-push-4{left:33.33333333333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.666666666666664%}.col-sm-push-1{left:8.333333333333332%}.col-sm-push-0{left:0}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666666666666%}.col-sm-offset-10{margin-left:83.33333333333334%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666666666666%}.col-sm-offset-7{margin-left:58.333333333333336%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666666666667%}.col-sm-offset-4{margin-left:33.33333333333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.666666666666664%}.col-sm-offset-1{margin-left:8.333333333333332%}.col-sm-offset-0{margin-left:0}}@media(min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666666666666%}.col-md-10{width:83.33333333333334%}.col-md-9{width:75%}.col-md-8{width:66.66666666666666%}.col-md-7{width:58.333333333333336%}.col-md-6{width:50%}.col-md-5{width:41.66666666666667%}.col-md-4{width:33.33333333333333%}.col-md-3{width:25%}.col-md-2{width:16.666666666666664%}.col-md-1{width:8.333333333333332%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666666666666%}.col-md-pull-10{right:83.33333333333334%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666666666666%}.col-md-pull-7{right:58.333333333333336%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666666666667%}.col-md-pull-4{right:33.33333333333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.666666666666664%}.col-md-pull-1{right:8.333333333333332%}.col-md-pull-0{right:0}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666666666666%}.col-md-push-10{left:83.33333333333334%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666666666666%}.col-md-push-7{left:58.333333333333336%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666666666667%}.col-md-push-4{left:33.33333333333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.666666666666664%}.col-md-push-1{left:8.333333333333332%}.col-md-push-0{left:0}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666666666666%}.col-md-offset-10{margin-left:83.33333333333334%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666666666666%}.col-md-offset-7{margin-left:58.333333333333336%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666666666667%}.col-md-offset-4{margin-left:33.33333333333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.666666666666664%}.col-md-offset-1{margin-left:8.333333333333332%}.col-md-offset-0{margin-left:0}}@media(min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666666666666%}.col-lg-10{width:83.33333333333334%}.col-lg-9{width:75%}.col-lg-8{width:66.66666666666666%}.col-lg-7{width:58.333333333333336%}.col-lg-6{width:50%}.col-lg-5{width:41.66666666666667%}.col-lg-4{width:33.33333333333333%}.col-lg-3{width:25%}.col-lg-2{width:16.666666666666664%}.col-lg-1{width:8.333333333333332%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666666666666%}.col-lg-pull-10{right:83.33333333333334%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666666666666%}.col-lg-pull-7{right:58.333333333333336%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666666666667%}.col-lg-pull-4{right:33.33333333333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.666666666666664%}.col-lg-pull-1{right:8.333333333333332%}.col-lg-pull-0{right:0}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666666666666%}.col-lg-push-10{left:83.33333333333334%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666666666666%}.col-lg-push-7{left:58.333333333333336%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666666666667%}.col-lg-push-4{left:33.33333333333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.666666666666664%}.col-lg-push-1{left:8.333333333333332%}.col-lg-push-0{left:0}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666666666666%}.col-lg-offset-10{margin-left:83.33333333333334%}.col-lg-offset-9{marg