Junfeng Liu
2017-06-02
Rust is a systems programming language focused on three goals: safety, speed, and concurrency.
Rust combines low-level control over performance with high-level convenience and safety guarantees.
Rust is a language for confident, productive systems programming.
let x = 0;
let mut y = 1;
fn main() {
println!("Hello, World!");
}
rustup install nightly
rustup default nightly
rustup update nightly
rustup self update
rustup target list
rustup target add x86_64-unknown-linux-musl
rustup component add rls
cargo new <project> --bin
cargo run
cargo run --example json
cargo build
cargo build --release --target x86_64-unknown-linux-musl
cargo update
cargo doc --open
cargo test
cargo bench
cargo login
cargo publish
Parse command line arguments with clap:
extern crate lopdf;
use lopdf::Document;
#[macro_use]
extern crate clap;
use clap::{App, Arg, SubCommand};
use std::str::FromStr;
fn main() {
let app = App::new("PDF utility program using lopdf library")
.version(crate_version!())
.author(crate_authors!())
.arg(Arg::with_name("input")
.short("i")
.long("input")
.value_name("input file")
.takes_value(true)
.global(true))
.arg(Arg::with_name("output")
.short("o")
.long("output")
.value_name("output file")
.takes_value(true)
.global(true))
.subcommand(SubCommand::with_name("process")
.about("Process PDF document with specified operations")
.arg(Arg::with_name("operations")
.value_name("operations")
.help("e.g. prune_objects delete_zero_length_streams renumber_objects")
.takes_value(true)
.multiple(true)))
.subcommand(SubCommand::with_name("compress")
.about("Compress PDF document"))
.subcommand(SubCommand::with_name("decompress")
.about("Decompress PDF document"))
.subcommand(SubCommand::with_name("delete_pages")
.about("Delete pages")
.arg(Arg::with_name("pages")
.value_name("page numbers")
.help("e.g. 3,5,7-9")
.takes_value(true)))
.subcommand(SubCommand::with_name("prune_objects")
.about("Prune unused objects"))
.subcommand(SubCommand::with_name("delete_objects")
.about("Delete objects")
.arg(Arg::with_name("ids")
.value_name("object ids")
.help("e.g. \"1 0,2 1,35,36\"")
.takes_value(true)))
.subcommand(SubCommand::with_name("renumber_objects")
.about("Renumber objects"))
.subcommand(SubCommand::with_name("delete_zero_length_streams")
.about("Delete zero length stream objects"))
.get_matches();
if let (cmd, Some(args)) = app.subcommand() {
if let Some(input) = args.value_of("input") {
println!("Open {}", input);
let mut doc = Document::load(input).unwrap();
println!("Do {}", cmd);
match cmd {
"process" => {
if let Some(operations) = args.values_of("operations") {
for operation in operations {
println!("Do {}", operation);
apply_operation(&mut doc, operation);
}
}
}
"delete_pages" => {
if let Some(pages) = args.value_of("pages") {
let mut page_numbers = vec![];
for page in pages.split(',') {
let nums: Vec<u32> = page.split('-').map(|num|u32::from_str(num).unwrap()).collect();
match nums.len() {
1 => page_numbers.push(nums[0]),
2 => page_numbers.append(&mut (nums[0]..nums[1]+1).collect()),
_ => {}
}
}
doc.delete_pages(&page_numbers);
}
}
"delete_objects" => {
if let Some(ids) = args.value_of("ids") {
for id in ids.split(',') {
let nums: Vec<u32> = id.split(' ').map(|num|u32::from_str(num).unwrap()).collect();
match nums.len() {
1 => doc.delete_object(&(nums[0], 0)),
2 => doc.delete_object(&(nums[0], nums[1] as u16)),
_ => None
};
}
}
}
operation @ _ => {
apply_operation(&mut doc, operation);
}
}
doc.change_producer("https://crates.io/crates/lopdf");
if let Some(output) = args.value_of("output") {
println!("Save to {}", output);
doc.save(output).unwrap();
}
}
}
fn apply_operation(doc: &mut Document, operation: &str) {
match operation {
"compress" => doc.compress(),
"decompress" => doc.decompress(),
"renumber_objects" => doc.renumber_objects(),
"prune_objects" => {
let ids = doc.prune_objects();
println!("Deleted {:?}", ids);
}
"delete_zero_length_streams" => {
let streams = doc.delete_zero_length_streams();
if streams.len() > 0 {
println!("Deleted {:?}", streams);
}
}
_ => {}
}
}
}
> ./pdfutil help
PDF utility program using lopdf library 0.1.0
Junfeng Liu <china.liujunfeng@gmail.com>
USAGE:
pdfutil [OPTIONS] [SUBCOMMAND]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-i, --input <input file>
-o, --output <output file>
SUBCOMMANDS:
compress Compress PDF document
decompress Decompress PDF document
delete_objects Delete objects
delete_pages Delete pages
delete_zero_length_streams Delete zero length stream objects
help Prints this message or the help of the given subcommand(s)
process Process PDF document with specified operations
prune_objects Prune unused objects
renumber_objects Renumber objects
Output
> ./pdfutil help delete_pages
pdfutil-delete_pages
Delete pages
USAGE:
pdfutil delete_pages [OPTIONS] [page numbers]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-i, --input <input file>
-o, --output <output file>
ARGS:
<page numbers> e.g. 3,5,7-9
Output
Parse TOML into your own structs using Serde:
extern crate serde;
// extern crate serde_json;
extern crate toml;
use std::collections::BTreeMap;
use std::fs::File;
use std::env;
use std::io::{Result, Read};
#[derive(Serialize, Deserialize, Debug)]
pub struct Authorization {
pub origins: Vec<String>
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ApiInfo {
pub provider: String,
pub url: String,
pub params: Vec<String>,
pub format: Option<String>
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ApiCollection {
pub authorization: Authorization,
pub api: BTreeMap<String, Vec<ApiInfo>>
}
pub fn load_config() -> Result<ApiCollection> {
let config_file = env::current_dir()?.join("config/apis.toml");
println!("Load {}", config_file.display());
let mut input = String::new();
let mut file = File::open(&config_file)?;
file.read_to_string(&mut input)?;
// let deserialized: ApiCollection = serde_json::from_str(&input).unwrap();
let deserialized: ApiCollection = toml::from_str(&input).unwrap();
// println!(" -> {:?}", deserialized);
return Ok(deserialized);
}
apis.toml
[authorization]
origins = ["http://192.168.2.204:3000", "http://www.yunluwang.com"]
[[api.GetIpInfo]]
provider = "taobao"
url = "http://ip.taobao.com/service/getIpInfo.php{?ip}"
params = ["ip"]
format = "json"
[[api.GetIpInfo]]
provider = "sina"
url = "http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json{&ip}"
params = ["ip"]
format = "json"
[[api.GetIpInfo]]
provider = "pconline"
url = "http://whois.pconline.com.cn/ipJson.jsp?json=true{&ip}"
params = ["ip"]
format = "json"
Pastebin application in Rocket:
#![feature(plugin)]
#![plugin(rocket_codegen)]
extern crate rocket;
extern crate rand;
mod paste_id;
use std::io;
use std::fs::File;
use std::path::Path;
use rocket::Data;
use rocket::response::content;
use paste_id::PasteID;
const HOST: &'static str = "http://localhost:8000";
const ID_LENGTH: usize = 3;
#[post("/", data = "<paste>")]
fn upload(paste: Data) -> io::Result<String> {
let id = PasteID::new(ID_LENGTH);
let filename = format!("upload/{id}", id = id);
let url = format!("{host}/{id}\n", host = HOST, id = id);
paste.stream_to_file(Path::new(&filename))?;
Ok(url)
}
#[get("/<id>")]
fn retrieve(id: PasteID) -> Option<content::Plain<File>> {
let filename = format!("upload/{id}", id = id);
File::open(&filename).map(|f| content::Plain(f)).ok()
}
#[get("/")]
fn index() -> &'static str {
"
USAGE
POST /
accepts raw data in the body of the request and responds with a URL of
a page containing the body's content
EXMAPLE: curl --data-binary @file.txt http://localhost:8000
GET /<id>
retrieves the content for the paste with id `<id>`
"
}
fn main() {
rocket::ignite().mount("/", routes![index, upload, retrieve]).launch()
}
paste_id.rs
use std::fmt;
use std::borrow::Cow;
use rocket::request::FromParam;
use rand::{self, Rng};
/// Table to retrieve base62 values from.
const BASE62: &'static [u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
/// A _probably_ unique paste ID.
pub struct PasteID<'a>(Cow<'a, str>);
impl<'a> PasteID<'a> {
/// Generate a _probably_ unique ID with `size` characters. For readability,
/// the characters used are from the sets [0-9], [A-Z], [a-z]. The
/// probability of a collision depends on the value of `size`. In
/// particular, the probability of a collision is 1/62^(size).
pub fn new(size: usize) -> PasteID<'static> {
let mut id = String::with_capacity(size);
let mut rng = rand::thread_rng();
for _ in 0..size {
id.push(BASE62[rng.gen::<usize>() % 62] as char);
}
PasteID(Cow::Owned(id))
}
}
impl<'a> fmt::Display for PasteID<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
/// Returns `true` if `id` is a valid paste ID and `false` otherwise.
fn valid_id(id: &str) -> bool {
id.chars().all(|c| {
(c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9')
})
}
/// Returns an instance of `PasteID` if the path segment is a valid ID.
/// Otherwise returns the invalid ID as the `Err` value.
impl<'a> FromParam<'a> for PasteID<'a> {
type Error = &'a str;
fn from_param(param: &'a str) -> Result<PasteID<'a>, &'a str> {
match valid_id(param) {
true => Ok(PasteID(Cow::Borrowed(param))),
false => Err(param)
}
}
}
Read two books: