-
Notifications
You must be signed in to change notification settings - Fork 3
Feature/generate levels graph #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
19b042d
Changed Rust project structure, and started the new graph generation …
sandspider2234 994b62b
Moved the rust scripts one dir up and added some code to the generate…
ShayNehmad a85e083
Works up until the graph is created
ShayNehmad 92c0cbd
Generate level graph works!
ShayNehmad 077d730
Improve how the graph looks and behaves in the UI
ShayNehmad cc5b59c
Added some unit tests, run with `cargo test`
ShayNehmad 602f0fd
Fix CR comments
ShayNehmad 5a60376
Fixed CR comment :)
ShayNehmad File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
9 changes: 7 additions & 2 deletions
9
scripts/generate-pre-receive-hook/Cargo.toml → scripts/Cargo.toml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,21 @@ | ||
[package] | ||
name = "generate-pre-receive-hook" | ||
name = "make-git-better-scripts" | ||
version = "0.1.0" | ||
authors = ["Shay Nehmad <shay.nehmad@guardicore.com>"] | ||
edition = "2018" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[lib] | ||
name = "common" | ||
path = "src/lib/lib.rs" | ||
|
||
[dependencies] | ||
structopt = "0.3.13" | ||
serde = { version = "1.0", features = ["derive"] } | ||
serde_json = "1.0" | ||
toml = "0.5" | ||
tinytemplate = "1.0.4" | ||
simple_logger = "1.6.0" | ||
log = "0.4" | ||
|
||
petgraph = "0.5" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
use log::{debug, info}; | ||
use petgraph::dot::{Config, Dot}; | ||
use petgraph::graph::NodeIndex; | ||
use petgraph::{Directed, Graph}; | ||
use serde::Serialize; | ||
use std::fs; | ||
use std::io::Write; | ||
use structopt::StructOpt; | ||
use tinytemplate::TinyTemplate; | ||
|
||
use common::{GameConfig, Level}; | ||
|
||
type LevelsGraph = Graph<Level, i32, Directed>; | ||
|
||
#[derive(Debug, StructOpt)] | ||
#[structopt(about = "A script to generate a levels graph from a game config.")] | ||
struct Cli { | ||
#[structopt(parse(from_os_str), help = "Path to game config file to read")] | ||
game_config_path: std::path::PathBuf, | ||
|
||
#[structopt(parse(from_os_str), help = "Path to the graph template file to read")] | ||
template_path: std::path::PathBuf, | ||
|
||
#[structopt( | ||
parse(from_os_str), | ||
default_value = "output/levelgraph.html", | ||
help = "Path to output file (creates if doesn't exist)" | ||
)] | ||
output_path: std::path::PathBuf, | ||
|
||
#[structopt( | ||
short = "v", | ||
long = "verbose", | ||
help = "Show more information about the actions taken" | ||
)] | ||
verbose: bool, | ||
} | ||
|
||
/// Recursive function that populates the game graph | ||
/// | ||
/// If receives a graph initialized with the first level as a root node. | ||
fn add_level_nodes_to_graph<'a>( | ||
current_level: Level, | ||
current_node: &'a NodeIndex, | ||
levels_graph: &'a mut LevelsGraph, | ||
game_config: &'a GameConfig, | ||
) { | ||
if current_level.flags.len() == 0 { | ||
return; | ||
}; | ||
|
||
for flag in current_level.flags { | ||
debug!("level {} flag {}", current_level.title, flag); | ||
let mut levels_iterator = game_config.levels.iter(); | ||
let found = levels_iterator.find(|x| x.title == flag); | ||
match found { | ||
Some(x) => { | ||
debug!( | ||
"The flag does point to another level, {}. Adding level as node to graph", | ||
x.title | ||
); | ||
let new_node = levels_graph.add_node(x.clone()); | ||
debug!("Adding edge from {} to {}", current_level.title, x.title); | ||
levels_graph.add_edge(*current_node, new_node, 0); | ||
debug!("Recursive calling add nodes on {}", x.title); | ||
add_level_nodes_to_graph(x.clone(), &new_node, levels_graph, &game_config); | ||
} | ||
None => { | ||
debug!("The flag doesn't point to another level - no need to recurse"); | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn create_graph_from_game_config(game_config: &GameConfig) -> LevelsGraph { | ||
let mut levels_graph = LevelsGraph::new(); | ||
|
||
let first_level = game_config.levels[0].clone(); | ||
let tree_root = levels_graph.add_node(first_level.clone()); | ||
add_level_nodes_to_graph(first_level, &tree_root, &mut levels_graph, &game_config); | ||
|
||
levels_graph | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use petgraph::algo::is_cyclic_directed; | ||
|
||
#[test] | ||
fn test_create_graph_from_game_config() { | ||
let first_level = Level { | ||
title: String::from("first"), | ||
branch: String::from("first"), | ||
solution_checker: String::from("first"), | ||
flags: vec!["second".to_string()], | ||
}; | ||
let second_level = Level { | ||
title: String::from("second"), | ||
branch: String::from("sec"), | ||
solution_checker: String::from("sec"), | ||
flags: vec!["another".to_string(), "asdf".to_string()], | ||
}; | ||
|
||
let game_conf = GameConfig { | ||
levels: vec![first_level, second_level], | ||
}; | ||
let graph = create_graph_from_game_config(&game_conf); | ||
|
||
assert_eq!(graph.node_count(), 2); | ||
assert_eq!(graph.edge_count(), 1); | ||
assert!(graph.is_directed()); | ||
assert!(!is_cyclic_directed(&graph)); | ||
} | ||
} | ||
|
||
#[derive(Serialize)] | ||
struct Context { | ||
levels_graph_as_dot: String, | ||
} | ||
|
||
fn main() { | ||
let args = Cli::from_args(); | ||
|
||
if args.verbose { | ||
simple_logger::init_with_level(log::Level::Debug).unwrap(); | ||
} else { | ||
simple_logger::init_with_level(log::Level::Info).unwrap(); | ||
}; | ||
|
||
info!("Reading script from {:?}", args.game_config_path); | ||
let game_config_file_contents = fs::read_to_string(args.game_config_path).unwrap(); | ||
let game_config: GameConfig = toml::from_str(&game_config_file_contents).unwrap(); | ||
|
||
let levels_graph = create_graph_from_game_config(&game_config); | ||
|
||
let levels_graph_as_dot = Dot::with_config(&levels_graph, &[Config::EdgeNoLabel]); | ||
let context = Context { | ||
levels_graph_as_dot: format!("{}", levels_graph_as_dot), | ||
}; | ||
|
||
debug!("Generated graph:\n{:?}", levels_graph_as_dot); | ||
|
||
info!("Reading template from {:?}", args.template_path); | ||
let template_file_contents = fs::read_to_string(args.template_path).unwrap(); | ||
|
||
let mut tt = TinyTemplate::new(); | ||
let template_name = "levels_graph"; | ||
tt.add_template(template_name, &template_file_contents) | ||
.unwrap(); | ||
let rendered = tt.render(template_name, &context).unwrap(); | ||
|
||
debug!("########## RENDERED TEMPLATE ##########"); | ||
debug!("{}\n", rendered); | ||
|
||
let mut output_dir = args.output_path.clone(); | ||
output_dir.pop(); | ||
fs::create_dir_all(&output_dir).expect("Failed to create parent dir"); | ||
let mut output_file = fs::File::create(&args.output_path).expect("Couldn't create file!"); | ||
output_file.write_all(&rendered.as_bytes()).unwrap(); | ||
|
||
info!("Wrote rendered file to {:?}", args.output_path); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<div id="mynetwork"></div> | ||
|
||
<script type="text/javascript"> | ||
var DOTstring = ` | ||
{levels_graph_as_dot | unescaped} | ||
`; | ||
var parsedData = vis.parseDOTNetwork(DOTstring); | ||
|
||
var data = \{ | ||
nodes: parsedData.nodes, | ||
edges: parsedData.edges | ||
} | ||
|
||
// create a network | ||
var container = document.getElementById('mynetwork'); | ||
|
||
var options = \{ | ||
autoResize: true, | ||
nodes: \{ | ||
shape: "box", | ||
shadow: true, | ||
color: "#e8eef2", | ||
font: "20px arial black" | ||
}, | ||
edges: \{ | ||
color: "#e8eef2", | ||
}, | ||
physics: \{ | ||
enabled: true, | ||
solver: "hierarchicalRepulsion", | ||
}, | ||
layout: \{ | ||
hierarchical: \{ | ||
direction: "LR", | ||
levelSeparation: 100, | ||
nodeSpacing: 33, | ||
} | ||
} | ||
}; | ||
|
||
// initialize your network! | ||
var network = new vis.Network(container, data, options); | ||
network.on("click", function(params) \{ | ||
if (1 == params.nodes.length) \{ | ||
levelName = data.nodes[params.nodes[0]].label; | ||
console.log("Clicked on one node, it's this node: " + levelName); | ||
resulting_url = document.location.origin + "/levels/" + levelName; | ||
console.log("Resulting URL: " + resulting_url); | ||
document.location.href = resulting_url; | ||
} | ||
}); | ||
</script> |
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
use serde::{Deserialize, Serialize}; | ||
use std::fmt; | ||
|
||
#[derive(Debug, Clone, Default, Deserialize, Serialize)] | ||
pub struct Level { | ||
pub title: String, | ||
pub branch: String, | ||
pub solution_checker: String, | ||
pub flags: Vec<String>, | ||
} | ||
|
||
impl fmt::Display for Level { | ||
// This trait requires `fmt` with this exact signature. | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
// Write strictly the first element into the supplied output | ||
// stream: `f`. Returns `fmt::Result` which indicates whether the | ||
// operation succeeded or failed. Note that `write!` uses syntax which | ||
// is very similar to `println!`. | ||
write!(f, "{}", self.title) | ||
} | ||
} | ||
sandspider2234 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#[derive(Debug, Default, Deserialize, Serialize)] | ||
pub struct GameConfig { | ||
pub levels: Vec<Level>, | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_level_display() { | ||
let level = Level { | ||
title: "tit".to_string(), | ||
branch: "bra".to_string(), | ||
solution_checker: "sol".to_string(), | ||
flags: vec!["fla".to_string()], | ||
}; | ||
assert_eq!(format!("{}", level), "tit".to_string()); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.