Skip to content
Snippets Groups Projects
program.rs 4.03 KiB
Newer Older
Stephen D's avatar
Stephen D committed
use crate::models::NewUserProgram;
use crate::schema::user_programs::columns;
use crate::schema::user_programs::columns::discord_user_id;
use crate::schema::user_programs::dsl::user_programs;
use bigdecimal::{BigDecimal, FromPrimitive};
use diesel::{BoolExpressionMethods, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
use serenity::model::id::UserId;
use std::collections::HashMap;

pub(crate) struct Program {
	pub(crate) code: HashMap<u32, String>,
}

impl Program {
	pub fn new() -> Self {
		Self {
			code: HashMap::new(),
		}
	}

	pub fn list_programs_by_user(conn: &PgConnection, user_id: UserId) -> Option<Vec<String>> {
		user_programs
			.filter(columns::discord_user_id.eq(BigDecimal::from_u64(*user_id.as_u64()).unwrap()))
			.select(columns::name)
			.get_results(conn)
			.ok()
	}

Stephen D's avatar
Stephen D committed
	pub fn list_published_programs(conn: &PgConnection) -> Option<Vec<(i32, String)>> {
		user_programs
			.filter(columns::published.eq(1))
			.select((columns::id, columns::name))
			.load(conn)
			.ok()
	}

	pub fn load_published_program(&mut self, conn: &PgConnection, id: i32) -> Option<String> {
		let program: Vec<(String, String)> = user_programs
			.filter(columns::id.eq(id).and(columns::published.eq(1)))
			.limit(1)
			.select((columns::name, columns::code))
			.get_results(conn)
			.ok()?;

		if program.is_empty() {
			return None;
		}

		let name = &program[0].0;
		let code = &program[0].1;

		self.parse_string(code)?;

		Some(name.to_string())
	}

	pub fn set_program_published(
		conn: &PgConnection,
		name: &str,
		user_id: UserId,
		published: bool,
	) -> Option<()> {
		if diesel::update(
			user_programs.filter(
				columns::discord_user_id
					.eq(BigDecimal::from_u64(*user_id.as_u64()).unwrap())
					.and(columns::name.eq(name)),
			),
		)
		.set(columns::published.eq(if published { 1 } else { 0 }))
		.execute(conn)
		.ok()? == 1
		{
			Some(())
		} else {
			None
		}
	}

Stephen D's avatar
Stephen D committed
	pub fn stringify(&self) -> String {
		let mut code: Vec<(u32, String)> =
			self.code.iter().map(|(a, b)| (*a, b.to_owned())).collect();
		code.sort_by(|a, b| a.0.cmp(&b.0));

		code.into_iter()
			.map(|a| a.1)
			.collect::<Vec<String>>()
			.join("\n")
	}

	pub fn stringy_line_nums(&self) -> String {
		let mut code: Vec<(u32, String)> =
			self.code.iter().map(|(a, b)| (*a, b.to_owned())).collect();
		code.sort_by(|a, b| a.0.cmp(&b.0));

		code.into_iter()
			.map(|a| format!("{}\t{}", a.0, a.1))
			.collect::<Vec<String>>()
			.join("\n")
	}

	pub fn save_program(&self, conn: &PgConnection, user_id: UserId, name: &str) -> Option<()> {
		let code = self.stringy_line_nums();
		let new_program = NewUserProgram {
			discord_user_id: BigDecimal::from_u64(*user_id.as_u64()).unwrap(),
			name,
			code: code.as_str(),
		};

		diesel::insert_into(user_programs)
			.values(&new_program)
			.on_conflict((discord_user_id, columns::name))
			.do_update()
			.set(columns::code.eq(&code))
			.execute(conn)
			.ok()?;

		Some(())
	}

	pub fn load_program(&mut self, conn: &PgConnection, user_id: UserId, name: &str) -> Option<()> {
		let code: Vec<String> = user_programs
			.filter(
				columns::discord_user_id
					.eq(BigDecimal::from_u64(*user_id.as_u64()).unwrap())
					.and(columns::name.eq(name)),
			)
			.limit(1)
			.select(columns::code)
			.get_results(conn)
			.ok()?;

		if code.is_empty() {
			return None;
		}

		let code = &code[0];

		self.parse_string(code)?;

		Some(())
	}

	fn parse_string(&mut self, code: &str) -> Option<()> {
		let mut valid = true;
		let code: HashMap<u32, String> = code
			.split('\n')
			.map(|line| {
				let mut iter = line.splitn(2, &[' ', '\t'][..]);
				// This unwrap_or_else thing is pretty ugly
				// Is there a better way?
				let line_num: u32 = iter
					.next()
					.unwrap_or_else(|| {
						valid = false;
						"0"
					})
					.parse()
					.unwrap_or_else(|_| {
						valid = false;
						0
					});
				let line_code = iter
					.next()
					.unwrap_or_else(|| {
						valid = false;
						""
					})
					.to_owned();

				(line_num, line_code)
			})
			.collect();

		if valid {
			self.code = code;
			Some(())
		} else {
			None
		}
	}
}