-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwormhole.rs
143 lines (136 loc) · 5.01 KB
/
wormhole.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use std::convert::Infallible;
use std::path::PathBuf;
use std::thread;
use crate::endpoints;
use crate::project_path::ProjectPath;
use crate::projects;
use crate::projects::Mutation;
use crate::ps;
use hyper::{header, Body, Request, Response, StatusCode};
use url::form_urlencoded;
#[derive(Debug)]
pub enum Application {
Editor,
Terminal,
Other,
}
#[derive(Debug)]
pub enum WindowAction {
Focus,
Raise,
}
#[derive(Debug)]
pub struct QueryParams {
pub land_in: Option<Application>,
pub line: Option<usize>,
pub names: Vec<String>,
}
pub async fn service(req: Request<Body>) -> Result<Response<Body>, Infallible> {
let uri = req.uri();
let path = uri.path().to_string();
if &path == "/favicon.ico" {
return Ok(Response::new(Body::from("")));
}
let params = QueryParams::from_query(uri.query());
if &path != "/list-projects/" {
ps!("\nRequest: {} {:?}", uri, params);
}
if &path == "/list-projects/" {
Ok(endpoints::list_projects())
} else if let Some(path) = path.strip_prefix("/add-project/") {
// An absolute path must have a double slash: /add-project//Users/me/file.rs
Ok(endpoints::add_project(&path.trim(), params.names))
} else if let Some(name) = path.strip_prefix("/remove-project/") {
Ok(endpoints::remove_project(&name.trim()))
} else {
// wormhole uses the `hs` client to make a call to the hammerspoon
// service. But one might also want to use hammerspoon to configure a
// key binding to make a call to the wormhole service. In practice I
// found that hammerspoon did not support this concurrency: it was
// unable to handle the `hs` call from wormhole when it was still
// waiting for its originating HTTP request to return. Instead the `hs`
// call blocked until the HTTP request timed out. So, wormhole returns
// immediately, performing its actions asynchronously.
if let Some((Some(project_path), mutation, land_in)) =
determine_requested_operation(&path, params.line, Some(Application::Terminal))
{
thread::spawn(move || project_path.open(mutation, land_in));
Ok(Response::new(Body::from("Sent into wormhole.")))
} else {
let redirect_to = format!(
"https://github.com{path}#L{}?wormhole=false",
params.line.unwrap_or(1)
);
ps!("Redirecting to: {}", redirect_to);
let response = Response::builder()
.status(StatusCode::FOUND)
.header(header::LOCATION, redirect_to)
.body(Body::empty())
.unwrap();
return Ok(response);
}
}
}
fn determine_requested_operation(
url_path: &str,
line: Option<usize>,
land_in: Option<Application>,
) -> Option<(Option<ProjectPath>, Mutation, Option<Application>)> {
let projects = projects::lock();
if url_path == "/previous-project/" {
let p = projects.previous().map(|p| p.as_project_path());
Some((p, Mutation::RotateRight, land_in))
} else if url_path == "/next-project/" {
let p = projects.next().map(|p| p.as_project_path());
Some((p, Mutation::RotateLeft, land_in))
} else if let Some(name) = url_path.strip_prefix("/project/") {
let p = projects.by_name(name).map(|p| p.as_project_path());
Some((p, Mutation::Insert, land_in))
} else if let Some(absolute_path) = url_path.strip_prefix("/file/") {
let p = ProjectPath::from_absolute_path(&PathBuf::from(absolute_path), &projects);
Some((p, Mutation::Insert, land_in))
} else if let Some(project_path) = ProjectPath::from_github_url(&url_path, line, &projects) {
if url_path.ends_with(".md") {
None
} else {
Some((
Some(project_path),
Mutation::Insert,
Some(Application::Editor),
))
}
} else {
None
}
}
impl QueryParams {
pub fn from_query(query: Option<&str>) -> Self {
let mut params = QueryParams {
land_in: None,
line: None,
names: vec![],
};
if let Some(query) = query {
for (key, val) in
form_urlencoded::parse(query.to_lowercase().as_bytes()).collect::<Vec<(_, _)>>()
{
if key == "land-in" {
if val == "terminal" {
params.land_in = Some(Application::Terminal);
} else if val == "editor" {
params.land_in = Some(Application::Editor);
}
} else if key == "line" {
params.line = val.parse::<usize>().ok();
} else if key == "name" {
params.names = val
.to_string()
.split(",")
.map(|s| s.trim().to_string())
.collect();
}
}
}
params
}
}