delete old planetwars-cli code
This commit is contained in:
parent
31f8271db6
commit
c6293d8e32
16 changed files with 0 additions and 658 deletions
|
@ -1,25 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "planetwars-cli"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
[[bin]]
|
|
||||||
name = "pwcli"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
futures-core = "0.3"
|
|
||||||
futures = "0.3"
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
|
||||||
rand = "0.6"
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
serde_json = "1.0"
|
|
||||||
toml = "0.5"
|
|
||||||
clap = { version = "3.0.0-rc.8", features = ["derive"] }
|
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
|
||||||
shlex = "1.1"
|
|
||||||
planetwars-matchrunner = { path = "../planetwars-matchrunner" }
|
|
||||||
|
|
||||||
rust-embed = "6.3.0"
|
|
||||||
axum = { version = "0.4", features = ["ws"] }
|
|
||||||
mime_guess = "2"
|
|
|
@ -1,57 +0,0 @@
|
||||||
# planetwars-cli
|
|
||||||
ATTENTION: this package is currently out-of-date.
|
|
||||||
|
|
||||||
Note: this project is under active development. All file and configuration formats will take some time to stabilize, so be prepared for breakage when you upgrade to a new version.
|
|
||||||
## Building
|
|
||||||
|
|
||||||
The cli comes with a local webserver for visualizing matches.
|
|
||||||
Therefore, you'll have to build the web application first, so that it can be embedded in the binary.
|
|
||||||
|
|
||||||
You will need:
|
|
||||||
- rust
|
|
||||||
- wasm-pack
|
|
||||||
- npm
|
|
||||||
|
|
||||||
First, build the frontend:
|
|
||||||
```bash
|
|
||||||
cd web/pw-frontend
|
|
||||||
npm install
|
|
||||||
npm run build-wasm
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Then build the backend:
|
|
||||||
```bash
|
|
||||||
cd planetwars-cli
|
|
||||||
cargo build --bin pwcli --release
|
|
||||||
```
|
|
||||||
|
|
||||||
You can install the binary by running
|
|
||||||
```bash
|
|
||||||
cargo install --path .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
First, initialize your workspace:
|
|
||||||
```bash
|
|
||||||
pwcli init my-planetwars-workspace
|
|
||||||
```
|
|
||||||
This creates all required files and directories for your planetwars workspace:
|
|
||||||
- `pw_workspace.toml`: workspace configuration
|
|
||||||
- `maps/`: for storing maps
|
|
||||||
- `matches/`: match logs will be written here
|
|
||||||
- `bots/simplebot/` an example bot to get started
|
|
||||||
|
|
||||||
All subsequent commands should be run from the root directory of your workspace.
|
|
||||||
|
|
||||||
Try playing an example match:
|
|
||||||
```bash
|
|
||||||
pwcli run-match hex simplebot simplebot
|
|
||||||
```
|
|
||||||
|
|
||||||
You can now watch a visualization of the match in the web interface:
|
|
||||||
```bash
|
|
||||||
pwcli serve
|
|
||||||
```
|
|
||||||
|
|
||||||
You can now try writing your own bot by copying the `simplebot` example. Don't forget to add it in your workspace configuration!
|
|
|
@ -1,43 +0,0 @@
|
||||||
{
|
|
||||||
"planets": [
|
|
||||||
{
|
|
||||||
"name": "protos",
|
|
||||||
"x": -6,
|
|
||||||
"y": 0,
|
|
||||||
"owner": 1,
|
|
||||||
"ship_count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "duteros",
|
|
||||||
"x": -3,
|
|
||||||
"y": 5,
|
|
||||||
"ship_count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tritos",
|
|
||||||
"x": 3,
|
|
||||||
"y": 5,
|
|
||||||
"ship_count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tetartos",
|
|
||||||
"x": 6,
|
|
||||||
"y": 0,
|
|
||||||
"owner": 2,
|
|
||||||
"ship_count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pemptos",
|
|
||||||
"x": 3,
|
|
||||||
"y": -5,
|
|
||||||
"ship_count": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "extos",
|
|
||||||
"x": -3,
|
|
||||||
"y": -5,
|
|
||||||
"ship_count": 6
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
[paths]
|
|
||||||
maps_dir = "maps"
|
|
||||||
matches_dir = "matches"
|
|
||||||
|
|
||||||
[bots.simplebot]
|
|
||||||
path = "bots/simplebot"
|
|
|
@ -1,2 +0,0 @@
|
||||||
name = "simplebot"
|
|
||||||
run_command = "python3 simplebot.py"
|
|
|
@ -1,33 +0,0 @@
|
||||||
import sys, json
|
|
||||||
|
|
||||||
def move(command):
|
|
||||||
""" print a command record to stdout """
|
|
||||||
moves = []
|
|
||||||
if command is not None:
|
|
||||||
moves.append(command)
|
|
||||||
|
|
||||||
print(json.dumps({ 'moves': moves }))
|
|
||||||
# flush the buffer, so that the gameserver can receive the json-encoded line.
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
|
|
||||||
for line in sys.stdin:
|
|
||||||
state = json.loads(line)
|
|
||||||
# you are always player 1.
|
|
||||||
my_planets = [p for p in state['planets'] if p['owner'] == 1]
|
|
||||||
other_planets = [p for p in state['planets'] if p['owner'] != 1]
|
|
||||||
|
|
||||||
if not my_planets or not other_planets:
|
|
||||||
# we don't own any planets, so we can't make any moves.
|
|
||||||
move(None)
|
|
||||||
else:
|
|
||||||
# find my planet that has the most ships
|
|
||||||
planet = max(my_planets, key=lambda p: p['ship_count'])
|
|
||||||
# find enemy planet that has the least ships
|
|
||||||
destination = min(other_planets, key=lambda p: p['ship_count'])
|
|
||||||
# attack!
|
|
||||||
move({
|
|
||||||
'origin': planet['name'],
|
|
||||||
'destination': destination['name'],
|
|
||||||
'ship_count': planet['ship_count'] - 1
|
|
||||||
})
|
|
|
@ -1,6 +0,0 @@
|
||||||
use planetwars_cli;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
planetwars_cli::run().await
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
use clap::Parser;
|
|
||||||
use std::io;
|
|
||||||
use tokio::process;
|
|
||||||
|
|
||||||
use crate::workspace::Workspace;
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
pub struct BuildCommand {
|
|
||||||
/// Name of the bot to build
|
|
||||||
bot: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BuildCommand {
|
|
||||||
pub async fn run(self) -> io::Result<()> {
|
|
||||||
let workspace = Workspace::open_current_dir()?;
|
|
||||||
let bot = workspace.get_bot(&self.bot)?;
|
|
||||||
if let Some(argv) = bot.config.get_build_argv() {
|
|
||||||
process::Command::new(&argv[0])
|
|
||||||
.args(&argv[1..])
|
|
||||||
.current_dir(&bot.path)
|
|
||||||
.spawn()?
|
|
||||||
.wait()
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
use futures::io;
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
pub struct InitCommand {
|
|
||||||
/// workspace root directory
|
|
||||||
path: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! copy_asset {
|
|
||||||
($path:expr, $file_name:literal) => {
|
|
||||||
::std::fs::write(
|
|
||||||
$path.join($file_name),
|
|
||||||
include_bytes!(concat!("../../assets/", $file_name)),
|
|
||||||
)?;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InitCommand {
|
|
||||||
pub async fn run(self) -> io::Result<()> {
|
|
||||||
let path = PathBuf::from(&self.path);
|
|
||||||
|
|
||||||
// create directories
|
|
||||||
std::fs::create_dir_all(&path)?;
|
|
||||||
std::fs::create_dir(path.join("maps"))?;
|
|
||||||
std::fs::create_dir(path.join("matches"))?;
|
|
||||||
std::fs::create_dir_all(path.join("bots/simplebot"))?;
|
|
||||||
|
|
||||||
// create files
|
|
||||||
copy_asset!(path, "pw_workspace.toml");
|
|
||||||
copy_asset!(path.join("maps"), "hex.json");
|
|
||||||
copy_asset!(path.join("bots/"), "simplebot/botconfig.toml");
|
|
||||||
copy_asset!(path.join("bots/"), "simplebot/simplebot.py");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
mod build;
|
|
||||||
mod init;
|
|
||||||
mod run_match;
|
|
||||||
mod serve;
|
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
#[clap(name = "pwcli")]
|
|
||||||
#[clap(author, version, about)]
|
|
||||||
pub struct Cli {
|
|
||||||
#[clap(subcommand)]
|
|
||||||
command: Command,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cli {
|
|
||||||
pub async fn run() -> io::Result<()> {
|
|
||||||
let cli = Self::parse();
|
|
||||||
|
|
||||||
match cli.command {
|
|
||||||
Command::Init(command) => command.run().await,
|
|
||||||
Command::RunMatch(command) => command.run().await,
|
|
||||||
Command::Serve(command) => command.run().await,
|
|
||||||
Command::Build(command) => command.run().await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
enum Command {
|
|
||||||
/// Initialize a new workspace
|
|
||||||
Init(init::InitCommand),
|
|
||||||
/// Run a match
|
|
||||||
RunMatch(run_match::RunMatchCommand),
|
|
||||||
/// Host local webserver
|
|
||||||
Serve(serve::ServeCommand),
|
|
||||||
/// Run build command for a bot
|
|
||||||
Build(build::BuildCommand),
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
use planetwars_matchrunner::{run_match, MatchConfig, MatchPlayer};
|
|
||||||
|
|
||||||
use crate::workspace::Workspace;
|
|
||||||
#[derive(Parser)]
|
|
||||||
pub struct RunMatchCommand {
|
|
||||||
/// map name
|
|
||||||
map: String,
|
|
||||||
/// bot names
|
|
||||||
bots: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RunMatchCommand {
|
|
||||||
pub async fn run(self) -> io::Result<()> {
|
|
||||||
let workspace = Workspace::open_current_dir()?;
|
|
||||||
|
|
||||||
let map_path = workspace.map_path(&self.map);
|
|
||||||
let timestamp = chrono::Local::now().format("%Y-%m-%d-%H-%M-%S");
|
|
||||||
let log_path = workspace.match_path(&format!("{}-{}", &self.map, ×tamp));
|
|
||||||
|
|
||||||
let mut players = Vec::new();
|
|
||||||
for bot_name in &self.bots {
|
|
||||||
let bot = workspace.get_bot(&bot_name)?;
|
|
||||||
players.push(MatchPlayer {
|
|
||||||
name: bot_name.clone(),
|
|
||||||
path: bot.path.clone(),
|
|
||||||
argv: bot.config.get_run_argv(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let match_config = MatchConfig {
|
|
||||||
map_name: self.map,
|
|
||||||
map_path,
|
|
||||||
log_path: log_path.clone(),
|
|
||||||
players,
|
|
||||||
};
|
|
||||||
|
|
||||||
run_match(match_config).await;
|
|
||||||
println!("match completed successfully");
|
|
||||||
// TODO: maybe print the match result as well?
|
|
||||||
|
|
||||||
let relative_path = match log_path.strip_prefix(&workspace.root_path) {
|
|
||||||
Ok(path) => path.to_str().unwrap(),
|
|
||||||
Err(_) => log_path.to_str().unwrap(),
|
|
||||||
};
|
|
||||||
println!("wrote match log to {}", relative_path);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
|
|
||||||
use crate::web;
|
|
||||||
use crate::workspace::Workspace;
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
pub struct ServeCommand;
|
|
||||||
|
|
||||||
impl ServeCommand {
|
|
||||||
pub async fn run(self) -> io::Result<()> {
|
|
||||||
let workspace = Workspace::open_current_dir()?;
|
|
||||||
web::run(workspace).await;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
mod commands;
|
|
||||||
mod web;
|
|
||||||
mod workspace;
|
|
||||||
|
|
||||||
pub async fn run() {
|
|
||||||
let res = commands::Cli::run().await;
|
|
||||||
if let Err(err) = res {
|
|
||||||
eprintln!("{}", err);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,175 +0,0 @@
|
||||||
use axum::{
|
|
||||||
body::{boxed, Full},
|
|
||||||
extract::{ws::WebSocket, Extension, Path, WebSocketUpgrade},
|
|
||||||
handler::Handler,
|
|
||||||
http::{header, StatusCode, Uri},
|
|
||||||
response::{IntoResponse, Response},
|
|
||||||
routing::{get, Router},
|
|
||||||
AddExtensionLayer, Json,
|
|
||||||
};
|
|
||||||
use mime_guess;
|
|
||||||
use planetwars_matchrunner::MatchMeta;
|
|
||||||
use rust_embed::RustEmbed;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::{
|
|
||||||
fs,
|
|
||||||
io::{self, BufRead},
|
|
||||||
net::SocketAddr,
|
|
||||||
path,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::workspace::Workspace;
|
|
||||||
|
|
||||||
struct State {
|
|
||||||
workspace: Workspace,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
fn new(workspace: Workspace) -> Self {
|
|
||||||
Self { workspace }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run(workspace: Workspace) {
|
|
||||||
let shared_state = Arc::new(State::new(workspace));
|
|
||||||
|
|
||||||
// build our application with a route
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/", get(index_handler))
|
|
||||||
.route("/ws", get(ws_handler))
|
|
||||||
.route("/api/matches", get(list_matches))
|
|
||||||
.route("/api/matches/:match_id", get(get_match))
|
|
||||||
.fallback(static_handler.into_service())
|
|
||||||
.layer(AddExtensionLayer::new(shared_state));
|
|
||||||
|
|
||||||
// run it
|
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], 5000));
|
|
||||||
println!("serving at http://{}", addr);
|
|
||||||
axum::Server::bind(&addr)
|
|
||||||
.serve(app.into_make_service())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn ws_handler(ws: WebSocketUpgrade) -> impl IntoResponse {
|
|
||||||
ws.on_upgrade(handle_socket)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_socket(mut socket: WebSocket) {
|
|
||||||
while let Some(msg) = socket.recv().await {
|
|
||||||
let msg = if let Ok(msg) = msg {
|
|
||||||
msg
|
|
||||||
} else {
|
|
||||||
// client disconnected
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if socket.send(msg).await.is_err() {
|
|
||||||
// client disconnected
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct MatchData {
|
|
||||||
name: String,
|
|
||||||
#[serde(flatten)]
|
|
||||||
meta: MatchMeta,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn list_matches(Extension(state): Extension<Arc<State>>) -> Json<Vec<MatchData>> {
|
|
||||||
let mut matches = state
|
|
||||||
.workspace
|
|
||||||
.matches_dir()
|
|
||||||
.read_dir()
|
|
||||||
.unwrap()
|
|
||||||
.filter_map(|entry| {
|
|
||||||
let entry = entry.unwrap();
|
|
||||||
get_match_data(&entry).ok()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
matches.sort_by(|a, b| {
|
|
||||||
let a = a.meta.timestamp;
|
|
||||||
let b = b.meta.timestamp;
|
|
||||||
a.cmp(&b).reverse()
|
|
||||||
});
|
|
||||||
Json(matches)
|
|
||||||
}
|
|
||||||
|
|
||||||
// extracts 'filename' if the entry matches'$filename.log'.
|
|
||||||
fn get_match_data(entry: &fs::DirEntry) -> io::Result<MatchData> {
|
|
||||||
let file_name = entry.file_name();
|
|
||||||
let path = path::Path::new(&file_name);
|
|
||||||
|
|
||||||
let name = get_match_name(&path)
|
|
||||||
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid match name"))?;
|
|
||||||
|
|
||||||
let meta = read_match_meta(&entry.path())?;
|
|
||||||
|
|
||||||
Ok(MatchData { name, meta })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_match_name(path: &path::Path) -> Option<String> {
|
|
||||||
if path.extension() != Some("log".as_ref()) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
path.file_stem()
|
|
||||||
.and_then(|name| name.to_str())
|
|
||||||
.map(|name| name.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_match_meta(path: &path::Path) -> io::Result<MatchMeta> {
|
|
||||||
let file = fs::File::open(path)?;
|
|
||||||
let mut reader = io::BufReader::new(file);
|
|
||||||
let mut line = String::new();
|
|
||||||
reader.read_line(&mut line)?;
|
|
||||||
let meta: MatchMeta = serde_json::from_str(&line)?;
|
|
||||||
Ok(meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_match(Extension(state): Extension<Arc<State>>, Path(id): Path<String>) -> String {
|
|
||||||
let mut match_path = state.workspace.matches_dir().join(id);
|
|
||||||
match_path.set_extension("log");
|
|
||||||
fs::read_to_string(match_path).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn index_handler() -> impl IntoResponse {
|
|
||||||
static_handler("/index.html".parse::<Uri>().unwrap()).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// static_handler is a handler that serves static files from the
|
|
||||||
async fn static_handler(uri: Uri) -> impl IntoResponse {
|
|
||||||
let path = uri.path().trim_start_matches('/').to_string();
|
|
||||||
StaticFile(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(RustEmbed)]
|
|
||||||
#[folder = "../web/pw-frontend/dist/"]
|
|
||||||
struct Asset;
|
|
||||||
pub struct StaticFile<T>(pub T);
|
|
||||||
|
|
||||||
impl<T> IntoResponse for StaticFile<T>
|
|
||||||
where
|
|
||||||
T: Into<String>,
|
|
||||||
{
|
|
||||||
fn into_response(self) -> Response {
|
|
||||||
let path = self.0.into();
|
|
||||||
match Asset::get(path.as_str()) {
|
|
||||||
Some(content) => {
|
|
||||||
let body = boxed(Full::from(content.data));
|
|
||||||
let mime = mime_guess::from_path(path).first_or_octet_stream();
|
|
||||||
Response::builder()
|
|
||||||
.header(header::CONTENT_TYPE, mime.as_ref())
|
|
||||||
.body(body)
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
None => Response::builder()
|
|
||||||
.status(StatusCode::NOT_FOUND)
|
|
||||||
.body(boxed(Full::from("404")))
|
|
||||||
.unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
use shlex;
|
|
||||||
use std::fs;
|
|
||||||
use std::io;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
const BOT_CONFIG_FILENAME: &str = "botconfig.toml";
|
|
||||||
|
|
||||||
pub struct WorkspaceBot {
|
|
||||||
pub path: PathBuf,
|
|
||||||
pub config: BotConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WorkspaceBot {
|
|
||||||
pub fn open(path: &Path) -> io::Result<Self> {
|
|
||||||
let config_path = path.join(BOT_CONFIG_FILENAME);
|
|
||||||
let config_str = fs::read_to_string(config_path)?;
|
|
||||||
let bot_config: BotConfig = toml::from_str(&config_str)?;
|
|
||||||
|
|
||||||
Ok(WorkspaceBot {
|
|
||||||
path: path.to_path_buf(),
|
|
||||||
config: bot_config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct BotConfig {
|
|
||||||
pub name: String,
|
|
||||||
pub run_command: String,
|
|
||||||
pub build_command: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BotConfig {
|
|
||||||
// TODO: these commands should not be here
|
|
||||||
pub fn get_run_argv(&self) -> Vec<String> {
|
|
||||||
// TODO: proper error handling
|
|
||||||
shlex::split(&self.run_command)
|
|
||||||
.expect("Failed to parse bot run command. Check for unterminated quotes.")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_build_argv(&self) -> Option<Vec<String>> {
|
|
||||||
// TODO: proper error handling
|
|
||||||
self.build_command.as_ref().map(|cmd| {
|
|
||||||
shlex::split(cmd)
|
|
||||||
.expect("Failed to parse bot build command. Check for unterminated quotes.")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::env;
|
|
||||||
use std::fs;
|
|
||||||
use std::io;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use self::bot::WorkspaceBot;
|
|
||||||
|
|
||||||
const WORKSPACE_CONFIG_FILENAME: &str = "pw_workspace.toml";
|
|
||||||
|
|
||||||
pub mod bot;
|
|
||||||
|
|
||||||
pub struct Workspace {
|
|
||||||
pub root_path: PathBuf,
|
|
||||||
pub config: WorkspaceConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct WorkspaceConfig {
|
|
||||||
paths: WorkspacePaths,
|
|
||||||
bots: HashMap<String, BotEntry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct WorkspacePaths {
|
|
||||||
maps_dir: PathBuf,
|
|
||||||
matches_dir: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct BotEntry {
|
|
||||||
path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Workspace {
|
|
||||||
pub fn open(root_path: &Path) -> io::Result<Workspace> {
|
|
||||||
let config_path = root_path.join(WORKSPACE_CONFIG_FILENAME);
|
|
||||||
let config_str = fs::read_to_string(config_path)?;
|
|
||||||
let workspace_config: WorkspaceConfig = toml::from_str(&config_str)?;
|
|
||||||
|
|
||||||
Ok(Workspace {
|
|
||||||
root_path: root_path.to_path_buf(),
|
|
||||||
config: workspace_config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open_current_dir() -> io::Result<Workspace> {
|
|
||||||
Workspace::open(&env::current_dir()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn maps_dir(&self) -> PathBuf {
|
|
||||||
self.root_path.join(&self.config.paths.maps_dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn map_path(&self, map_name: &str) -> PathBuf {
|
|
||||||
self.maps_dir().join(format!("{}.json", map_name))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn matches_dir(&self) -> PathBuf {
|
|
||||||
self.root_path.join(&self.config.paths.matches_dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn match_path(&self, match_name: &str) -> PathBuf {
|
|
||||||
self.matches_dir().join(format!("{}.log", match_name))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_bot(&self, bot_name: &str) -> io::Result<WorkspaceBot> {
|
|
||||||
let bot_entry = self.config.bots.get(bot_name).ok_or_else(|| {
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::NotFound,
|
|
||||||
format!("no such bot: {}", bot_name),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
WorkspaceBot::open(&self.root_path.join(&bot_entry.path))
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue