parent
73119e7e8e
commit
5c5fd81b38
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "stackline-cli"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Shad Amethyst"]
|
||||
description = "A simple runner and editor for Stackline 2"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "3.2", features = ["derive"] }
|
||||
stackline = { path = "../stackline" }
|
||||
serde_json = "1.0"
|
@ -0,0 +1,505 @@
|
||||
#![feature(iter_intersperse)]
|
||||
|
||||
use stackline::prelude::*;
|
||||
use stackline::tile::*;
|
||||
use clap::Parser;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use std::io::Write;
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
// let mut world = World::new();
|
||||
// let mut pane = Pane::empty(4, 4).unwrap();
|
||||
// pane.set_tile((0, 0), FullTile::from(Wire::new(Orientation::Any)));
|
||||
// world.set_pane(String::from("main"), pane);
|
||||
// let raw = serde_json::to_string(&world).unwrap();
|
||||
// std::fs::write(&args.file, &raw).unwrap();
|
||||
|
||||
let raw = std::fs::read_to_string(&args.file).expect(&format!("Couldn't open {}!", args.file.display()));
|
||||
|
||||
let mut world: World = serde_json::from_str(&raw).unwrap();
|
||||
|
||||
loop {
|
||||
let mut line = String::new();
|
||||
std::io::stdin().read_line(&mut line).unwrap();
|
||||
|
||||
line = line.trim().to_string();
|
||||
let mut tokens = line.split(' ');
|
||||
match tokens.next() {
|
||||
None => continue,
|
||||
Some("run") => {
|
||||
if let Some(Ok(steps)) = tokens.next().map(|s| s.parse::<usize>()) {
|
||||
run(&mut world, steps).unwrap();
|
||||
} else {
|
||||
eprintln!("Syntax error: invalid number of steps");
|
||||
}
|
||||
}
|
||||
Some("step") => {
|
||||
step(&mut world);
|
||||
}
|
||||
|
||||
Some("print") => {
|
||||
print!("{}", world);
|
||||
}
|
||||
Some("get") => {
|
||||
if let (Some(x), Some(y)) = (tokens.next(), tokens.next()) {
|
||||
if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) {
|
||||
get(&world, x, y);
|
||||
}
|
||||
} else {
|
||||
eprintln!("Expected two arguments");
|
||||
}
|
||||
}
|
||||
|
||||
Some("set") => {
|
||||
if let (Some(x), Some(y), Some(name)) = (tokens.next(), tokens.next(), tokens.next()) {
|
||||
if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) {
|
||||
set(&mut world, x, y, name);
|
||||
}
|
||||
} else {
|
||||
eprintln!("Expected three arguments");
|
||||
}
|
||||
}
|
||||
Some("remove") => {
|
||||
if let (Some(x), Some(y)) = (tokens.next(), tokens.next()) {
|
||||
if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) {
|
||||
remove(&mut world, x, y);
|
||||
}
|
||||
} else {
|
||||
eprintln!("Expected two arguments");
|
||||
}
|
||||
}
|
||||
Some("prop") => {
|
||||
if let (Some(x), Some(y), Some(prop_name)) = (tokens.next(), tokens.next(), tokens.next()) {
|
||||
if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) {
|
||||
prop(&mut world, x, y, prop_name, tokens.intersperse(" ").collect());
|
||||
}
|
||||
} else {
|
||||
eprintln!("Expected four arguments");
|
||||
}
|
||||
}
|
||||
Some("state") => {
|
||||
if let (Some(x), Some(y), Some(new_state)) = (tokens.next(), tokens.next(), tokens.next()) {
|
||||
if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) {
|
||||
state(&mut world, x, y, new_state);
|
||||
}
|
||||
} else {
|
||||
eprintln!("Expected three arguments");
|
||||
}
|
||||
}
|
||||
|
||||
Some("signal") => {
|
||||
if let (Some(x), Some(y)) = (tokens.next(), tokens.next()) {
|
||||
if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) {
|
||||
signal(&mut world, x, y);
|
||||
}
|
||||
} else {
|
||||
eprintln!("Expected two arguments");
|
||||
}
|
||||
}
|
||||
Some("clear") => {
|
||||
if let (Some(x), Some(y)) = (tokens.next(), tokens.next()) {
|
||||
if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) {
|
||||
clear(&mut world, x, y);
|
||||
}
|
||||
} else {
|
||||
eprintln!("Expected two arguments");
|
||||
}
|
||||
}
|
||||
Some("push") => {
|
||||
if let (Some(x), Some(y)) = (tokens.next(), tokens.next()) {
|
||||
if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) {
|
||||
push(&mut world, x, y, tokens.intersperse(" ").collect());
|
||||
}
|
||||
} else {
|
||||
eprintln!("Expected three arguments");
|
||||
}
|
||||
}
|
||||
Some("pop") => {
|
||||
if let (Some(x), Some(y)) = (tokens.next(), tokens.next()) {
|
||||
if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) {
|
||||
pop(&mut world, x, y);
|
||||
}
|
||||
} else {
|
||||
eprintln!("Expected two arguments");
|
||||
}
|
||||
}
|
||||
Some("dir") => {
|
||||
if let (Some(x), Some(y), Some(direction)) = (tokens.next(), tokens.next(), tokens.next()) {
|
||||
if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) {
|
||||
dir(&mut world, x, y, direction);
|
||||
}
|
||||
} else {
|
||||
eprintln!("Expected three arguments");
|
||||
}
|
||||
}
|
||||
|
||||
Some("load") => {
|
||||
if let Some(path) = tokens.next() {
|
||||
load(&mut world, path);
|
||||
} else {
|
||||
load(&mut world, &args.file);
|
||||
}
|
||||
}
|
||||
Some("save") => {
|
||||
if let Some(path) = tokens.next() {
|
||||
save(&world, path);
|
||||
} else {
|
||||
save(&world, &args.file);
|
||||
}
|
||||
}
|
||||
|
||||
Some("help") => {
|
||||
println!("- `print`: prints the current world");
|
||||
println!("- `get <x> <y>`: prints the JSON-serialized data of the tile at (x, y)");
|
||||
println!("- `set <x> <y> <tilename>`: sets the tile at (x, y) to a default tilename");
|
||||
println!("- `remove <x> <y>`: removes the tile at (x, y)");
|
||||
|
||||
println!("- `prop <x> <y> <prop_name> [data]`: sets the property of the tile at (x, y)");
|
||||
println!(" if the tile is a single tuple struct, then prop_name is ignored.");
|
||||
println!(" if the tile is a tuple struct, then prop_name should be the index of the property");
|
||||
|
||||
println!("- `state <x> <y> <state>`: sets the state at (x, y) to `state`");
|
||||
|
||||
println!("- `signal <x> <y>`: adds an empty signal to the tile at (x, y)");
|
||||
println!("- `push <x> <y> <value>`: pushes `value` to the signal at (x, y)");
|
||||
println!("- `pop <x> <y>`: pops a value from the signal at (x, y)");
|
||||
println!("- `clear <x> <y>`: clears the signal of the tile at (x, y)");
|
||||
println!("- `dir <x> <y> <dir>`: sets the direction of the signal at (x, y)");
|
||||
|
||||
println!("- `run <steps>`: runs a number of steps");
|
||||
println!("- `step`: runs a single step");
|
||||
println!("- `load [file]`: saves the current state to `file` (defaults to the path in the parameters)");
|
||||
println!("- `save [file]`: saves the current state to `file` (defaults to the path in the parameters)");
|
||||
}
|
||||
Some(cmd) => {
|
||||
eprintln!("Syntax error: unknown command {}", cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(author, version, about)]
|
||||
struct Args {
|
||||
#[clap(short, long, value_parser)]
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
fn run(world: &mut World, steps: usize) -> std::io::Result<()> {
|
||||
let mut stdout = std::io::stdout();
|
||||
let mut first = true;
|
||||
for _ in 0..steps {
|
||||
if !first {
|
||||
world.step();
|
||||
write!(stdout, "\x1b[4;A")?;
|
||||
}
|
||||
first = false;
|
||||
write!(stdout, "{}", world)?;
|
||||
stdout.flush()?;
|
||||
std::thread::sleep(Duration::new(0, 100_000_000));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn step(world: &mut World) {
|
||||
world.step();
|
||||
print!("{}", world);
|
||||
}
|
||||
|
||||
fn get(world: &World, x: i32, y: i32) {
|
||||
match world.get((x, y)) {
|
||||
Some(tile) => {
|
||||
match serde_json::to_string_pretty(&*tile) {
|
||||
Ok(serialized) => println!("{}", serialized),
|
||||
Err(err) => eprintln!("Error while serializing tile at {}:{}; {}", x, y, err),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
eprintln!("No tile at {}:{}!", x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prop(world: &mut World, x: i32, y: i32, prop_name: &str, value: String) {
|
||||
use serde_json::Value;
|
||||
|
||||
let tile = match world.get_mut((x, y)) {
|
||||
Some(tile) => {
|
||||
if let Some(tile) = tile.get_mut() {
|
||||
tile
|
||||
} else {
|
||||
eprintln!("Tile at {}:{} is empty!", x, y);
|
||||
return
|
||||
}
|
||||
}
|
||||
None => {
|
||||
eprintln!("No tile at {}:{}!", x, y);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut tile_value = match serde_json::to_value(&tile) {
|
||||
Ok(serialized) => serialized,
|
||||
Err(err) => {
|
||||
eprintln!("Error while serializing tile at {}:{}; {}", x, y, err);
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
let parsed: Value = match serde_json::from_str(&value) {
|
||||
Ok(parsed) => parsed,
|
||||
Err(err) => {
|
||||
eprintln!("Error while parsing value: {}", err);
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if let Value::Object(ref mut enum_map) = &mut tile_value {
|
||||
if let Some(enum_value) = enum_map.values_mut().next() {
|
||||
match enum_value {
|
||||
Value::Object(map) => {
|
||||
map.insert(prop_name.to_string(), parsed);
|
||||
}
|
||||
Value::Array(vec) => {
|
||||
if let Ok(num) = prop_name.parse::<usize>() {
|
||||
if num >= vec.len() {
|
||||
eprintln!("Index out of bound: len is {} but index is {}", vec.len(), num);
|
||||
return
|
||||
}
|
||||
vec[num] = parsed;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
*enum_value = parsed;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!("Format error: expected enum to be encoded as a single-element map.");
|
||||
return
|
||||
}
|
||||
} else {
|
||||
eprintln!("Format error: expected enum to be encoded as a single-element map.");
|
||||
return
|
||||
}
|
||||
|
||||
*tile = match serde_json::from_value(tile_value) {
|
||||
Ok(tile) => tile,
|
||||
Err(err) => {
|
||||
eprintln!("Error while inserting value: {}", err);
|
||||
return
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn set(world: &mut World, x: i32, y: i32, name: &str) {
|
||||
let tile = match world.get_mut((x, y)) {
|
||||
Some(tile) => tile,
|
||||
None => {
|
||||
eprintln!("No tile at {}:{}!", x, y);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
*tile = match AnyTile::new(name) {
|
||||
Some(tile) => FullTile::from(tile),
|
||||
None => {
|
||||
eprintln!("No tile named {}", name);
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn remove(world: &mut World, x: i32, y: i32) {
|
||||
match world.get_mut((x, y)) {
|
||||
Some(tile) => *tile = FullTile::new(None),
|
||||
None => {
|
||||
eprintln!("No tile at {}:{}!", x, y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn signal(world: &mut World, x: i32, y: i32) {
|
||||
match world.get_mut_with_pos((x, y)) {
|
||||
Some((tile, x, y)) => {
|
||||
tile.set_signal(Some(Signal::empty((x, y), Direction::Right)));
|
||||
tile.set_state(State::Active);
|
||||
}
|
||||
None => {
|
||||
eprintln!("No tile at {}:{}!", x, y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(world: &mut World, x: i32, y: i32) {
|
||||
match world.get_mut((x, y)) {
|
||||
Some(tile) => {
|
||||
tile.set_signal(None);
|
||||
tile.set_state(State::Idle);
|
||||
}
|
||||
None => {
|
||||
eprintln!("No tile at {}:{}!", x, y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push(world: &mut World, x: i32, y: i32, value: String) {
|
||||
use serde_json::Value as JValue;
|
||||
|
||||
let value: JValue = match serde_json::from_str(&value) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
eprintln!("Error while parsing value: {}", err);
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
let value: Value = match value {
|
||||
JValue::Number(num) => {
|
||||
if let Some(f) = num.as_f64() {
|
||||
Value::Number(f)
|
||||
} else {
|
||||
eprintln!("Unsupported value: {:?}", num);
|
||||
return
|
||||
}
|
||||
}
|
||||
JValue::String(s) => Value::String(s),
|
||||
x => {
|
||||
eprintln!("Unsupported value: {:?}", x);
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
match world.get_mut((x, y)) {
|
||||
Some(tile) => {
|
||||
match tile.take_signal() {
|
||||
Some(mut signal) => {
|
||||
signal.push(value);
|
||||
tile.set_signal(Some(signal));
|
||||
}
|
||||
None => {
|
||||
eprintln!("No signal at {}:{}!", x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
eprintln!("No tile at {}:{}!", x, y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pop(world: &mut World, x: i32, y: i32) {
|
||||
match world.get_mut((x, y)) {
|
||||
Some(tile) => {
|
||||
match tile.take_signal() {
|
||||
Some(mut signal) => {
|
||||
let popped = signal.pop();
|
||||
tile.set_signal(Some(signal));
|
||||
|
||||
if let Some(popped) = popped {
|
||||
match serde_json::to_string_pretty(&popped) {
|
||||
Ok(pretty) => println!("{}", pretty),
|
||||
Err(err) => {
|
||||
eprintln!("Error while printing popped value: {}", err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!("Nothing to pop at {}:{}!", x, y);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
eprintln!("No signal at {}:{}!", x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
eprintln!("No tile at {}:{}!", x, y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dir(world: &mut World, x: i32, y: i32, direction: &str) {
|
||||
let direction: Direction = match serde_json::from_str(direction) {
|
||||
Ok(direction) => direction,
|
||||
Err(err) => {
|
||||
eprintln!("Error while parsing direction: {}", err);
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
match world.get_mut((x, y)) {
|
||||
Some(tile) => {
|
||||
match tile.take_signal() {
|
||||
Some(signal) => {
|
||||
tile.set_signal(Some(signal.moved(direction)));
|
||||
}
|
||||
None => {
|
||||
eprintln!("No signal at {}:{}!", x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
eprintln!("No tile at {}:{}!", x, y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn state(world: &mut World, x: i32, y: i32, state: &str) {
|
||||
let state: State = match serde_json::from_str(state) {
|
||||
Ok(state) => state,
|
||||
Err(err) => {
|
||||
eprintln!("Error while parsing state: {}", err);
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
match world.get_mut((x, y)) {
|
||||
Some(tile) => {
|
||||
tile.set_state(state);
|
||||
}
|
||||
None => {
|
||||
eprintln!("No tile at {}:{}!", x, y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn save(world: &World, path: impl AsRef<Path>) {
|
||||
match serde_json::to_string(world) {
|
||||
Ok(raw) => {
|
||||
std::fs::write(path.as_ref(), &raw).unwrap_or_else(|err| {
|
||||
eprintln!("Error while saving: {}", err);
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Error while converting world to JSON: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load(world: &mut World, path: impl AsRef<Path>) {
|
||||
match std::fs::read_to_string(path.as_ref()) {
|
||||
Ok(string) => {
|
||||
match serde_json::from_str(&string) {
|
||||
Ok(parsed) => {
|
||||
*world = parsed;
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Error while parsing file: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Error while reading file: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue