Skip to content

Commit 9b3ee6d

Browse files
authored
Merge pull request #51 from tinaun/playground
?eval and ?play commands
2 parents 00f8d18 + 2da9828 commit 9b3ee6d

File tree

2 files changed

+311
-0
lines changed

2 files changed

+311
-0
lines changed

src/main.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod ban;
1212
mod commands;
1313
mod crates;
1414
mod db;
15+
mod playground;
1516
mod schema;
1617
mod state_machine;
1718
mod tags;
@@ -30,6 +31,7 @@ use std::collections::HashMap;
3031
struct Config {
3132
tags: bool,
3233
crates: bool,
34+
eval: bool,
3335
discord_token: String,
3436
mod_id: String,
3537
talk_id: String,
@@ -108,6 +110,37 @@ fn app() -> Result<()> {
108110
cmds.help("?docs", "Lookup documentation", crates::doc_help);
109111
}
110112

113+
if config.eval {
114+
// rust playground
115+
cmds.add(
116+
"?play mode={} edition={} channel={} ```\ncode```",
117+
playground::run,
118+
);
119+
cmds.add("?play code...", playground::err);
120+
cmds.help(
121+
"?play",
122+
"Compile and run rust code in a playground",
123+
|args| playground::help(args, "play"),
124+
);
125+
126+
cmds.add(
127+
"?eval mode={} edition={} channel={} ```\ncode```",
128+
playground::eval,
129+
);
130+
cmds.add(
131+
"?eval mode={} edition={} channel={} ```code```",
132+
playground::eval,
133+
);
134+
cmds.add(
135+
"?eval mode={} edition={} channel={} `code`",
136+
playground::eval,
137+
);
138+
cmds.add("?eval code...", playground::eval_err);
139+
cmds.help("?eval", "Evaluate a single rust expression", |args| {
140+
playground::help(args, "eval")
141+
});
142+
}
143+
111144
// Slow mode.
112145
// 0 seconds disables slowmode
113146
cmds.add_protected("?slowmode {channel} {seconds}", api::slow_mode, api::is_mod);

src/playground.rs

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
//! run rust code on the rust-lang playground
2+
3+
use crate::{
4+
api,
5+
commands::{Args, Result},
6+
};
7+
8+
use reqwest::header;
9+
use serde::{Deserialize, Serialize};
10+
use std::collections::HashMap;
11+
use std::str::FromStr;
12+
13+
#[derive(Debug, Serialize)]
14+
struct PlaygroundCode<'a> {
15+
channel: Channel,
16+
edition: Edition,
17+
code: &'a str,
18+
#[serde(rename = "crateType")]
19+
crate_type: CrateType,
20+
mode: Mode,
21+
tests: bool,
22+
}
23+
24+
impl<'a> PlaygroundCode<'a> {
25+
fn new(code: &'a str) -> Self {
26+
PlaygroundCode {
27+
channel: Channel::Nightly,
28+
edition: Edition::E2018,
29+
code,
30+
crate_type: CrateType::Binary,
31+
mode: Mode::Debug,
32+
tests: false,
33+
}
34+
}
35+
36+
fn url_from_gist(&self, gist: &str) -> String {
37+
let version = match self.channel {
38+
Channel::Nightly => "nightly",
39+
Channel::Beta => "beta",
40+
Channel::Stable => "stable",
41+
};
42+
43+
let edition = match self.edition {
44+
Edition::E2015 => "2015",
45+
Edition::E2018 => "2018",
46+
};
47+
48+
let mode = match self.mode {
49+
Mode::Debug => "debug",
50+
Mode::Release => "release",
51+
};
52+
53+
format!(
54+
"https://play.rust-lang.org/?version={}&mode={}&edition={}&gist={}",
55+
version, mode, edition, gist
56+
)
57+
}
58+
}
59+
60+
#[derive(Debug, Serialize)]
61+
#[serde(rename_all = "snake_case")]
62+
enum Channel {
63+
Stable,
64+
Beta,
65+
Nightly,
66+
}
67+
68+
impl FromStr for Channel {
69+
type Err = Box<dyn std::error::Error>;
70+
71+
fn from_str(s: &str) -> Result<Self> {
72+
match s {
73+
"stable" => Ok(Channel::Stable),
74+
"beta" => Ok(Channel::Beta),
75+
"nightly" => Ok(Channel::Nightly),
76+
_ => Err(format!("invalid release channel `{}`", s).into()),
77+
}
78+
}
79+
}
80+
81+
#[derive(Debug, Serialize)]
82+
enum Edition {
83+
#[serde(rename = "2015")]
84+
E2015,
85+
#[serde(rename = "2018")]
86+
E2018,
87+
}
88+
89+
impl FromStr for Edition {
90+
type Err = Box<dyn std::error::Error>;
91+
92+
fn from_str(s: &str) -> Result<Self> {
93+
match s {
94+
"2015" => Ok(Edition::E2015),
95+
"2018" => Ok(Edition::E2018),
96+
_ => Err(format!("invalid edition `{}`", s).into()),
97+
}
98+
}
99+
}
100+
101+
#[derive(Debug, Serialize)]
102+
enum CrateType {
103+
#[serde(rename = "bin")]
104+
Binary,
105+
#[serde(rename = "lib")]
106+
Library,
107+
}
108+
109+
#[derive(Debug, Serialize)]
110+
#[serde(rename_all = "snake_case")]
111+
enum Mode {
112+
Debug,
113+
Release,
114+
}
115+
116+
impl FromStr for Mode {
117+
type Err = Box<dyn std::error::Error>;
118+
119+
fn from_str(s: &str) -> Result<Self> {
120+
match s {
121+
"debug" => Ok(Mode::Debug),
122+
"release" => Ok(Mode::Release),
123+
_ => Err(format!("invalid compilation mode `{}`", s).into()),
124+
}
125+
}
126+
}
127+
128+
#[derive(Debug, Deserialize)]
129+
struct PlayResult {
130+
success: bool,
131+
stdout: String,
132+
stderr: String,
133+
}
134+
135+
fn run_code(args: &Args, code: &str) -> Result<String> {
136+
let mut errors = String::new();
137+
138+
let channel = args.params.get("channel").unwrap_or_else(|| &"nightly");
139+
let mode = args.params.get("mode").unwrap_or_else(|| &"debug");
140+
let edition = args.params.get("edition").unwrap_or_else(|| &"2018");
141+
142+
let mut request = PlaygroundCode::new(code);
143+
144+
match Channel::from_str(channel) {
145+
Ok(c) => request.channel = c,
146+
Err(e) => errors += &format!("{}\n", e),
147+
}
148+
149+
match Mode::from_str(mode) {
150+
Ok(m) => request.mode = m,
151+
Err(e) => errors += &format!("{}\n", e),
152+
}
153+
154+
match Edition::from_str(edition) {
155+
Ok(e) => request.edition = e,
156+
Err(e) => errors += &format!("{}\n", e),
157+
}
158+
159+
if !code.contains("fn main") {
160+
request.crate_type = CrateType::Library;
161+
}
162+
163+
let resp = args
164+
.http
165+
.post("https://play.rust-lang.org/execute")
166+
.json(&request)
167+
.send()?;
168+
169+
let result: PlayResult = resp.json()?;
170+
171+
let result = if result.success {
172+
result.stdout
173+
} else {
174+
result.stderr
175+
};
176+
177+
Ok(if result.len() + errors.len() > 1994 {
178+
format!(
179+
"{}Output too large. Playground link: {}",
180+
errors,
181+
get_playground_link(args, code, &request)?
182+
)
183+
} else if result.len() == 0 {
184+
format!("{}compilation succeded.", errors)
185+
} else {
186+
format!("{}```{}```", errors, result)
187+
})
188+
}
189+
190+
fn get_playground_link(args: &Args, code: &str, request: &PlaygroundCode) -> Result<String> {
191+
let mut payload = HashMap::new();
192+
payload.insert("code", code);
193+
194+
let resp = args
195+
.http
196+
.post("https://play.rust-lang.org/meta/gist/")
197+
.header(header::REFERER, "https://discord.gg/rust-lang")
198+
.json(&payload)
199+
.send()?;
200+
201+
let resp: HashMap<String, String> = resp.json()?;
202+
info!("gist response: {:?}", resp);
203+
204+
resp.get("id")
205+
.map(|id| request.url_from_gist(id))
206+
.ok_or_else(|| "no gist found".into())
207+
}
208+
209+
pub fn run(args: Args) -> Result<()> {
210+
let code = args
211+
.params
212+
.get("code")
213+
.ok_or("Unable to retrieve param: query")?;
214+
215+
let result = run_code(&args, code)?;
216+
api::send_reply(&args, &result)?;
217+
Ok(())
218+
}
219+
220+
pub fn help(args: Args, name: &str) -> Result<()> {
221+
let message = format!(
222+
"Compile and run rust code. All code is executed on https://play.rust-lang.org.
223+
```?{} mode={{}} channel={{}} edition={{}} ``\u{200B}`code``\u{200B}` ```
224+
Optional arguments:
225+
\tmode: debug, release (default: debug)
226+
\tchannel: stable, beta, nightly (default: nightly)
227+
\tedition: 2015, 2018 (default: 2018)
228+
",
229+
name
230+
);
231+
232+
api::send_reply(&args, &message)?;
233+
Ok(())
234+
}
235+
236+
pub fn err(args: Args) -> Result<()> {
237+
let message = "Missing code block. Please use the following markdown:
238+
\\`\\`\\`rust
239+
code here
240+
\\`\\`\\`
241+
";
242+
243+
api::send_reply(&args, message)?;
244+
Ok(())
245+
}
246+
247+
pub fn eval(args: Args) -> Result<()> {
248+
let code = args
249+
.params
250+
.get("code")
251+
.ok_or("Unable to retrieve param: query")?;
252+
253+
let code = format!(
254+
"fn main(){{
255+
println!(\"{{:?}}\",{{
256+
{}
257+
}});
258+
}}",
259+
code
260+
);
261+
262+
let result = run_code(&args, &code)?;
263+
api::send_reply(&args, &result)?;
264+
Ok(())
265+
}
266+
267+
pub fn eval_err(args: Args) -> Result<()> {
268+
let message = "Missing code block. Please use the following markdown:
269+
\\`code here\\`
270+
or
271+
\\`\\`\\`rust
272+
code here
273+
\\`\\`\\`
274+
";
275+
276+
api::send_reply(&args, message)?;
277+
Ok(())
278+
}

0 commit comments

Comments
 (0)