Skip to content

Commit

Permalink
Feat: add default export directory override (#195)
Browse files Browse the repository at this point in the history
* feat: default export path
* added docs
* document why __private mod is pub
* add default env dir test to ci
* add export_to to crate1 dependency for tests
  • Loading branch information
stegaBOB authored Feb 12, 2024
1 parent 660dedd commit 823abfd
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 7 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ jobs:
run: |
cargo t
tsc bindings/* --noEmit
- name: dependencies e2e test with default export env
working-directory: e2e/dependencies/consumer
run: |
TS_RS_EXPORT_DIR=custom-bindings cargo t
tsc custom-bindings/* --noEmit
e2e-workspace:
name: Run 'workspace' end-to-end test
runs-on: ubuntu-latest
Expand All @@ -27,6 +32,11 @@ jobs:
run: |
cargo t
tsc parent/bindings/* --noEmit
- name: workspace e2e with default export env
working-directory: e2e/workspace
run: |
TS_RS_EXPORT_DIR=custom-bindings cargo t
tsc parent/custom-bindings/* --noEmit
readme-up-to-date:
name: Check that README.md is up-to-date
Expand Down
3 changes: 2 additions & 1 deletion e2e/workspace/crate1/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use ts_rs::TS;

#[derive(TS)]
#[ts(export_to = "crate1/")]
pub struct Crate1 {
pub x: [[[i32; 128]; 128]; 128],
}
}
8 changes: 8 additions & 0 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,18 @@ impl DerivedTS {
}

fn into_impl(self, rust_ty: Ident, generics: Generics) -> TokenStream {
let mut get_export_to = quote! {};
let export_to = match &self.export_to {
Some(dirname) if dirname.ends_with('/') => {
format!("{}{}.ts", dirname, self.name)
}
Some(filename) => filename.clone(),
None => {
get_export_to = quote! {
fn get_export_to() -> Option<String> {
ts_rs::__private::get_export_to_path::<Self>()
}
};
format!("bindings/{}.ts", self.name)
}
};
Expand Down Expand Up @@ -95,6 +101,8 @@ impl DerivedTS {
quote! {
#impl_start {
const EXPORT_TO: Option<&'static str> = Some(#export_to);
#get_export_to

#docs

fn decl() -> String {
Expand Down
31 changes: 28 additions & 3 deletions ts-rs/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::{
fmt::Write,
path::{Component, Path, PathBuf},
sync::Mutex,
sync::OnceLock,
};

use thiserror::Error;
Expand Down Expand Up @@ -128,6 +129,28 @@ pub(crate) fn export_type_to<T: TS + ?Sized + 'static, P: AsRef<Path>>(
Ok(())
}

#[doc(hidden)]
pub mod __private {
use super::*;

const EXPORT_DIR_ENV_VAR: &str = "TS_RS_EXPORT_DIR";
fn provided_default_dir() -> Option<&'static str> {
static EXPORT_TO: OnceLock<Option<String>> = OnceLock::new();
EXPORT_TO.get_or_init(|| std::env::var(EXPORT_DIR_ENV_VAR).ok()).as_deref()
}

/// Returns the path to where `T` should be exported using the `TS_RS_EXPORT_DIR` environment variable.
///
/// This should only be used by the TS derive macro; the `get_export_to` trait method should not
/// be overridden if the `#[ts(export_to = ..)]` attribute exists.
pub fn get_export_to_path<T: TS + ?Sized>() -> Option<String> {
provided_default_dir().map_or_else(
|| T::EXPORT_TO.map(ToString::to_string),
|path| Some(format!("{path}/{}.ts", T::name())),
)
}
}

/// Returns the generated defintion for `T`.
pub(crate) fn export_type_to_string<T: TS + ?Sized + 'static>() -> Result<String, ExportError> {
let mut buffer = String::with_capacity(1024);
Expand All @@ -141,7 +164,8 @@ pub(crate) fn export_type_to_string<T: TS + ?Sized + 'static>() -> Result<String
fn output_path<T: TS + ?Sized>() -> Result<PathBuf, ExportError> {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| ManifestDirNotSet)?;
let manifest_dir = Path::new(&manifest_dir);
let path = PathBuf::from(T::EXPORT_TO.ok_or(CannotBeExported(std::any::type_name::<T>()))?);
let path =
PathBuf::from(T::get_export_to().ok_or(CannotBeExported(std::any::type_name::<T>()))?);
Ok(manifest_dir.join(path))
}

Expand All @@ -160,7 +184,8 @@ fn generate_decl<T: TS + ?Sized>(out: &mut String) {

/// Push an import statement for all dependencies of `T`
fn generate_imports<T: TS + ?Sized + 'static>(out: &mut String) -> Result<(), ExportError> {
let path = Path::new(T::EXPORT_TO.ok_or(CannotBeExported(std::any::type_name::<T>()))?);
let export_to = T::get_export_to().ok_or(CannotBeExported(std::any::type_name::<T>()))?;
let path = Path::new(&export_to);

let deps = T::dependencies();
let deduplicated_deps = deps
Expand All @@ -170,7 +195,7 @@ fn generate_imports<T: TS + ?Sized + 'static>(out: &mut String) -> Result<(), Ex
.collect::<BTreeMap<_, _>>();

for (_, dep) in deduplicated_deps {
let rel_path = import_path(path, Path::new(dep.exported_to));
let rel_path = import_path(path, Path::new(&dep.exported_to));
writeln!(
out,
"import type {{ {} }} from {:?};",
Expand Down
22 changes: 19 additions & 3 deletions ts-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ pub use ts_rs_macros::TS;
pub use crate::export::ExportError;
use crate::typelist::TypeList;

// Used in generated code. Not public API
#[doc(hidden)]
pub use crate::export::__private;

#[cfg(feature = "chrono-impl")]
mod chrono;
mod export;
Expand Down Expand Up @@ -199,10 +203,18 @@ pub mod typelist;
///
/// - `#[ts(export)]`:
/// Generates a test which will export the type, by default to `bindings/<name>.ts` when running
/// `cargo test`
/// `cargo test`. The default base directory can be overridden with the `TS_RS_EXPORT_DIR` environment variable.
/// Adding the variable to a project's [config.toml](https://doc.rust-lang.org/cargo/reference/config.html#env) can
/// make it easier to manage.
/// ```toml
/// # <project-root>/.cargo/config.toml
/// [env]
/// TS_RS_EXPORT_DIR = { value = "<OVERRIDE_DIR>", relative = true }
/// ```
///
/// - `#[ts(export_to = "..")]`:
/// Specifies where the type should be exported to. Defaults to `bindings/<name>.ts`.
/// The `export_to` attribute will also override the `TS_RS_EXPORT_DIR` environment variable.
/// If the provided path ends in a trailing `/`, it is interpreted as a directory.
/// Note that you need to add the `export` attribute as well, in order to generate a test which exports the type.
///
Expand Down Expand Up @@ -271,6 +283,10 @@ pub trait TS {
const EXPORT_TO: Option<&'static str> = None;
const DOCS: Option<&'static str> = None;

fn get_export_to() -> Option<String> {
Self::EXPORT_TO.map(ToString::to_string)
}

/// Declaration of this type, e.g. `interface User { user_id: number, ... }`.
/// This function will panic if the type has no declaration.
fn decl() -> String {
Expand Down Expand Up @@ -370,15 +386,15 @@ pub struct Dependency {
pub ts_name: String,
/// Path to where the type would be exported. By default a filename is derived from the types
/// name, which can be customized with `#[ts(export_to = "..")]`.
pub exported_to: &'static str,
pub exported_to: String,
}

impl Dependency {
/// Constructs a [`Dependency`] from the given type `T`.
/// If `T` is not exportable (meaning `T::EXPORT_TO` is `None`), this function will return
/// `None`
pub fn from_ty<T: TS + 'static + ?Sized>() -> Option<Self> {
let exported_to = T::EXPORT_TO?;
let exported_to = T::get_export_to()?;
Some(Dependency {
type_id: TypeId::of::<T>(),
ts_name: T::name(),
Expand Down

0 comments on commit 823abfd

Please sign in to comment.