//! This file defines the binary for cheddar, which can be interacted //! with in two different ways: //! //! 1. As a CLI tool that acts as a cgit filter. //! 2. As a long-running HTTP server that handles rendering requests //! (matching the SourceGraph protocol). use clap::{App, Arg}; use rouille::{router, try_or_400, Response}; use serde::Deserialize; use serde_json::json; use std::collections::HashMap; use std::io; use cheddar::{format_code, format_markdown, THEMES}; // Server endpoint for rendering the syntax of source code. This // replaces the 'syntect_server' component of Sourcegraph. fn code_endpoint(request: &rouille::Request) -> rouille::Response { #[derive(Deserialize)] struct SourcegraphQuery { filepath: String, theme: String, code: String, } let query: SourcegraphQuery = try_or_400!(rouille::input::json_input(request)); let mut buf: Vec<u8> = Vec::new(); // We don't use syntect with the sourcegraph themes bundled // currently, so let's fall back to something that is kind of // similar (tm). let theme = &THEMES.themes[match query.theme.as_str() { "Sourcegraph (light)" => "Solarized (light)", _ => "Solarized (dark)", }]; format_code(theme, &mut query.code.as_bytes(), &mut buf, &query.filepath); Response::json(&json!({ "is_plaintext": false, "data": String::from_utf8_lossy(&buf) })) } // Server endpoint for rendering a Markdown file. fn markdown_endpoint(request: &rouille::Request) -> rouille::Response { let mut texts: HashMap<String, String> = try_or_400!(rouille::input::json_input(request)); for text in texts.values_mut() { let mut buf: Vec<u8> = Vec::new(); format_markdown(&mut text.as_bytes(), &mut buf, true); *text = String::from_utf8_lossy(&buf).to_string(); } Response::json(&texts) } fn highlighting_server(listen: &str) { println!("Starting syntax highlighting server on '{}'", listen); rouille::start_server(listen, move |request| { router!(request, // Markdown rendering route (POST) (/markdown) => { markdown_endpoint(request) }, // Code rendering route (POST) (/) => { code_endpoint(request) }, _ => { rouille::Response::empty_404() }, ) }); } fn main() { // Parse the command-line flags passed to cheddar to determine // whether it is running in about-filter mode (`--about-filter`) // and what file extension has been supplied. let matches = App::new("cheddar") .about("TVL's syntax highlighter") .arg( Arg::with_name("about-filter") .help("Run as a cgit about-filter (renders Markdown)") .long("about-filter") .takes_value(false), ) .arg( Arg::with_name("no-tagfilter") .help("Disable HTML tag filter") .long("no-tagfilter") .takes_value(false), ) .arg( Arg::with_name("sourcegraph-server") .help("Run as a Sourcegraph compatible web-server") .long("sourcegraph-server") .takes_value(false), ) .arg( Arg::with_name("listen") .help("Address to listen on") .long("listen") .takes_value(true), ) .arg(Arg::with_name("filename").help("File to render").index(1)) .get_matches(); if matches.is_present("sourcegraph-server") { highlighting_server( matches .value_of("listen") .expect("Listening address is required for server mode"), ); return; } let filename = matches.value_of("filename").expect("filename is required"); let stdin = io::stdin(); let mut in_handle = stdin.lock(); let stdout = io::stdout(); let mut out_handle = stdout.lock(); if matches.is_present("about-filter") && filename.ends_with(".md") { format_markdown( &mut in_handle, &mut out_handle, !matches.is_present("no-tagfilter"), ); } else { format_code( &THEMES.themes["InspiredGitHub"], &mut in_handle, &mut out_handle, filename, ); } }