Skip to content

Commit 51e2592

Browse files
committed
fix
1 parent 392f624 commit 51e2592

File tree

2 files changed

+68
-36
lines changed

2 files changed

+68
-36
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ jobs:
1919
components: rustfmt, clippy
2020
- name: Run formatting check
2121
run: cargo fmt --check
22+
- name: Install solc
23+
run: sudo apt-get update && sudo apt-get install -y solc
2224
- name: Run clippy
2325
run: cargo clippy --workspace --all-targets --all-features -- -D warnings
2426
- name: Run tests

crates/cargo-pvm-contract/src/main.rs

Lines changed: 66 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ use clap::{Parser, Subcommand, ValueEnum};
33
use include_dir::{Dir, include_dir};
44
use inquire::{Select, Text};
55
use log::debug;
6-
use std::{fs, path::PathBuf};
6+
use std::{
7+
fs,
8+
path::{Path, PathBuf},
9+
};
710

811
mod scaffold;
912

@@ -27,8 +30,8 @@ enum Commands {
2730
struct PvmContractArgs {
2831
#[arg(long, value_enum, requires = "non_interactive")]
2932
init_type: Option<InitType>,
30-
#[arg(long, value_enum, requires = "non_interactive")]
31-
example: Option<ExampleChoice>,
33+
#[arg(long, requires = "non_interactive")]
34+
example: Option<String>,
3235
#[arg(long, value_enum, requires = "non_interactive")]
3336
memory_model: Option<MemoryModel>,
3437
#[arg(long, requires = "non_interactive")]
@@ -73,31 +76,61 @@ impl std::fmt::Display for MemoryModel {
7376
}
7477
}
7578

76-
#[derive(Debug, Clone, Copy, PartialEq, ValueEnum)]
77-
enum ExampleChoice {
78-
MyToken,
79+
#[derive(Debug, Clone, PartialEq, Eq)]
80+
struct ExampleContract {
81+
name: String,
82+
filename: String,
7983
}
8084

81-
impl std::fmt::Display for ExampleChoice {
82-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83-
match self {
84-
ExampleChoice::MyToken => write!(f, "MyToken (ERC20-like token)"),
85+
impl ExampleContract {
86+
fn from_path(path: &Path) -> Option<Self> {
87+
if path.extension().and_then(|ext| ext.to_str()) != Some("sol") {
88+
return None;
8589
}
90+
91+
let filename = path.file_name()?.to_str()?.to_string();
92+
let name = path.file_stem()?.to_str()?.to_string();
93+
Some(Self { name, filename })
94+
}
95+
96+
fn matches(&self, query: &str) -> bool {
97+
let query = query.trim().to_ascii_lowercase();
98+
let name = self.name.to_ascii_lowercase();
99+
let filename = self.filename.to_ascii_lowercase();
100+
query == name || query == filename
86101
}
87102
}
88103

89-
impl ExampleChoice {
90-
fn sol_filename(&self) -> &'static str {
91-
match self {
92-
ExampleChoice::MyToken => "MyToken.sol",
93-
}
104+
impl std::fmt::Display for ExampleContract {
105+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106+
write!(f, "{}", self.name)
94107
}
108+
}
95109

96-
fn default_name(&self) -> &'static str {
97-
match self {
98-
ExampleChoice::MyToken => "MyToken",
99-
}
110+
fn load_examples() -> Result<Vec<ExampleContract>> {
111+
let examples_dir = TEMPLATES_DIR
112+
.get_dir("examples")
113+
.ok_or_else(|| anyhow::anyhow!("Examples directory not found in templates"))?;
114+
let mut examples: Vec<ExampleContract> = examples_dir
115+
.files()
116+
.filter_map(|file| ExampleContract::from_path(file.path()))
117+
.collect();
118+
119+
examples.sort_by(|left, right| left.name.cmp(&right.name));
120+
121+
if examples.is_empty() {
122+
anyhow::bail!("No example contracts found in templates/examples");
100123
}
124+
125+
Ok(examples)
126+
}
127+
128+
fn find_example(examples: &[ExampleContract], query: &str) -> Result<ExampleContract> {
129+
examples
130+
.iter()
131+
.find(|example| example.matches(query))
132+
.cloned()
133+
.ok_or_else(|| anyhow::anyhow!("Unknown example: {query}"))
101134
}
102135

103136
fn main() -> Result<()> {
@@ -152,8 +185,7 @@ fn init_command_interactive(builder_path: Option<&std::path::Path>) -> Result<()
152185
scaffold::init_blank_contract(&contract_name, builder_path)
153186
}
154187
InitType::Example => {
155-
// Prompt for example choice
156-
let examples = vec![ExampleChoice::MyToken];
188+
let examples = load_examples()?;
157189
let example = Select::new("Select an example:", examples)
158190
.prompt()
159191
.context("Failed to get example choice")?;
@@ -166,7 +198,7 @@ fn init_command_interactive(builder_path: Option<&std::path::Path>) -> Result<()
166198

167199
// Ask for name with example name as default
168200
let contract_name = Text::new("What is your contract name?")
169-
.with_default(example.default_name())
201+
.with_default(&example.name)
170202
.with_help_message("This will be the name of the project directory")
171203
.prompt()
172204
.context("Failed to get contract name")?;
@@ -178,11 +210,10 @@ fn init_command_interactive(builder_path: Option<&std::path::Path>) -> Result<()
178210
check_dir_exists(&contract_name)?;
179211
debug!(
180212
"Initializing from example: {} with memory model: {:?}",
181-
example.sol_filename(),
182-
memory_model
213+
example.filename, memory_model
183214
);
184215

185-
init_from_example(example, &contract_name, memory_model, builder_path)
216+
init_from_example(&example, &contract_name, memory_model, builder_path)
186217
}
187218
InitType::SolidityFile => {
188219
// Prompt for .sol file path
@@ -257,15 +288,15 @@ fn init_command_non_interactive(
257288
scaffold::init_blank_contract(&contract_name, builder_path)
258289
}
259290
InitType::Example => {
260-
let example = args.example.ok_or_else(|| {
291+
let examples = load_examples()?;
292+
let example_name = args.example.ok_or_else(|| {
261293
anyhow::anyhow!("--example is required for example initialization")
262294
})?;
295+
let example = find_example(&examples, &example_name)?;
263296
let memory_model = args.memory_model.ok_or_else(|| {
264297
anyhow::anyhow!("--memory-model is required for example initialization")
265298
})?;
266-
let contract_name = args
267-
.name
268-
.unwrap_or_else(|| example.default_name().to_string());
299+
let contract_name = args.name.unwrap_or_else(|| example.name.clone());
269300

270301
if contract_name.is_empty() {
271302
anyhow::bail!("Contract name cannot be empty");
@@ -274,11 +305,10 @@ fn init_command_non_interactive(
274305
check_dir_exists(&contract_name)?;
275306
debug!(
276307
"Initializing from example: {} with memory model: {:?}",
277-
example.sol_filename(),
278-
memory_model
308+
example.filename, memory_model
279309
);
280310

281-
init_from_example(example, &contract_name, memory_model, builder_path)
311+
init_from_example(&example, &contract_name, memory_model, builder_path)
282312
}
283313
InitType::SolidityFile => {
284314
let sol_path = args.sol_file.ok_or_else(|| {
@@ -321,13 +351,13 @@ fn init_command_non_interactive(
321351
}
322352

323353
fn init_from_example(
324-
example: ExampleChoice,
354+
example: &ExampleContract,
325355
contract_name: &str,
326356
memory_model: MemoryModel,
327357
builder_path: Option<&std::path::Path>,
328358
) -> Result<()> {
329359
// Get the embedded example .sol file
330-
let example_path = format!("examples/{}", example.sol_filename());
360+
let example_path = format!("examples/{}", example.filename);
331361
let example_file = TEMPLATES_DIR
332362
.get_file(&example_path)
333363
.ok_or_else(|| anyhow::anyhow!("Example file not found: {}", example_path))?;
@@ -340,9 +370,9 @@ fn init_from_example(
340370
.as_nanos();
341371
let example_temp_dir = temp_dir.join(format!("cargo-pvm-contract-{timestamp}"));
342372
fs::create_dir_all(&example_temp_dir).with_context(|| {
343-
format!("Failed to create temporary directory for example: {example_temp_dir:?}",)
373+
format!("Failed to create temporary directory for example: {example_temp_dir:?}")
344374
})?;
345-
let temp_sol_path = example_temp_dir.join(example.sol_filename());
375+
let temp_sol_path = example_temp_dir.join(example.filename.as_str());
346376
fs::write(&temp_sol_path, example_file.contents())
347377
.with_context(|| format!("Failed to write temporary .sol file: {:?}", temp_sol_path))?;
348378

0 commit comments

Comments
 (0)