Skip to content

Commit c391a5b

Browse files
committed
feat: add graph command
1 parent 78e7fe4 commit c391a5b

4 files changed

Lines changed: 134 additions & 0 deletions

File tree

src/actions/simplicity/graph.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use std::str::FromStr;
2+
3+
use simplicity::{
4+
dag::{MaxSharing, NoSharing},
5+
jet,
6+
};
7+
8+
use crate::hal_simplicity::Program;
9+
10+
#[derive(Debug, thiserror::Error)]
11+
pub enum SimplicityGraphError {
12+
#[error("invalid program: {0}")]
13+
ProgramParse(simplicity::ParseError),
14+
}
15+
16+
#[derive(Debug, Clone, Copy)]
17+
pub enum SharingLevel {
18+
NoSharing,
19+
MaxSharing,
20+
}
21+
22+
impl FromStr for SharingLevel {
23+
type Err = String;
24+
fn from_str(s: &str) -> Result<Self, Self::Err> {
25+
match s.to_lowercase().as_str() {
26+
"none" => Ok(SharingLevel::NoSharing),
27+
"max" => Ok(SharingLevel::MaxSharing),
28+
other => Err(format!("unknown sharing level: {}", other)),
29+
}
30+
}
31+
}
32+
33+
#[derive(Debug, Clone, Copy)]
34+
pub enum GraphFormat {
35+
Dot,
36+
Mermaid,
37+
}
38+
39+
impl FromStr for GraphFormat {
40+
type Err = String;
41+
fn from_str(s: &str) -> Result<Self, Self::Err> {
42+
match s.to_lowercase().as_str() {
43+
"graphviz" | "dot" => Ok(GraphFormat::Dot),
44+
"mermaid" | "mermaidjs" => Ok(GraphFormat::Mermaid),
45+
other => Err(format!("unknown graph format: {}", other)),
46+
}
47+
}
48+
}
49+
50+
pub fn simplicity_graph(
51+
program: &str,
52+
witness: Option<&str>,
53+
sharing_level: SharingLevel,
54+
graph_format: GraphFormat,
55+
) -> Result<String, SimplicityGraphError> {
56+
let program = Program::<jet::Elements>::from_str(program, witness)
57+
.map_err(SimplicityGraphError::ProgramParse)?;
58+
59+
let sharing_level = match sharing_level {
60+
SharingLevel::MaxSharing => simplicity::node::SharingLevel::Max,
61+
SharingLevel::NoSharing => simplicity::node::SharingLevel::None,
62+
};
63+
let graph_format = match graph_format {
64+
GraphFormat::Dot => simplicity::node::GraphFormat::Dot,
65+
GraphFormat::Mermaid => simplicity::node::GraphFormat::Mermaid,
66+
};
67+
68+
let res = if let Some(node) = program.redeem_node() {
69+
let graph = node.display_as_graph(graph_format, sharing_level);
70+
graph.to_string()
71+
} else {
72+
let graph = program.commit_prog().display_as_graph(graph_format, sharing_level);
73+
graph.to_string()
74+
};
75+
Ok(res)
76+
}

src/actions/simplicity/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
pub mod graph;
12
pub mod info;
23
pub mod pset;
34
pub mod sighash;
45

6+
pub use graph::*;
57
pub use info::*;
68
pub use sighash::*;
79

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2025 Andrew Poelstra
2+
// SPDX-License-Identifier: CC0-1.0
3+
4+
use clap::value_t;
5+
use hal_simplicity::actions::simplicity::{GraphFormat, SharingLevel};
6+
7+
use crate::cmd;
8+
9+
use super::Error;
10+
11+
pub fn cmd<'a>() -> clap::App<'a, 'a> {
12+
cmd::subcommand("graph", "Parse a base64-encoded Simplicity program and display a graph").args(
13+
&[
14+
cmd::arg("program", "a Simplicity program in base64").takes_value(true).required(true),
15+
cmd::arg("witness", "a hex encoding of all the witness data for the program")
16+
.takes_value(true)
17+
.required(false),
18+
cmd::arg(
19+
"sharing",
20+
"the level of node sharing to use when displaying. Either none or max",
21+
)
22+
.short("s")
23+
.long("sharing")
24+
.takes_value(true)
25+
.required(false)
26+
.default_value("none")
27+
.possible_values(&["none", "max"]),
28+
cmd::arg("format", "the format for the graph, either graphviz (alias dot) or mermaid")
29+
.long("format")
30+
.takes_value(true)
31+
.required(false)
32+
.default_value("graphviz")
33+
.possible_values(&["graphviz", "dot", "mermaid"]),
34+
],
35+
)
36+
}
37+
38+
pub fn exec<'a>(matches: &clap::ArgMatches<'a>) {
39+
let program = matches.value_of("program").expect("program is mandatory");
40+
let witness = matches.value_of("witness");
41+
let sharing = value_t!(matches, "sharing", SharingLevel).unwrap_or(SharingLevel::NoSharing);
42+
let format = value_t!(matches, "format", GraphFormat).unwrap_or(GraphFormat::Dot);
43+
44+
match hal_simplicity::actions::simplicity::simplicity_graph(program, witness, sharing, format) {
45+
Ok(graph) => println!("{}", graph),
46+
Err(e) => cmd::print_output(
47+
matches,
48+
&Error {
49+
error: format!("{}", e),
50+
},
51+
),
52+
}
53+
}

src/bin/hal-simplicity/cmd/simplicity/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2025 Andrew Poelstra
22
// SPDX-License-Identifier: CC0-1.0
33

4+
mod graph;
45
mod info;
56
mod pset;
67
mod sighash;
@@ -16,6 +17,7 @@ struct Error {
1617

1718
pub fn subcommand<'a>() -> clap::App<'a, 'a> {
1819
cmd::subcommand_group("simplicity", "manipulate Simplicity programs")
20+
.subcommand(self::graph::cmd())
1921
.subcommand(self::info::cmd())
2022
.subcommand(self::pset::cmd())
2123
.subcommand(self::sighash::cmd())
@@ -24,6 +26,7 @@ pub fn subcommand<'a>() -> clap::App<'a, 'a> {
2426
pub fn execute<'a>(matches: &clap::ArgMatches<'a>) {
2527
match matches.subcommand() {
2628
("info", Some(m)) => self::info::exec(m),
29+
("graph", Some(m)) => self::graph::exec(m),
2730
("pset", Some(m)) => self::pset::exec(m),
2831
("sighash", Some(m)) => self::sighash::exec(m),
2932
(_, _) => unreachable!("clap prints help"),

0 commit comments

Comments
 (0)