Browse Source

First commit

Basic Markdown import tool

Probably a lot of things could be improved, but it works!
master
Baptiste Gelez 2 years ago
commit
e82b8b77e3
8 changed files with 1667 additions and 0 deletions
  1. +5
    -0
      .env
  2. +2
    -0
      .gitignore
  3. +1447
    -0
      Cargo.lock
  4. +13
    -0
      Cargo.toml
  5. +1
    -0
      rust-toolchain
  6. +176
    -0
      src/main.rs
  7. +11
    -0
      tests/md/article.md
  8. +12
    -0
      tests/md/canapi.md

+ 5
- 0
.env View File

@@ -0,0 +1,5 @@
PLUME_API_URL=localhost:2020

PLUME_CLIENT_ID=b62af083cd34da7ad36fbf1f2133b5c74ad16e05da9fcabdcb8b6672e63
PLUME_CLIENT_SECRET=b22c28a4e6cf9a9eb9c4749aefaf4b26afa38ac7c6f6dd42aa324e4cce224e
PLUME_API_TOKEN=c7e38c11bc1543637f9e85a123513b4c22e3029c74cb6b6412aa3979d22d11a

+ 2
- 0
.gitignore View File

@@ -0,0 +1,2 @@
/target
**/*.rs.bk

+ 1447
- 0
Cargo.lock
File diff suppressed because it is too large
View File


+ 13
- 0
Cargo.toml View File

@@ -0,0 +1,13 @@
[package]
name = "amsterdam"
version = "0.1.0"
authors = ["Bat' <baptiste@gelez.xyz>"]

[dependencies]
canapi = "0.2.0"
clap = "2.0"
dotenv = "0.13"
plume-api = { git = "https://github.com/Plume-org/Plume", rev = "88a6a81ad8ce8cdb18b9ae3467961cf9490e4411" }
reqwest = "0.9.3"
rpassword = "2.0.0"
serde_json = "1.0"

+ 1
- 0
rust-toolchain View File

@@ -0,0 +1 @@
nightly-2018-07-17

+ 176
- 0
src/main.rs View File

@@ -0,0 +1,176 @@
extern crate canapi;
extern crate clap;
extern crate dotenv;
extern crate plume_api;
extern crate serde_json;
extern crate reqwest;
extern crate rpassword;

use canapi::*;
use clap::{App, Arg, ArgMatches, SubCommand};
use dotenv::dotenv;
use plume_api::{apps::AppEndpoint, posts::PostEndpoint};
use reqwest::{ClientBuilder, Method};
use std::{io::{self, prelude::*, BufRead, BufReader}, env, fs::{metadata, File, OpenOptions}, str::FromStr, fmt::Display};

fn main() {
dotenv().ok();
let mut app = App::new("Amsterdam")
.bin_name("amsterdam")
.version(env!("CARGO_PKG_VERSION"))
.about("Import articles to Plume.")
.subcommand(SubCommand::with_name("md")
.arg(Arg::with_name("FILES")
.takes_value(true)
.multiple(true)
.help("The Markdown files to import"))
.about("Import Markown files")
);

if env::var("PLUME_API_TOKEN").is_err() {
get_token();
}

match app.clone().get_matches().subcommand() {
("md", Some(args)) => md(args),
_ => app.print_help().unwrap(),
}
}

fn write_to_env<A: Display, B: Display>(var: A, val: B) {
let mut file = OpenOptions::new()
.write(true)
.append(true)
.open(".env")
.unwrap();

writeln!(file, "{}={}", var, val).expect("Couldn't write to file");
dotenv().ok();
}

fn make_client() -> (String, String) {
println!("Please enter your instance domain (without https:// or http://)");
let mut url = String::new();
io::stdin().read_line(&mut url).ok();
write_to_env("PLUME_API_URL", url);

let client = AppEndpoint::default().create::<ReqwestFetch>(AppEndpoint {
name: String::from("Amsterdam"),
..AppEndpoint::default()
});
if let Ok(client) = client {
write_to_env("PLUME_CLIENT_ID", client.client_id.clone().unwrap());
write_to_env("PLUME_CLIENT_SECRET", client.client_secret.clone().unwrap());
(client.client_id.unwrap(), client.client_secret.unwrap())
} else {
panic!("client is err");
}
}

fn get_token() {
let (c_id, c_secret) = env::var("PLUME_CLIENT_ID")
.and_then(|id| env::var("PLUME_CLIENT_SECRET").map(|sec| (id, sec)))
.unwrap_or_else(|_| make_client());
println!("What is your username?");
let mut name = String::new();
io::stdin().read_line(&mut name).ok();

println!("What is your password?");
let password = rpassword::read_password().unwrap();

let url = format!(
"https://{}/api/v1/oauth2?username={}&password={}&client_id={}&client_secret={}&scopes=write",
env::var("PLUME_API_URL").unwrap(),
name,
password,
c_id,
c_secret,
);

let json: serde_json::Value = ClientBuilder::new().danger_accept_invalid_certs(true).build().unwrap()
.get(url.as_str()).send().and_then(|mut r| r.text())
.map(|t| serde_json::from_str(t.as_str()).unwrap())
.unwrap();
write_to_env("PLUME_API_TOKEN", json["token"].as_str().unwrap())
}

enum ParseState {
Ready,
FrontMatter(PostEndpoint),
Body(PostEndpoint),
}

fn md<'a>(args: &'a ArgMatches) {
for path in args.values_of("FILES").unwrap() {
if metadata(path).expect("File not found").is_file() {
println!("Importing {}", path);

let f = File::open(path).expect("File not found");
let file = BufReader::new(f);
let parse_result = file.lines().fold(ParseState::Ready, |state, line| {
let line = line.expect("Couldn't read line");
match state {
ParseState::Ready => if line.chars().all(|c| c == '-') {
ParseState::FrontMatter(PostEndpoint::default())
} else {
panic!("Your articles should use FrontMatter metadata")
},
ParseState::FrontMatter(mut post) => {
let mut parsed = line.splitn(2, ':');
let mut field = parsed.next().expect("No metadata field");
if let Some(value) = parsed.next() {
let value = value.trim();
match field {
"title" => post.title = Some(value.to_string()),
"subtitle" => post.subtitle = Some(value.to_string()),
"tags" => post.tags = Some(value.split(",").map(str::trim).map(String::from).collect()),
"date" => post.creation_date = Some(value.to_string()),
x => println!("The {} field will be ignored.", x),
};
ParseState::FrontMatter(post)
} else {
ParseState::Body(post)
}
},
ParseState::Body(mut post) => {
if let Some(source) = post.source {
post.source = Some(source + "\n" + line.as_ref());
} else {
post.source = Some(line);
}
ParseState::Body(post)
}
}
});

if let ParseState::Body(article) = parse_result {
println!("Publishing {}...", article.title.clone().unwrap());
PostEndpoint::default().create::<ReqwestFetch>(article).ok();
} else {
panic!("Error while parsing article metadata");
}
}
}
}

struct ReqwestFetch;

impl Fetch for ReqwestFetch {
fn fetch<T: Endpoint>(method: &'static str, url: String, data: Option<T>) -> Result<T, Error> {
ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build()
.unwrap()
.request(
Method::from_str(method).expect("Canapi: invalid method"),
format!("https://{}{}", env::var("PLUME_API_URL").unwrap(), url).as_str()
)
.body(serde_json::to_string(&data).expect("Error while serializing post"))
.header("Content-Type", "application/json")
.bearer_auth(env::var("PLUME_API_TOKEN").unwrap_or(String::new()))
.send()
.and_then(|mut r| r.text())
.map_err(|e| { println!("err: {}", e.to_string()); Error::Fetch(e.to_string()) })
.and_then(|t| { println!("got: {}", t); serde_json::from_str(t.as_ref()).map_err(|e| Error::SerDe(e.to_string())) })
}
}

+ 11
- 0
tests/md/article.md View File

@@ -0,0 +1,11 @@
---
title: My article
subtitle: This is my article
tags: foo, bar, baz
---

# Hello, world!

That's an article.

Bye.

+ 12
- 0
tests/md/canapi.md View File

@@ -0,0 +1,12 @@
---
title: Imported from Amsterdam! 3
tags: yay,plume, amsterdam,canapi
author: whatever
date: 2015-06-03
---

With an old date

This post have been imported with Amsterdam!

**YAY**

Loading…
Cancel
Save