#![cfg_attr(nightly, feature(doc_cfg))]
use std::collections::HashMap;
use std::error::Error as StdError;
use std::fs;
use std::io::Write as _;
use std::path::{Path, PathBuf};
use move_syn::sanitize_for_tokenizer;
use move_syn::unsynn::{IParse as _, Ident, Span, ToTokens as _, TokenStream};
use moverox_codegen::ModuleGen as _;
#[cfg(test)]
mod tests;
const MOVE_FILE_EXT: &str = "move";
type Result<T, E = Box<dyn StdError + Send + Sync + 'static>> = ::std::result::Result<T, E>;
pub fn move_package(pkg_path: impl AsRef<Path>, name: &str) -> Builder<'_> {
Builder {
pkg_path: pkg_path.as_ref().to_owned(),
name,
with_implicit_iota_imports: false,
with_implicit_sui_imports: false,
emit_rerun_if_changed: std::env::var_os("CARGO").is_some(),
out_dir: None,
moverox_path: "::moverox".to_token_stream(),
address_map: Default::default(),
published_at: None,
}
}
pub struct Builder<'a> {
pkg_path: PathBuf,
name: &'a str,
with_implicit_iota_imports: bool,
with_implicit_sui_imports: bool,
emit_rerun_if_changed: bool,
out_dir: Option<PathBuf>,
moverox_path: TokenStream,
address_map: HashMap<Ident, TokenStream>,
published_at: Option<&'a str>,
}
impl<'a> Builder<'a> {
pub const fn with_implicit_iota_imports(mut self) -> Self {
self.with_implicit_iota_imports = true;
self
}
pub const fn with_implicit_sui_imports(mut self) -> Self {
self.with_implicit_sui_imports = true;
self
}
pub fn moverox_path(mut self, rust_path: &str) -> Self {
self.moverox_path = rust_path.to_token_stream();
self
}
pub fn map_address(mut self, named_address: &str, rust_path: &str) -> Self {
self.address_map.insert(
Ident::new(named_address, Span::call_site()),
rust_path.to_token_stream(),
);
self
}
pub const fn published_at(mut self, hex_address: &'a str) -> Self {
self.published_at = Some(hex_address);
self
}
pub const fn emit_rerun_if_changed(mut self, enable: bool) -> Self {
self.emit_rerun_if_changed = enable;
self
}
pub fn out_dir(mut self, path: impl Into<PathBuf>) -> Self {
self.out_dir = Some(path.into());
self
}
pub fn build(self) -> Result<()> {
let move_files = self.collect_move_files()?;
let modules = self.parse_files(&move_files)?;
let rust_code = self.generate_rust_str(&modules)?;
let target = self
.out_dir
.map_or_else(default_out_dir, Ok)?
.join(format!("{}.rs", self.name));
let mut file = fs::File::create(&target)?;
file.write_all(b"// This file is @generated by moverox-build.\n")?;
file.write_all(rust_code.as_bytes())?;
Ok(())
}
fn collect_move_files(&self) -> Result<Vec<PathBuf>> {
let move_sources = self.pkg_path.join("sources").canonicalize()?;
let mut move_files = vec![];
visit_move_files(&move_sources, &mut |path| {
if self.emit_rerun_if_changed {
println!("cargo:rerun-if-changed={}", path.display());
}
move_files.push(path.to_owned());
})?;
Ok(move_files)
}
fn parse_files(&self, move_files: &[PathBuf]) -> Result<Vec<move_syn::Module>> {
let mut move_modules = Vec::with_capacity(move_files.len());
for path in move_files {
let contents = sanitize_for_tokenizer(&fs::read_to_string(path)?);
let parsed_file: move_syn::File = contents
.into_token_iter()
.parse_all()
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
for mut module in parsed_file.into_modules() {
if self.with_implicit_iota_imports {
module.with_implicit_iota_imports();
}
if self.with_implicit_sui_imports {
module.with_implicit_sui_imports();
}
module.fully_qualify_datatype_field_types();
move_modules.push(module);
}
}
Ok(move_modules)
}
fn generate_rust_str(&self, move_modules: &[move_syn::Module]) -> Result<String> {
let mut address_map = self.address_map.clone();
for module in move_modules {
address_map.insert(module.named_address.clone(), "super".to_token_stream());
}
let package_address = self
.published_at
.map(move_syn::unsynn::LiteralString::from_str);
let mut generated_code = String::new();
for module in move_modules {
if !module.items().any(|item| item.kind.is_datatype()) {
continue;
}
let rust_code = module
.to_rust(&self.moverox_path, package_address.as_ref(), &address_map)
.map_err(|err| format!("module {}: {err}", module.ident))?
.to_string();
generated_code.push_str(&rust_code);
generated_code.push('\n');
}
Ok(generated_code)
}
}
fn visit_move_files(path: &Path, f: &mut impl FnMut(&Path)) -> std::io::Result<()> {
if path.is_file() && path.extension().is_some_and(|ext| ext == MOVE_FILE_EXT) {
f(path);
return Ok(());
}
if !path.is_dir() {
return Ok(());
}
for entry in fs::read_dir(path)? {
visit_move_files(&entry?.path(), f)?;
}
Ok(())
}
fn default_out_dir() -> Result<PathBuf> {
Ok(std::env::var_os("OUT_DIR")
.ok_or("OUT_DIR environment variable is not set")?
.into())
}