Move business logic out of main, use askama and bound the ENTRIES
This commit is contained in:
parent
3093b06b4e
commit
9d4b3ecafc
8 changed files with 156 additions and 578 deletions
21
src/highlight.rs
Normal file
21
src/highlight.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
extern crate syntect;
|
||||
|
||||
use syntect::parsing::SyntaxSet;
|
||||
use syntect::highlighting::ThemeSet;
|
||||
use syntect::easy::HighlightLines;
|
||||
use syntect::html::{styled_line_to_highlighted_html, IncludeBackground};
|
||||
|
||||
/// Takes the content of a paste and the extension passed in by the viewer and will return the content
|
||||
/// highlighted in the appropriate format in HTML.
|
||||
pub fn highlight(content: &str, ext: &str) -> Option<String> {
|
||||
lazy_static! {
|
||||
static ref SS: SyntaxSet = SyntaxSet::load_defaults_newlines();
|
||||
static ref TS: ThemeSet = ThemeSet::load_defaults();
|
||||
}
|
||||
|
||||
let syntax = SS.find_syntax_by_extension(ext)?;
|
||||
let mut h = HighlightLines::new(syntax, &TS.themes["base16-ocean.dark"]);
|
||||
let regions = h.highlight(content, &SS);
|
||||
|
||||
Some(styled_line_to_highlighted_html(®ions[..], IncludeBackground::No))
|
||||
}
|
45
src/io.rs
Normal file
45
src/io.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
extern crate gpw;
|
||||
extern crate linked_hash_map;
|
||||
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
|
||||
use std::sync::RwLock;
|
||||
use std::env;
|
||||
use std::cell::RefCell;
|
||||
|
||||
lazy_static! {
|
||||
static ref ENTRIES: RwLock<LinkedHashMap<String, String>> = RwLock::new(LinkedHashMap::new());
|
||||
}
|
||||
|
||||
/// Ensures `ENTRIES` is less than the size of `BIN_BUFFER_SIZE`. If it isn't then
|
||||
/// `ENTRIES.len() - BIN_BUFFER_SIZE` elements will be popped off the front of the map.
|
||||
fn purge_old() {
|
||||
let entries_len = ENTRIES.read().unwrap().len();
|
||||
let buffer_size = env::var("BIN_BUFFER_SIZE").map(|f| f.parse::<usize>().unwrap()).unwrap_or(1000usize);
|
||||
|
||||
if entries_len > buffer_size {
|
||||
let to_remove = entries_len - buffer_size;
|
||||
|
||||
let mut entries = ENTRIES.write().unwrap();
|
||||
|
||||
for _ in 0..to_remove {
|
||||
entries.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a randomly generated id, stores the given paste under that id and then returns the id.
|
||||
pub fn store_paste(content: String) -> String {
|
||||
thread_local!(static KEYGEN: RefCell<gpw::PasswordGenerator> = RefCell::new(gpw::PasswordGenerator::default()));
|
||||
let id = KEYGEN.with(|k| k.borrow_mut().next().unwrap());
|
||||
|
||||
purge_old();
|
||||
ENTRIES.write().unwrap().insert(id.clone(), content);
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
/// Get a paste by id. Returns `None` if the paste doesn't exist.
|
||||
pub fn get_paste<'a>(id: &str) -> Option<String> {
|
||||
ENTRIES.read().unwrap().get(id).map(|f| f.clone())
|
||||
}
|
95
src/main.rs
95
src/main.rs
|
@ -1,30 +1,35 @@
|
|||
#![feature(proc_macro_hygiene, decl_macro)]
|
||||
#![feature(uniform_paths)]
|
||||
|
||||
#[macro_use] extern crate lazy_static;
|
||||
|
||||
#[macro_use] extern crate rocket;
|
||||
extern crate rocket_contrib;
|
||||
|
||||
extern crate gpw;
|
||||
extern crate syntect;
|
||||
extern crate chashmap;
|
||||
extern crate askama;
|
||||
extern crate askama_escape;
|
||||
|
||||
mod io;
|
||||
mod highlight;
|
||||
|
||||
use io::{store_paste, get_paste};
|
||||
use highlight::highlight;
|
||||
|
||||
use askama::Template;
|
||||
use askama_escape::{MarkupDisplay, Html};
|
||||
|
||||
use rocket_contrib::templates::Template;
|
||||
use rocket::response::Redirect;
|
||||
use rocket::request::Form;
|
||||
use rocket::Data;
|
||||
|
||||
use serde::Serialize;
|
||||
use syntect::parsing::SyntaxSet;
|
||||
use syntect::highlighting::ThemeSet;
|
||||
use syntect::easy::HighlightLines;
|
||||
use syntect::html::{styled_line_to_highlighted_html, IncludeBackground};
|
||||
use std::io::Read;
|
||||
|
||||
use chashmap::CHashMap;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
struct Index {}
|
||||
|
||||
lazy_static! {
|
||||
static ref ENTRIES: CHashMap<String, String> = CHashMap::new();
|
||||
#[get("/")]
|
||||
fn index() -> Index {
|
||||
Index {}
|
||||
}
|
||||
|
||||
|
||||
|
@ -32,25 +37,6 @@ lazy_static! {
|
|||
struct IndexForm {
|
||||
val: String
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
fn index() -> Template {
|
||||
#[derive(Serialize)]
|
||||
struct Index {}
|
||||
|
||||
Template::render("index", Index {})
|
||||
}
|
||||
|
||||
|
||||
/// Generates a randomly generated id, stores the given paste under that id and then returns the id.
|
||||
fn store_paste(content: String) -> String {
|
||||
thread_local!(static KEYGEN: RefCell<gpw::PasswordGenerator> = RefCell::new(gpw::PasswordGenerator::default()));
|
||||
|
||||
let id = KEYGEN.with(|k| k.borrow_mut().next().unwrap());
|
||||
ENTRIES.insert(id.clone(), content);
|
||||
id
|
||||
}
|
||||
|
||||
#[post("/", data = "<input>")]
|
||||
fn submit(input: Form<IndexForm>) -> Redirect {
|
||||
let id = store_paste(input.into_inner().val);
|
||||
|
@ -58,53 +44,40 @@ fn submit(input: Form<IndexForm>) -> Redirect {
|
|||
}
|
||||
|
||||
#[put("/", data = "<input>")]
|
||||
fn submit_raw(input: String) -> String {
|
||||
format!("https://{}/{}", "localhost:8000", store_paste(input))
|
||||
fn submit_raw(input: Data) -> std::io::Result<String> {
|
||||
let mut data = String::new();
|
||||
input.open().take(1024 * 1000).read_to_string(&mut data)?;
|
||||
Ok(format!("https://{}/{}", "localhost:8000", store_paste(data)))
|
||||
}
|
||||
|
||||
|
||||
/// Takes the content of a paste and the extension passed in by the viewer and will return the content
|
||||
/// highlighted in the appropriate format in HTML.
|
||||
fn highlight(content: &str, ext: &str) -> Option<String> {
|
||||
lazy_static! {
|
||||
static ref SS: SyntaxSet = SyntaxSet::load_defaults_newlines();
|
||||
static ref TS: ThemeSet = ThemeSet::load_defaults();
|
||||
}
|
||||
|
||||
let syntax = SS.find_syntax_by_extension(ext)?;
|
||||
let mut h = HighlightLines::new(syntax, &TS.themes["base16-ocean.dark"]);
|
||||
let regions = h.highlight(content, &SS);
|
||||
|
||||
Some(styled_line_to_highlighted_html(®ions[..], IncludeBackground::No))
|
||||
#[derive(Template)]
|
||||
#[template(path = "paste.html")]
|
||||
struct Render {
|
||||
content: MarkupDisplay<Html, String>
|
||||
}
|
||||
|
||||
#[get("/<key>")]
|
||||
fn render<'a>(key: String) -> Option<Template> {
|
||||
fn render<'a>(key: String) -> Option<Render> {
|
||||
let mut splitter = key.splitn(2, ".");
|
||||
let key = splitter.next().unwrap();
|
||||
let ext = splitter.next();
|
||||
|
||||
// get() returns a read-only lock, we're not going to be writing to this key
|
||||
// again so we can hold this for as long as we want
|
||||
let entry = ENTRIES.get(key)?;
|
||||
let entry = get_paste(key)?;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Render<'a> {
|
||||
content: Cow<'a, String>
|
||||
}
|
||||
|
||||
Some(Template::render("paste", Render {
|
||||
Some(Render {
|
||||
content: match ext {
|
||||
None => Cow::Borrowed(&*entry),
|
||||
Some(extension) => Cow::Owned(highlight(&*entry, extension)?)
|
||||
None => MarkupDisplay::new_unsafe(entry, Html),
|
||||
Some(extension) => MarkupDisplay::new_safe(highlight(&entry, extension)?, Html)
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
rocket::ignite()
|
||||
.attach(Template::fairing())
|
||||
.mount("/", routes![index, submit, submit_raw, render])
|
||||
.launch();
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>bin.</title>
|
||||
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
html, body { margin: 0; }
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
background: #263238;
|
||||
color: #B0BEC5;
|
||||
|
||||
display: flex;
|
||||
}
|
||||
|
||||
{% block styles %}
|
||||
{% endblock styles %}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
</body>
|
||||
</html>
|
|
@ -1,59 +0,0 @@
|
|||
{% extends "base" %}
|
||||
|
||||
{% block styles %}
|
||||
form { flex: 1; }
|
||||
|
||||
textarea {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: none;
|
||||
border: none;
|
||||
color: inherit;
|
||||
font-family: monospace;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
|
||||
background: #2196F3;
|
||||
|
||||
color: white;
|
||||
font-size: 2rem;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button[type="submit"].hidden { display: none; }
|
||||
{% endblock styles %}
|
||||
|
||||
{% block content %}
|
||||
<form action="/" method="post">
|
||||
<textarea name="val" placeholder="bin something" autofocus autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
|
||||
|
||||
<button type="submit" title="⌘+⏎">✎</button>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
const form = document.querySelector('form');
|
||||
const input = document.querySelector('textarea');
|
||||
const button = document.querySelector('button[type="submit"]');
|
||||
|
||||
const onInput = () => button.classList.toggle('hidden', !input.value);
|
||||
input.addEventListener('input', onInput);
|
||||
onInput();
|
||||
|
||||
document.body.addEventListener('keydown', (e) => {
|
||||
if (e.keyCode == 13 && e.metaKey) {
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock content %}
|
|
@ -1,16 +0,0 @@
|
|||
{% extends "base" %}
|
||||
|
||||
{% block styles %}
|
||||
pre {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
overflow: scroll;
|
||||
font-family: Courier;
|
||||
line-height: 1.1;
|
||||
}
|
||||
{% endblock styles %}
|
||||
|
||||
{% block content %}
|
||||
<pre><code>{{ content | safe }}</code></pre>
|
||||
{% endblock content %}
|
Loading…
Add table
Add a link
Reference in a new issue