Compare commits
No commits in common. "e1d70e2c4f0105246e29460ab837ecabfffcdbd5" and "f70154e819ed3415e86e4a7fc5cd54ed1a4851ed" have entirely different histories.
e1d70e2c4f
...
f70154e819
6 changed files with 53 additions and 116 deletions
20
bunbun.default.toml
Normal file
20
bunbun.default.toml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# The location which your server is listening on and binds to. You must restart
|
||||||
|
# bunbun for changes to take effect for this config option.
|
||||||
|
bind_address: "127.0.0.1:8080"
|
||||||
|
|
||||||
|
# The root location where people can access your instance of bunbun
|
||||||
|
public_address: "localhost:8080"
|
||||||
|
|
||||||
|
# A default route, if no route is was matched. If none were matched, the entire
|
||||||
|
# query is used as the query for the default route. This field is optional.
|
||||||
|
default_route: "g"
|
||||||
|
|
||||||
|
routes:
|
||||||
|
# Meta
|
||||||
|
ls: "/ls"
|
||||||
|
help: "/ls"
|
||||||
|
list: "/ls"
|
||||||
|
# Google
|
||||||
|
g: "https://google.com/search?q={{query}}"
|
||||||
|
yt: "https://www.youtube.com/results?search_query={{query}}"
|
||||||
|
r: "https://reddit.com/r/{{query}}"
|
|
@ -1,35 +0,0 @@
|
||||||
# The location which your server is listening on and binds to. You must restart
|
|
||||||
# bunbun for changes to take effect for this config option.
|
|
||||||
bind_address: "127.0.0.1:8080"
|
|
||||||
|
|
||||||
# The root location where people can access your instance of bunbun
|
|
||||||
public_address: "localhost:8080"
|
|
||||||
|
|
||||||
# A default route, if no route is was matched. If none were matched, the entire
|
|
||||||
# query is used as the query for the default route. This field is optional, but
|
|
||||||
# highly recommended for ease-of-use.
|
|
||||||
default_route: "g"
|
|
||||||
|
|
||||||
# A list containing route groups. Each route group must have a name and a
|
|
||||||
# mapping of routes, with an optional description field. Each route mapping may
|
|
||||||
# contain "{{query}}", which will be populated by the user's search query. This
|
|
||||||
# input is percent-escaped. If multiple routes are defined, then the later
|
|
||||||
# defined route is used.
|
|
||||||
groups:
|
|
||||||
-
|
|
||||||
name: "Meta commands"
|
|
||||||
description: "Commands for bunbun"
|
|
||||||
routes:
|
|
||||||
ls: &ls "/ls"
|
|
||||||
help: *ls
|
|
||||||
list: *ls
|
|
||||||
-
|
|
||||||
name: "Google"
|
|
||||||
routes:
|
|
||||||
g: "https://google.com/search?q={{query}}"
|
|
||||||
yt: "https://www.youtube.com/results?search_query={{query}}"
|
|
||||||
-
|
|
||||||
name: "Uncategorized routes"
|
|
||||||
description: "One-off routes with no specific grouping"
|
|
||||||
routes:
|
|
||||||
r: "https://reddit.com/r/{{query}}"
|
|
|
@ -21,5 +21,5 @@ args:
|
||||||
- config:
|
- config:
|
||||||
short: "c"
|
short: "c"
|
||||||
long: "config"
|
long: "config"
|
||||||
default_value: "/etc/bunbun.yaml"
|
default_value: "/etc/bunbun.toml"
|
||||||
help: Specify the location of the config file to read from. Needs read/write permissions.
|
help: Specify the location of the config file to read from. Needs read/write permissions.
|
||||||
|
|
81
src/main.rs
81
src/main.rs
|
@ -5,7 +5,7 @@ use handlebars::Handlebars;
|
||||||
use hotwatch::{Event, Hotwatch};
|
use hotwatch::{Event, Hotwatch};
|
||||||
use libc::daemon;
|
use libc::daemon;
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -17,7 +17,7 @@ use std::time::Duration;
|
||||||
mod routes;
|
mod routes;
|
||||||
mod template_args;
|
mod template_args;
|
||||||
|
|
||||||
const DEFAULT_CONFIG: &[u8] = include_bytes!("../bunbun.default.yaml");
|
const DEFAULT_CONFIG: &[u8] = include_bytes!("../bunbun.default.toml");
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(clippy::enum_variant_names)]
|
#[allow(clippy::enum_variant_names)]
|
||||||
|
@ -61,8 +61,6 @@ from_error!(log::SetLoggerError, LoggerInitError);
|
||||||
pub struct State {
|
pub struct State {
|
||||||
public_address: String,
|
public_address: String,
|
||||||
default_route: Option<String>,
|
default_route: Option<String>,
|
||||||
groups: Vec<RouteGroup>,
|
|
||||||
/// Cached, flattened mapping of all routes and their destinations.
|
|
||||||
routes: HashMap<String, String>,
|
routes: HashMap<String, String>,
|
||||||
renderer: Handlebars,
|
renderer: Handlebars,
|
||||||
}
|
}
|
||||||
|
@ -74,20 +72,30 @@ fn main() -> Result<(), BunBunError> {
|
||||||
.author(crate_authors!())
|
.author(crate_authors!())
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
init_logger(
|
let log_level = match min(matches.occurrences_of("verbose"), 3) as i8
|
||||||
matches.occurrences_of("verbose"),
|
- min(matches.occurrences_of("quiet"), 2) as i8
|
||||||
matches.occurrences_of("quiet"),
|
{
|
||||||
)?;
|
-2 => None,
|
||||||
|
-1 => Some(log::Level::Error),
|
||||||
|
0 => Some(log::Level::Warn),
|
||||||
|
1 => Some(log::Level::Info),
|
||||||
|
2 => Some(log::Level::Debug),
|
||||||
|
3 => Some(log::Level::Trace),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
// config has default location provided, unwrapping is fine.
|
if let Some(level) = log_level {
|
||||||
|
simple_logger::init_with_level(level)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// config has default location provided
|
||||||
let conf_file_location = String::from(matches.value_of("config").unwrap());
|
let conf_file_location = String::from(matches.value_of("config").unwrap());
|
||||||
let conf = read_config(&conf_file_location)?;
|
let conf = read_config(&conf_file_location)?;
|
||||||
let renderer = compile_templates();
|
let renderer = compile_templates();
|
||||||
let state = Arc::from(RwLock::new(State {
|
let state = Arc::from(RwLock::new(State {
|
||||||
public_address: conf.public_address,
|
public_address: conf.public_address,
|
||||||
default_route: conf.default_route,
|
default_route: conf.default_route,
|
||||||
routes: cache_routes(&conf.groups),
|
routes: conf.routes,
|
||||||
groups: conf.groups,
|
|
||||||
renderer,
|
renderer,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -102,6 +110,7 @@ fn main() -> Result<(), BunBunError> {
|
||||||
|
|
||||||
let mut watch = Hotwatch::new_with_custom_delay(Duration::from_millis(500))?;
|
let mut watch = Hotwatch::new_with_custom_delay(Duration::from_millis(500))?;
|
||||||
// TODO: keep retry watching in separate thread
|
// TODO: keep retry watching in separate thread
|
||||||
|
|
||||||
// Closures need their own copy of variables for proper lifecycle management
|
// Closures need their own copy of variables for proper lifecycle management
|
||||||
let state_ref = state.clone();
|
let state_ref = state.clone();
|
||||||
let conf_file_location_clone = conf_file_location.clone();
|
let conf_file_location_clone = conf_file_location.clone();
|
||||||
|
@ -114,8 +123,7 @@ fn main() -> Result<(), BunBunError> {
|
||||||
Ok(conf) => {
|
Ok(conf) => {
|
||||||
state.public_address = conf.public_address;
|
state.public_address = conf.public_address;
|
||||||
state.default_route = conf.default_route;
|
state.default_route = conf.default_route;
|
||||||
state.routes = cache_routes(&conf.groups);
|
state.routes = conf.routes;
|
||||||
state.groups = conf.groups;
|
|
||||||
info!("Successfully updated active state");
|
info!("Successfully updated active state");
|
||||||
}
|
}
|
||||||
Err(e) => warn!("Failed to update config file: {}", e),
|
Err(e) => warn!("Failed to update config file: {}", e),
|
||||||
|
@ -148,43 +156,11 @@ fn main() -> Result<(), BunBunError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes the logger based on the number of quiet and verbose flags passed
|
|
||||||
/// in. Usually, these values are mutually exclusive, that is, if the number of
|
|
||||||
/// verbose flags is non-zero then the quiet flag is zero, and vice versa.
|
|
||||||
fn init_logger(
|
|
||||||
num_verbose_flags: u64,
|
|
||||||
num_quiet_flags: u64,
|
|
||||||
) -> Result<(), BunBunError> {
|
|
||||||
let log_level =
|
|
||||||
match min(num_verbose_flags, 3) as i8 - min(num_quiet_flags, 2) as i8 {
|
|
||||||
-2 => None,
|
|
||||||
-1 => Some(log::Level::Error),
|
|
||||||
0 => Some(log::Level::Warn),
|
|
||||||
1 => Some(log::Level::Info),
|
|
||||||
2 => Some(log::Level::Debug),
|
|
||||||
3 => Some(log::Level::Trace),
|
|
||||||
_ => unreachable!(), // values are clamped to [0, 3] - [0, 2]
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(level) = log_level {
|
|
||||||
simple_logger::init_with_level(level)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Config {
|
struct Config {
|
||||||
bind_address: String,
|
bind_address: String,
|
||||||
public_address: String,
|
public_address: String,
|
||||||
default_route: Option<String>,
|
default_route: Option<String>,
|
||||||
groups: Vec<RouteGroup>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
|
||||||
struct RouteGroup {
|
|
||||||
name: String,
|
|
||||||
description: Option<String>,
|
|
||||||
routes: HashMap<String, String>,
|
routes: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,21 +200,6 @@ fn read_config(config_file_path: &str) -> Result<Config, BunBunError> {
|
||||||
Ok(serde_yaml::from_str(&config_str)?)
|
Ok(serde_yaml::from_str(&config_str)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cache_routes(groups: &[RouteGroup]) -> HashMap<String, String> {
|
|
||||||
let mut mapping = HashMap::new();
|
|
||||||
for group in groups {
|
|
||||||
for (kw, dest) in &group.routes {
|
|
||||||
match mapping.insert(kw.clone(), dest.clone()) {
|
|
||||||
None => trace!("Inserting {} into mapping.", kw),
|
|
||||||
Some(old_value) => {
|
|
||||||
debug!("Overriding {} route from {} to {}.", kw, old_value, dest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mapping
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an instance with all pre-generated templates included into the
|
/// Returns an instance with all pre-generated templates included into the
|
||||||
/// binary. This allows for users to have a portable binary without needed the
|
/// binary. This allows for users to have a portable binary without needed the
|
||||||
/// templates at runtime.
|
/// templates at runtime.
|
||||||
|
|
|
@ -23,7 +23,7 @@ const FRAGMENT_ENCODE_SET: &AsciiSet = &CONTROLS
|
||||||
#[get("/ls")]
|
#[get("/ls")]
|
||||||
pub fn list(data: Data<Arc<RwLock<State>>>) -> impl Responder {
|
pub fn list(data: Data<Arc<RwLock<State>>>) -> impl Responder {
|
||||||
let data = data.read().unwrap();
|
let data = data.read().unwrap();
|
||||||
HttpResponse::Ok().body(data.renderer.render("list", &data.groups).unwrap())
|
HttpResponse::Ok().body(data.renderer.render("list", &data.routes).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
|
@ -14,29 +14,20 @@
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
}
|
}
|
||||||
h1, p { margin: 0; }
|
h1, p { margin: 0; }
|
||||||
table { margin-bottom: 1rem; }
|
table { margin-top: 1em; }
|
||||||
header { display: flex; flex-wrap: wrap; align-items: baseline; margin-top: 2rem; }
|
td, th { padding: 0 0.5em; }
|
||||||
header h2 { margin: 0 1rem 0 0; }
|
|
||||||
i { color: rgba(255, 255, 255, 0.5); }
|
|
||||||
td, th { padding: 0 0.5rem; }
|
|
||||||
.shortcut { text-align: right; }
|
.shortcut { text-align: right; }
|
||||||
.target { text-align: left; width: 100%; }
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Bunbun Command List</h1>
|
<h1>Bunbun Command List</h1>
|
||||||
<p><i>To edit this list, edit your <code>bunbun.toml</code> file.</i></p>
|
<p><em>To edit this list, edit your <code>bunbun.toml</code> file.</em></p>
|
||||||
<main>
|
|
||||||
{{#each this}} {{!-- Iterate over RouteGroup --}}
|
|
||||||
<header><h2>{{this.name}}</h2><i>{{this.description}}</i></header>
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Shortcut</th>
|
<th>Shortcut</th>
|
||||||
<th class="target">Target</th>
|
<th>Target</th>
|
||||||
</tr>
|
</tr>
|
||||||
{{#each this.routes}}<tr><td class="shortcut">{{@key}}</td><td class="target">{{this}}</td></tr>{{/each}}
|
{{#each this}}<tr><td class="shortcut">{{@key}}</td><td class="target">{{this}}</td></tr>{{/each}}
|
||||||
</table>
|
</table>
|
||||||
{{/each}}
|
|
||||||
</main>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue