Initial commit

This commit is contained in:
Jordan Johnson-Doyle 2019-02-10 21:35:04 +00:00
parent 0676fb3cd5
commit 2aeb2d5e19
No known key found for this signature in database
GPG Key ID: A95F87B578CE79B6
11 changed files with 1761 additions and 4 deletions

6
.gitignore vendored
View File

@ -1,10 +1,8 @@
.idea/
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk

3
COPYING Normal file
View File

@ -0,0 +1,3 @@
This project is dual-licensed under the WTFPL and 0BSD licenses, copies of both are included.
You may use this code under the terms of either license.

1505
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

18
Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "bin"
version = "0.1.0"
authors = ["Jordan Doyle <jordan@doyle.la>"]
edition = "2018"
[dependencies]
rocket = "0.4"
lazy_static = "1.2"
gpw = "0.1"
syntect = "3.0"
chashmap = { git = "https://github.com/redox-os/tfs" }
serde = { version = "1.0", features = ["derive"] }
[dependencies.rocket_contrib]
version = "*"
default-features = false
features = ["tera_templates"]

1
LICENSE Normal file
View File

@ -0,0 +1 @@
WTFPL OR 0BSD

5
LICENSE.0BSD Normal file
View File

@ -0,0 +1,5 @@
Copyright (C) 2019 by Jordan Doyle <jordan@doyle.la>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

13
LICENSE.WTFPL Normal file
View File

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

110
src/main.rs Normal file
View File

@ -0,0 +1,110 @@
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use] extern crate lazy_static;
#[macro_use] extern crate rocket;
extern crate rocket_contrib;
extern crate gpw;
extern crate syntect;
extern crate chashmap;
use rocket_contrib::templates::Template;
use rocket::response::Redirect;
use rocket::request::Form;
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 chashmap::CHashMap;
use std::borrow::Cow;
use std::cell::RefCell;
lazy_static! {
static ref ENTRIES: CHashMap<String, String> = CHashMap::new();
}
#[derive(FromForm)]
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);
Redirect::to(format!("/{}", id))
}
#[put("/", data = "<input>")]
fn submit_raw(input: String) -> String {
format!("https://{}/{}", "localhost:8000", store_paste(input))
}
/// 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(&regions[..], IncludeBackground::No))
}
#[get("/<key>")]
fn render<'a>(key: String) -> Option<Template> {
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)?;
#[derive(Serialize)]
struct Render<'a> {
content: Cow<'a, String>
}
Some(Template::render("paste", Render {
content: match ext {
None => Cow::Borrowed(&*entry),
Some(extension) => Cow::Owned(highlight(&*entry, extension)?)
}
}))
}
fn main() {
rocket::ignite()
.attach(Template::fairing())
.mount("/", routes![index, submit, submit_raw, render])
.launch();
}

View File

@ -0,0 +1,29 @@
<!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>

View File

@ -0,0 +1,59 @@
{% 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="&#x2318;+&#x23ce;">&#x270e;</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 %}

View File

@ -0,0 +1,16 @@
{% 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 %}