serde support
This commit is contained in:
parent
8c281fd174
commit
5b39035541
20 changed files with 415 additions and 185 deletions
|
@ -219,7 +219,7 @@ async function findUp(dir = process.cwd()): Promise<string | null> {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TypeDef {
|
interface TypeDef {
|
||||||
kind: 'fn' | 'struct' | 'impl' | 'enum'
|
kind: 'fn' | 'struct' | 'impl' | 'enum' | 'interface'
|
||||||
name: string
|
name: string
|
||||||
def: string
|
def: string
|
||||||
}
|
}
|
||||||
|
@ -243,34 +243,27 @@ async function processIntermediateTypeFile(source: string, target: string) {
|
||||||
const def = JSON.parse(line) as TypeDef
|
const def = JSON.parse(line) as TypeDef
|
||||||
|
|
||||||
switch (def.kind) {
|
switch (def.kind) {
|
||||||
case 'fn':
|
|
||||||
case 'enum':
|
|
||||||
dts += def.def + '\n'
|
|
||||||
break
|
|
||||||
case 'struct':
|
case 'struct':
|
||||||
classes.set(def.name, def.def)
|
classes.set(def.name, def.def)
|
||||||
break
|
break
|
||||||
case 'impl':
|
case 'impl':
|
||||||
impls.set(def.name, def.def)
|
impls.set(def.name, def.def)
|
||||||
|
break
|
||||||
|
case 'interface':
|
||||||
|
dts += `interface ${def.name} {\n${indentLines(def.def, 2)}\n}\n`
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
dts += def.def + '\n'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const [name, classDef] of classes.entries()) {
|
for (const [name, classDef] of classes.entries()) {
|
||||||
const implDef = impls.get(name)
|
const implDef = impls.get(name)
|
||||||
|
|
||||||
dts += `export class ${name} {
|
dts += `export class ${name} {\n${indentLines(classDef, 2)}`
|
||||||
${classDef
|
|
||||||
.split('\n')
|
|
||||||
.map((line) => line.trim())
|
|
||||||
.join('\n ')}`
|
|
||||||
|
|
||||||
if (implDef) {
|
if (implDef) {
|
||||||
dts +=
|
dts += `\n${indentLines(implDef, 2)}`
|
||||||
'\n ' +
|
|
||||||
implDef
|
|
||||||
.split('\n')
|
|
||||||
.map((line) => line.trim())
|
|
||||||
.join('\n ')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dts += '\n}\n'
|
dts += '\n}\n'
|
||||||
|
@ -279,3 +272,10 @@ async function processIntermediateTypeFile(source: string, target: string) {
|
||||||
await unlinkAsync(source)
|
await unlinkAsync(source)
|
||||||
await writeFileAsync(target, dts, 'utf8')
|
await writeFileAsync(target, dts, 'utf8')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function indentLines(input: string, spaces: number) {
|
||||||
|
return input
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => ''.padEnd(spaces, ' ') + line.trim())
|
||||||
|
.join('\n')
|
||||||
|
}
|
||||||
|
|
|
@ -51,7 +51,14 @@ pub struct NapiStruct {
|
||||||
pub vis: syn::Visibility,
|
pub vis: syn::Visibility,
|
||||||
pub fields: Vec<NapiStructField>,
|
pub fields: Vec<NapiStructField>,
|
||||||
pub is_tuple: bool,
|
pub is_tuple: bool,
|
||||||
pub gen_default_ctor: bool,
|
pub kind: NapiStructKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum NapiStructKind {
|
||||||
|
None,
|
||||||
|
Constructor,
|
||||||
|
Object,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
@ -5,7 +5,7 @@ use quote::ToTokens;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
codegen::{get_intermediate_ident, get_register_ident},
|
codegen::{get_intermediate_ident, get_register_ident},
|
||||||
BindgenResult, FnKind, NapiImpl, NapiStruct, TryToTokens,
|
BindgenResult, FnKind, NapiImpl, NapiStruct, NapiStructKind, TryToTokens,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate trait implementations for given Struct.
|
// Generate trait implementations for given Struct.
|
||||||
|
@ -53,7 +53,12 @@ fn gen_napi_value_map_impl(name: &Ident, to_napi_val_impl: TokenStream) -> Token
|
||||||
impl TryToTokens for NapiStruct {
|
impl TryToTokens for NapiStruct {
|
||||||
fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> {
|
fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> {
|
||||||
let napi_value_map_impl = self.gen_napi_value_map_impl();
|
let napi_value_map_impl = self.gen_napi_value_map_impl();
|
||||||
let class_helper_mod = self.gen_helper_mod();
|
|
||||||
|
let class_helper_mod = if self.kind == NapiStructKind::Object {
|
||||||
|
quote! {}
|
||||||
|
} else {
|
||||||
|
self.gen_helper_mod()
|
||||||
|
};
|
||||||
|
|
||||||
(quote! {
|
(quote! {
|
||||||
#napi_value_map_impl
|
#napi_value_map_impl
|
||||||
|
@ -72,7 +77,7 @@ impl NapiStruct {
|
||||||
Span::call_site(),
|
Span::call_site(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let ctor = if self.gen_default_ctor {
|
let ctor = if self.kind == NapiStructKind::Constructor {
|
||||||
self.gen_default_ctor()
|
self.gen_default_ctor()
|
||||||
} else {
|
} else {
|
||||||
quote! {}
|
quote! {}
|
||||||
|
@ -134,14 +139,20 @@ impl NapiStruct {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_napi_value_map_impl(&self) -> TokenStream {
|
fn gen_napi_value_map_impl(&self) -> TokenStream {
|
||||||
if !self.gen_default_ctor {
|
match self.kind {
|
||||||
return gen_napi_value_map_impl(&self.name, quote! {});
|
NapiStructKind::None => gen_napi_value_map_impl(&self.name, quote! {}),
|
||||||
|
NapiStructKind::Constructor => {
|
||||||
|
gen_napi_value_map_impl(&self.name, self.gen_to_napi_value_ctor_impl())
|
||||||
|
}
|
||||||
|
NapiStructKind::Object => self.gen_to_napi_value_obj_impl(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gen_to_napi_value_ctor_impl(&self) -> TokenStream {
|
||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
let js_name_str = &self.js_name;
|
let js_name_str = &self.js_name;
|
||||||
|
|
||||||
let mut fields_conversions = vec![];
|
let mut field_conversions = vec![];
|
||||||
let mut field_destructions = vec![];
|
let mut field_destructions = vec![];
|
||||||
|
|
||||||
for field in self.fields.iter() {
|
for field in self.fields.iter() {
|
||||||
|
@ -150,27 +161,25 @@ impl NapiStruct {
|
||||||
match &field.name {
|
match &field.name {
|
||||||
syn::Member::Named(ident) => {
|
syn::Member::Named(ident) => {
|
||||||
field_destructions.push(quote! { #ident });
|
field_destructions.push(quote! { #ident });
|
||||||
fields_conversions.push(quote! { <#ty as ToNapiValue>::to_napi_value(env, #ident)? });
|
field_conversions.push(quote! { <#ty as ToNapiValue>::to_napi_value(env, #ident)? });
|
||||||
}
|
}
|
||||||
syn::Member::Unnamed(i) => {
|
syn::Member::Unnamed(i) => {
|
||||||
field_destructions.push(quote! { arg#i });
|
field_destructions.push(quote! { arg#i });
|
||||||
fields_conversions.push(quote! { <#ty as ToNapiValue>::to_napi_value(env, arg#i)? });
|
field_conversions.push(quote! { <#ty as ToNapiValue>::to_napi_value(env, arg#i)? });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let destructed_fields = if self.is_tuple {
|
let destructed_fields = if self.is_tuple {
|
||||||
quote! {
|
quote! {
|
||||||
let Self (#(#field_destructions),*)
|
Self (#(#field_destructions),*)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
let Self {#(#field_destructions),*}
|
Self {#(#field_destructions),*}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
gen_napi_value_map_impl(
|
|
||||||
name,
|
|
||||||
quote! {
|
quote! {
|
||||||
impl ToNapiValue for #name {
|
impl ToNapiValue for #name {
|
||||||
unsafe fn to_napi_value(env: sys::napi_env, val: #name) -> Result<sys::napi_value> {
|
unsafe fn to_napi_value(env: sys::napi_env, val: #name) -> Result<sys::napi_value> {
|
||||||
|
@ -184,8 +193,8 @@ impl NapiStruct {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut result = std::ptr::null_mut();
|
let mut result = std::ptr::null_mut();
|
||||||
#destructed_fields = val;
|
let #destructed_fields = val;
|
||||||
let args = vec![#(#fields_conversions),*];
|
let args = vec![#(#field_conversions),*];
|
||||||
|
|
||||||
check_status!(
|
check_status!(
|
||||||
sys::napi_new_instance(env, ctor, args.len(), args.as_ptr(), &mut result),
|
sys::napi_new_instance(env, ctor, args.len(), args.as_ptr(), &mut result),
|
||||||
|
@ -199,8 +208,77 @@ impl NapiStruct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
|
fn gen_to_napi_value_obj_impl(&self) -> TokenStream {
|
||||||
|
let name = &self.name;
|
||||||
|
let name_str = self.name.to_string();
|
||||||
|
|
||||||
|
let mut obj_field_setters = vec![];
|
||||||
|
let mut obj_field_getters = vec![];
|
||||||
|
let mut field_destructions = vec![];
|
||||||
|
|
||||||
|
for field in self.fields.iter() {
|
||||||
|
let field_js_name = &field.js_name;
|
||||||
|
let ty = &field.ty;
|
||||||
|
|
||||||
|
match &field.name {
|
||||||
|
syn::Member::Named(ident) => {
|
||||||
|
field_destructions.push(quote! { #ident });
|
||||||
|
obj_field_setters.push(quote! { obj.set(#field_js_name, #ident)?; });
|
||||||
|
obj_field_getters.push(quote! { let #ident: #ty = obj.get(#field_js_name)?.expect(&format!("Field {} should exist", #field_js_name)); });
|
||||||
|
}
|
||||||
|
syn::Member::Unnamed(i) => {
|
||||||
|
field_destructions.push(quote! { arg#i });
|
||||||
|
obj_field_setters.push(quote! { obj.set(#field_js_name, arg#1)?; });
|
||||||
|
obj_field_getters.push(quote! { let arg#i: #ty = obj.get(#field_js_name)?.expect(&format!("Field {} should exist", #field_js_name)); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let destructed_fields = if self.is_tuple {
|
||||||
|
quote! {
|
||||||
|
Self (#(#field_destructions),*)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
Self {#(#field_destructions),*}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl TypeName for #name {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
#name_str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToNapiValue for #name {
|
||||||
|
unsafe fn to_napi_value(env: sys::napi_env, val: #name) -> Result<sys::napi_value> {
|
||||||
|
let env_wrapper = Env::from(env);
|
||||||
|
let mut obj = env_wrapper.create_object()?;
|
||||||
|
|
||||||
|
let #destructed_fields = val;
|
||||||
|
#(#obj_field_setters)*
|
||||||
|
|
||||||
|
Object::to_napi_value(env, obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromNapiValue for #name {
|
||||||
|
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||||
|
let env_wrapper = Env::from(env);
|
||||||
|
let mut obj = Object::from_napi_value(env, napi_val)?;
|
||||||
|
|
||||||
|
#(#obj_field_getters)*
|
||||||
|
|
||||||
|
let val = #destructed_fields;
|
||||||
|
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_default_getters_setters(&self) -> Vec<TokenStream> {
|
fn gen_default_getters_setters(&self) -> Vec<TokenStream> {
|
||||||
|
@ -273,7 +351,7 @@ impl NapiStruct {
|
||||||
let js_name = &self.js_name;
|
let js_name = &self.js_name;
|
||||||
let mut props = vec![];
|
let mut props = vec![];
|
||||||
|
|
||||||
if self.gen_default_ctor {
|
if self.kind == NapiStructKind::Constructor {
|
||||||
props.push(quote! { Property::new("constructor").unwrap().with_ctor(constructor) });
|
props.push(quote! { Property::new("constructor").unwrap().with_ctor(constructor) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,6 @@ mod r#struct;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use quote::ToTokens;
|
|
||||||
use regex::Regex;
|
|
||||||
use syn::Type;
|
use syn::Type;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -29,22 +27,66 @@ pub trait ToTypeDef {
|
||||||
fn to_type_def(&self) -> TypeDef;
|
fn to_type_def(&self) -> TypeDef;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static TYPE_REGEXES: Lazy<HashMap<&'static str, Regex>> = Lazy::new(|| {
|
static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
|
||||||
let mut map = HashMap::default();
|
let mut map = HashMap::default();
|
||||||
map.extend([
|
map.extend([
|
||||||
("Vec", Regex::new(r"^Vec < (.*) >$").unwrap()),
|
("()", "undefined"),
|
||||||
("Option", Regex::new(r"^Option < (.*) >").unwrap()),
|
("i8", "number"),
|
||||||
("Result", Regex::new(r"^Result < (.*) >").unwrap()),
|
("i16", "number"),
|
||||||
("HashMap", Regex::new(r"HashMap < (.*), (.*) >").unwrap()),
|
("i32", "number"),
|
||||||
|
("i64", "number"),
|
||||||
|
("u8", "number"),
|
||||||
|
("u16", "number"),
|
||||||
|
("u32", "number"),
|
||||||
|
("u64", "BigInt"),
|
||||||
|
("u128", "BigInt"),
|
||||||
|
("i128", "BigInt"),
|
||||||
|
("usize", "BigInt"),
|
||||||
|
("isize", "BigInt"),
|
||||||
|
("BigInt", "BigInt"),
|
||||||
|
("bool", "boolean"),
|
||||||
|
("String", "string"),
|
||||||
|
("str", "string"),
|
||||||
|
("Latin1String", "string"),
|
||||||
|
("Utf16String", "string"),
|
||||||
|
("char", "string"),
|
||||||
|
("Object", "object"),
|
||||||
|
("Value", "any"),
|
||||||
|
("Map", "Record<string, any>"),
|
||||||
|
("HashMap", "Record<{}, {}>"),
|
||||||
|
("Vec", "Array<{}>"),
|
||||||
|
("Option", "{} | null"),
|
||||||
|
("Result", "Error | {}"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
map
|
map
|
||||||
});
|
});
|
||||||
|
|
||||||
pub fn ty_to_ts_type(ty: &Type, omit_top_level_result: bool) -> String {
|
fn fill_ty(template: &str, args: Vec<String>) -> String {
|
||||||
|
let matches = template.match_indices("{}").collect::<Vec<_>>();
|
||||||
|
if args.len() != matches.len() {
|
||||||
|
return String::from("any");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ret = String::from("");
|
||||||
|
let mut prev = 0;
|
||||||
|
matches.into_iter().zip(args).for_each(|((index, _), arg)| {
|
||||||
|
ret.push_str(&template[prev..index]);
|
||||||
|
ret.push_str(&arg);
|
||||||
|
prev = index + 2;
|
||||||
|
});
|
||||||
|
|
||||||
|
ret.push_str(&template[prev..]);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool) -> String {
|
||||||
match ty {
|
match ty {
|
||||||
Type::Reference(r) => ty_to_ts_type(&r.elem, omit_top_level_result),
|
Type::Reference(r) => ty_to_ts_type(&r.elem, is_return_ty),
|
||||||
Type::Tuple(tuple) => {
|
Type::Tuple(tuple) => {
|
||||||
|
if tuple.elems.is_empty() {
|
||||||
|
"undefined".to_owned()
|
||||||
|
} else {
|
||||||
format!(
|
format!(
|
||||||
"[{}]",
|
"[{}]",
|
||||||
tuple
|
tuple
|
||||||
|
@ -55,58 +97,42 @@ pub fn ty_to_ts_type(ty: &Type, omit_top_level_result: bool) -> String {
|
||||||
.join(", ")
|
.join(", ")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Type::Path(syn::TypePath { qself: None, path }) => str_to_ts_type(
|
}
|
||||||
path.to_token_stream().to_string().as_str(),
|
Type::Path(syn::TypePath { qself: None, path }) => {
|
||||||
omit_top_level_result,
|
let mut ts_ty = None;
|
||||||
),
|
|
||||||
|
if let Some(syn::PathSegment { ident, arguments }) = path.segments.last() {
|
||||||
|
let rust_ty = ident.to_string();
|
||||||
|
let args = if let syn::PathArguments::AngleBracketed(arguments) = arguments {
|
||||||
|
arguments
|
||||||
|
.args
|
||||||
|
.iter()
|
||||||
|
.filter_map(|arg| match arg {
|
||||||
|
syn::GenericArgument::Type(generic_ty) => Some(ty_to_ts_type(generic_ty, false)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
if rust_ty == "Result" && is_return_ty {
|
||||||
|
ts_ty = Some(args.first().unwrap().to_owned());
|
||||||
|
} else if let Some(&known_ty) = KNOWN_TYPES.get(rust_ty.as_str()) {
|
||||||
|
if known_ty.contains("{}") {
|
||||||
|
ts_ty = Some(fill_ty(known_ty, args));
|
||||||
|
} else {
|
||||||
|
ts_ty = Some(known_ty.to_owned());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// there should be runtime registered type in else
|
||||||
|
ts_ty = Some(rust_ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ts_ty.unwrap_or_else(|| "any".to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
_ => "any".to_owned(),
|
_ => "any".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn str_to_ts_type(ty: &str, omit_top_level_result: bool) -> String {
|
|
||||||
match ty {
|
|
||||||
"()" => "undefined".to_owned(),
|
|
||||||
"i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" => "number".to_owned(),
|
|
||||||
"i128" | "isize" | "u64" | "u128" | "usize" => "BigInt".to_owned(),
|
|
||||||
"bool" => "boolean".to_owned(),
|
|
||||||
"String" | "str" | "char" => "string".to_owned(),
|
|
||||||
"Object" => "object".to_owned(),
|
|
||||||
// nothing but `& 'lifetime str` could ends with ` str`
|
|
||||||
s if s.ends_with(" str") => "string".to_owned(),
|
|
||||||
s if s.starts_with("Vec") && TYPE_REGEXES["Vec"].is_match(s) => {
|
|
||||||
let captures = TYPE_REGEXES["Vec"].captures(s).unwrap();
|
|
||||||
let inner = captures.get(1).unwrap().as_str();
|
|
||||||
|
|
||||||
format!("Array<{}>", str_to_ts_type(inner, false))
|
|
||||||
}
|
|
||||||
s if s.starts_with("Option") && TYPE_REGEXES["Option"].is_match(s) => {
|
|
||||||
let captures = TYPE_REGEXES["Option"].captures(s).unwrap();
|
|
||||||
let inner = captures.get(1).unwrap().as_str();
|
|
||||||
|
|
||||||
format!("{} | null", str_to_ts_type(inner, false))
|
|
||||||
}
|
|
||||||
s if s.starts_with("Result") && TYPE_REGEXES["Result"].is_match(s) => {
|
|
||||||
let captures = TYPE_REGEXES["Result"].captures(s).unwrap();
|
|
||||||
let inner = captures.get(1).unwrap().as_str();
|
|
||||||
|
|
||||||
if omit_top_level_result {
|
|
||||||
str_to_ts_type(inner, false)
|
|
||||||
} else {
|
|
||||||
format!("Error | {}", str_to_ts_type(inner, false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s if TYPE_REGEXES["HashMap"].is_match(s) => {
|
|
||||||
let captures = TYPE_REGEXES["HashMap"].captures(s).unwrap();
|
|
||||||
let key = captures.get(1).unwrap().as_str();
|
|
||||||
let val = captures.get(2).unwrap().as_str();
|
|
||||||
|
|
||||||
format!(
|
|
||||||
"Record<{}, {}>",
|
|
||||||
str_to_ts_type(key, false),
|
|
||||||
str_to_ts_type(val, false)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
s => s.to_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
use super::{ToTypeDef, TypeDef};
|
use super::{ToTypeDef, TypeDef};
|
||||||
use crate::{ty_to_ts_type, NapiImpl, NapiStruct};
|
use crate::{ty_to_ts_type, NapiImpl, NapiStruct, NapiStructKind};
|
||||||
|
|
||||||
impl ToTypeDef for NapiStruct {
|
impl ToTypeDef for NapiStruct {
|
||||||
fn to_type_def(&self) -> TypeDef {
|
fn to_type_def(&self) -> TypeDef {
|
||||||
TypeDef {
|
TypeDef {
|
||||||
kind: "struct".to_owned(),
|
kind: String::from(if self.kind == NapiStructKind::Object {
|
||||||
|
"interface"
|
||||||
|
} else {
|
||||||
|
"struct"
|
||||||
|
}),
|
||||||
name: self.js_name.to_owned(),
|
name: self.js_name.to_owned(),
|
||||||
def: self.gen_ts_class_fields(),
|
def: self.gen_ts_class(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +31,7 @@ impl ToTypeDef for NapiImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NapiStruct {
|
impl NapiStruct {
|
||||||
fn gen_ts_class_fields(&self) -> String {
|
fn gen_ts_class(&self) -> String {
|
||||||
let mut ctor_args = vec![];
|
let mut ctor_args = vec![];
|
||||||
let def = self
|
let def = self
|
||||||
.fields
|
.fields
|
||||||
|
@ -40,7 +44,7 @@ impl NapiStruct {
|
||||||
field_str.push_str("readonly ")
|
field_str.push_str("readonly ")
|
||||||
}
|
}
|
||||||
let arg = format!("{}: {}", &f.js_name, ty_to_ts_type(&f.ty, false));
|
let arg = format!("{}: {}", &f.js_name, ty_to_ts_type(&f.ty, false));
|
||||||
if self.gen_default_ctor {
|
if self.kind == NapiStructKind::Constructor {
|
||||||
ctor_args.push(arg.clone());
|
ctor_args.push(arg.clone());
|
||||||
}
|
}
|
||||||
field_str.push_str(&arg);
|
field_str.push_str(&arg);
|
||||||
|
@ -50,7 +54,7 @@ impl NapiStruct {
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\\n");
|
.join("\\n");
|
||||||
|
|
||||||
if self.gen_default_ctor {
|
if self.kind == NapiStructKind::Constructor {
|
||||||
format!("{}\\nconstructor({})", def, ctor_args.join(", "))
|
format!("{}\\nconstructor({})", def, ctor_args.join(", "))
|
||||||
} else {
|
} else {
|
||||||
def
|
def
|
||||||
|
|
|
@ -49,6 +49,7 @@ macro_rules! attrgen {
|
||||||
(readonly, Readonly(Span)),
|
(readonly, Readonly(Span)),
|
||||||
(skip, Skip(Span)),
|
(skip, Skip(Span)),
|
||||||
(strict, Strict(Span)),
|
(strict, Strict(Span)),
|
||||||
|
(object, Object(Span)),
|
||||||
|
|
||||||
// impl later
|
// impl later
|
||||||
// (inspectable, Inspectable(Span)),
|
// (inspectable, Inspectable(Span)),
|
||||||
|
|
|
@ -10,7 +10,7 @@ use attrs::{BindgenAttr, BindgenAttrs};
|
||||||
use convert_case::{Case, Casing};
|
use convert_case::{Case, Casing};
|
||||||
use napi_derive_backend::{
|
use napi_derive_backend::{
|
||||||
BindgenResult, CallbackArg, Diagnostic, FnKind, FnSelf, Napi, NapiEnum, NapiEnumVariant, NapiFn,
|
BindgenResult, CallbackArg, Diagnostic, FnKind, FnSelf, Napi, NapiEnum, NapiEnumVariant, NapiFn,
|
||||||
NapiFnArgKind, NapiImpl, NapiItem, NapiStruct, NapiStructField,
|
NapiFnArgKind, NapiImpl, NapiItem, NapiStruct, NapiStructField, NapiStructKind,
|
||||||
};
|
};
|
||||||
use proc_macro2::{Ident, TokenStream, TokenTree};
|
use proc_macro2::{Ident, TokenStream, TokenTree};
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
|
@ -665,16 +665,22 @@ impl ConvertToAST for syn::ItemStruct {
|
||||||
);
|
);
|
||||||
let mut fields = vec![];
|
let mut fields = vec![];
|
||||||
let mut is_tuple = false;
|
let mut is_tuple = false;
|
||||||
let gen_default_ctor = opts.constructor().is_some();
|
let struct_kind = if opts.constructor().is_some() {
|
||||||
|
NapiStructKind::Constructor
|
||||||
|
} else if opts.object().is_some() {
|
||||||
|
NapiStructKind::Object
|
||||||
|
} else {
|
||||||
|
NapiStructKind::None
|
||||||
|
};
|
||||||
|
|
||||||
for (i, field) in self.fields.iter_mut().enumerate() {
|
for (i, field) in self.fields.iter_mut().enumerate() {
|
||||||
match field.vis {
|
match field.vis {
|
||||||
syn::Visibility::Public(..) => {}
|
syn::Visibility::Public(..) => {}
|
||||||
_ => {
|
_ => {
|
||||||
if gen_default_ctor {
|
if struct_kind != NapiStructKind::None {
|
||||||
errors.push(err_span!(
|
errors.push(err_span!(
|
||||||
field,
|
field,
|
||||||
"#[napi] requires all struct fields to be public to mark struct as constructor\nthis field is not public."
|
"#[napi] requires all struct fields to be public to mark struct as constructor or object shape\nthis field is not public."
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
@ -693,7 +699,7 @@ impl ConvertToAST for syn::ItemStruct {
|
||||||
),
|
),
|
||||||
None => {
|
None => {
|
||||||
is_tuple = true;
|
is_tuple = true;
|
||||||
(i.to_string(), syn::Member::Unnamed(i.into()))
|
(format!("field{}", i), syn::Member::Unnamed(i.into()))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -719,7 +725,7 @@ impl ConvertToAST for syn::ItemStruct {
|
||||||
vis,
|
vis,
|
||||||
fields,
|
fields,
|
||||||
is_tuple,
|
is_tuple,
|
||||||
gen_default_ctor,
|
kind: struct_kind,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,6 @@ impl<const N: usize> CallbackInfo<N> {
|
||||||
|
|
||||||
pub fn construct<T>(&self, js_name: &str, obj: T) -> Result<sys::napi_value> {
|
pub fn construct<T>(&self, js_name: &str, obj: T) -> Result<sys::napi_value> {
|
||||||
let obj = Box::new(obj);
|
let obj = Box::new(obj);
|
||||||
let mut result = std::ptr::null_mut();
|
|
||||||
let this = self.this();
|
let this = self.this();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -67,7 +66,7 @@ impl<const N: usize> CallbackInfo<N> {
|
||||||
Box::into_raw(obj) as *mut std::ffi::c_void,
|
Box::into_raw(obj) as *mut std::ffi::c_void,
|
||||||
Some(raw_finalize_unchecked::<T>),
|
Some(raw_finalize_unchecked::<T>),
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
&mut result
|
&mut std::ptr::null_mut()
|
||||||
),
|
),
|
||||||
"Failed to initialize class `{}`",
|
"Failed to initialize class `{}`",
|
||||||
js_name,
|
js_name,
|
||||||
|
|
|
@ -8,6 +8,8 @@ mod map;
|
||||||
mod nil;
|
mod nil;
|
||||||
mod number;
|
mod number;
|
||||||
mod object;
|
mod object;
|
||||||
|
#[cfg(feature = "serde-json")]
|
||||||
|
mod serde;
|
||||||
mod string;
|
mod string;
|
||||||
|
|
||||||
pub use array::*;
|
pub use array::*;
|
||||||
|
|
|
@ -2,8 +2,8 @@ use crate::{bindgen_prelude::*, check_status, sys, type_of, ValueType};
|
||||||
use std::{ffi::CString, ptr};
|
use std::{ffi::CString, ptr};
|
||||||
|
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
env: sys::napi_env,
|
pub(crate) env: sys::napi_env,
|
||||||
inner: sys::napi_value,
|
pub(crate) inner: sys::napi_value,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Object {
|
impl Object {
|
||||||
|
|
95
crates/napi/src/bindgen_runtime/js_values/serde.rs
Normal file
95
crates/napi/src/bindgen_runtime/js_values/serde.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
|
use crate::{bindgen_runtime::Null, check_status, sys, type_of, Error, Result, Status, ValueType};
|
||||||
|
|
||||||
|
use super::{FromNapiValue, Object, ToNapiValue};
|
||||||
|
|
||||||
|
impl ToNapiValue for Value {
|
||||||
|
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
|
||||||
|
match val {
|
||||||
|
Value::Null => Null::to_napi_value(env, Null),
|
||||||
|
Value::Bool(b) => bool::to_napi_value(env, b),
|
||||||
|
Value::Number(n) => {
|
||||||
|
if n.is_i64() {
|
||||||
|
i64::to_napi_value(env, n.as_i64().unwrap())
|
||||||
|
} else if n.is_f64() {
|
||||||
|
f64::to_napi_value(env, n.as_f64().unwrap())
|
||||||
|
} else {
|
||||||
|
let n = n.as_u64().unwrap();
|
||||||
|
if n > u32::MAX as u64 {
|
||||||
|
todo!("impl BigInt")
|
||||||
|
} else {
|
||||||
|
u32::to_napi_value(env, n as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::String(s) => String::to_napi_value(env, s),
|
||||||
|
Value::Array(arr) => Vec::<Value>::to_napi_value(env, arr),
|
||||||
|
Value::Object(obj) => Map::to_napi_value(env, obj),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromNapiValue for Value {
|
||||||
|
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||||
|
let ty = type_of!(env, napi_val)?;
|
||||||
|
let val = match ty {
|
||||||
|
ValueType::Boolean => Value::Bool(bool::from_napi_value(env, napi_val)?),
|
||||||
|
ValueType::Number => {
|
||||||
|
return Err(Error::new(
|
||||||
|
Status::InvalidArg,
|
||||||
|
"Js Number is not be able to convert to rust.".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
ValueType::String => Value::String(String::from_napi_value(env, napi_val)?),
|
||||||
|
ValueType::Object => {
|
||||||
|
let mut is_arr = false;
|
||||||
|
check_status!(
|
||||||
|
sys::napi_is_array(env, napi_val, &mut is_arr),
|
||||||
|
"Failed to detect whether given js is an array"
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if is_arr {
|
||||||
|
Value::Array(Vec::<Value>::from_napi_value(env, napi_val)?)
|
||||||
|
} else {
|
||||||
|
Value::Object(Map::<String, Value>::from_napi_value(env, napi_val)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "napi6")]
|
||||||
|
ValueType::Bigint => todo!(),
|
||||||
|
_ => Value::Null,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToNapiValue for Map<String, Value> {
|
||||||
|
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
|
||||||
|
let mut obj = Object::new(env)?;
|
||||||
|
|
||||||
|
for (k, v) in val.into_iter() {
|
||||||
|
obj.set(k, v)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object::to_napi_value(env, obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromNapiValue for Map<String, Value> {
|
||||||
|
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||||
|
let obj = Object {
|
||||||
|
env,
|
||||||
|
inner: napi_val,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut map = Map::new();
|
||||||
|
for key in Object::keys(&obj)?.into_iter() {
|
||||||
|
if let Some(val) = obj.get(&key)? {
|
||||||
|
map.insert(key, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(map)
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,6 @@ Generated by [AVA](https://avajs.dev).
|
||||||
export function sumNums(nums: Array<number>): number␊
|
export function sumNums(nums: Array<number>): number␊
|
||||||
export function getCwd(callback: (arg0: string) => void): void␊
|
export function getCwd(callback: (arg0: string) => void): void␊
|
||||||
export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void␊
|
export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void␊
|
||||||
export function readPackageJson(): PackageJson␊
|
|
||||||
export enum Kind { Dog = 0, Cat = 1, Duck = 2 }␊
|
export enum Kind { Dog = 0, Cat = 1, Duck = 2 }␊
|
||||||
export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }␊
|
export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }␊
|
||||||
export function enumToI32(e: CustomNumEnum): number␊
|
export function enumToI32(e: CustomNumEnum): number␊
|
||||||
|
@ -23,10 +22,18 @@ Generated by [AVA](https://avajs.dev).
|
||||||
export function fibonacci(n: number): number␊
|
export function fibonacci(n: number): number␊
|
||||||
export function listObjKeys(obj: object): Array<string>␊
|
export function listObjKeys(obj: object): Array<string>␊
|
||||||
export function createObj(): object␊
|
export function createObj(): object␊
|
||||||
|
interface PackageJson {␊
|
||||||
|
name: string␊
|
||||||
|
version: string␊
|
||||||
|
dependencies: Record<string, any> | null␊
|
||||||
|
devDependencies: Record<string, any> | null␊
|
||||||
|
}␊
|
||||||
|
export function readPackageJson(): PackageJson␊
|
||||||
|
export function getPackageJsonName(packageJson: PackageJson): string␊
|
||||||
export function contains(source: string, target: string): boolean␊
|
export function contains(source: string, target: string): boolean␊
|
||||||
export function concatStr(mutS: string): string␊
|
export function concatStr(mutS: string): string␊
|
||||||
export function concatUtf16(s: Utf16String): Utf16String␊
|
export function concatUtf16(s: string): string␊
|
||||||
export function concatLatin1(s: Latin1String): string␊
|
export function concatLatin1(s: string): string␊
|
||||||
export class Animal {␊
|
export class Animal {␊
|
||||||
readonly kind: Kind␊
|
readonly kind: Kind␊
|
||||||
constructor(kind: Kind, name: string)␊
|
constructor(kind: Kind, name: string)␊
|
||||||
|
@ -35,11 +42,4 @@ Generated by [AVA](https://avajs.dev).
|
||||||
whoami(): string␊
|
whoami(): string␊
|
||||||
static getDogKind(): Kind␊
|
static getDogKind(): Kind␊
|
||||||
}␊
|
}␊
|
||||||
export class PackageJson {␊
|
|
||||||
name: string␊
|
|
||||||
version: string␊
|
|
||||||
dependencies: Record<string, string> | null␊
|
|
||||||
devDependencies: Record<string, string> | null␊
|
|
||||||
constructor(name: string, version: string, dependencies: Record<string, string> | null, devDependencies: Record<string, string> | null)␊
|
|
||||||
}␊
|
|
||||||
`
|
`
|
||||||
|
|
Binary file not shown.
|
@ -21,6 +21,7 @@ import {
|
||||||
readFile,
|
readFile,
|
||||||
throwError,
|
throwError,
|
||||||
readPackageJson,
|
readPackageJson,
|
||||||
|
getPackageJsonName,
|
||||||
} from '../'
|
} from '../'
|
||||||
|
|
||||||
test('number', (t) => {
|
test('number', (t) => {
|
||||||
|
@ -68,12 +69,6 @@ test('class', (t) => {
|
||||||
|
|
||||||
dog.name = '可乐'
|
dog.name = '可乐'
|
||||||
t.is(dog.name, '可乐')
|
t.is(dog.name, '可乐')
|
||||||
|
|
||||||
const packageJson = readPackageJson()
|
|
||||||
t.is(packageJson.name, 'napi-rs')
|
|
||||||
t.is(packageJson.version, '0.0.0')
|
|
||||||
t.is(packageJson.dependencies, null)
|
|
||||||
t.snapshot(Object.keys(packageJson.devDependencies!).sort())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('callback', (t) => {
|
test('callback', (t) => {
|
||||||
|
@ -107,3 +102,13 @@ test('Option', (t) => {
|
||||||
test('Result', (t) => {
|
test('Result', (t) => {
|
||||||
t.throws(() => throwError(), null, 'Manual Error')
|
t.throws(() => throwError(), null, 'Manual Error')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('serde-json', (t) => {
|
||||||
|
const packageJson = readPackageJson()
|
||||||
|
t.is(packageJson.name, 'napi-rs')
|
||||||
|
t.is(packageJson.version, '0.0.0')
|
||||||
|
t.is(packageJson.dependencies, null)
|
||||||
|
t.snapshot(Object.keys(packageJson.devDependencies!).sort())
|
||||||
|
|
||||||
|
t.is(getPackageJsonName(packageJson), 'napi-rs')
|
||||||
|
})
|
||||||
|
|
|
@ -4,7 +4,7 @@ The actual snapshot is saved in `values.spec.ts.snap`.
|
||||||
|
|
||||||
Generated by [AVA](https://avajs.dev).
|
Generated by [AVA](https://avajs.dev).
|
||||||
|
|
||||||
## class
|
## serde-json
|
||||||
|
|
||||||
> Snapshot 1
|
> Snapshot 1
|
||||||
|
|
||||||
|
|
Binary file not shown.
20
examples/napi/index.d.ts
vendored
20
examples/napi/index.d.ts
vendored
|
@ -3,7 +3,6 @@ export function getNums(): Array<number>
|
||||||
export function sumNums(nums: Array<number>): number
|
export function sumNums(nums: Array<number>): number
|
||||||
export function getCwd(callback: (arg0: string) => void): void
|
export function getCwd(callback: (arg0: string) => void): void
|
||||||
export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void
|
export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void
|
||||||
export function readPackageJson(): PackageJson
|
|
||||||
export enum Kind { Dog = 0, Cat = 1, Duck = 2 }
|
export enum Kind { Dog = 0, Cat = 1, Duck = 2 }
|
||||||
export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }
|
export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }
|
||||||
export function enumToI32(e: CustomNumEnum): number
|
export function enumToI32(e: CustomNumEnum): number
|
||||||
|
@ -13,10 +12,18 @@ export function add(a: number, b: number): number
|
||||||
export function fibonacci(n: number): number
|
export function fibonacci(n: number): number
|
||||||
export function listObjKeys(obj: object): Array<string>
|
export function listObjKeys(obj: object): Array<string>
|
||||||
export function createObj(): object
|
export function createObj(): object
|
||||||
|
interface PackageJson {
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
dependencies: Record<string, any> | null
|
||||||
|
devDependencies: Record<string, any> | null
|
||||||
|
}
|
||||||
|
export function readPackageJson(): PackageJson
|
||||||
|
export function getPackageJsonName(packageJson: PackageJson): string
|
||||||
export function contains(source: string, target: string): boolean
|
export function contains(source: string, target: string): boolean
|
||||||
export function concatStr(mutS: string): string
|
export function concatStr(mutS: string): string
|
||||||
export function concatUtf16(s: Utf16String): Utf16String
|
export function concatUtf16(s: string): string
|
||||||
export function concatLatin1(s: Latin1String): string
|
export function concatLatin1(s: string): string
|
||||||
export class Animal {
|
export class Animal {
|
||||||
readonly kind: Kind
|
readonly kind: Kind
|
||||||
constructor(kind: Kind, name: string)
|
constructor(kind: Kind, name: string)
|
||||||
|
@ -25,10 +32,3 @@ export class Animal {
|
||||||
whoami(): string
|
whoami(): string
|
||||||
static getDogKind(): Kind
|
static getDogKind(): Kind
|
||||||
}
|
}
|
||||||
export class PackageJson {
|
|
||||||
name: string
|
|
||||||
version: string
|
|
||||||
dependencies: Record<string, string> | null
|
|
||||||
devDependencies: Record<string, string> | null
|
|
||||||
constructor(name: string, version: string, dependencies: Record<string, string> | null, devDependencies: Record<string, string> | null)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use std::{collections::HashMap, fs};
|
|
||||||
|
|
||||||
use napi::bindgen_prelude::*;
|
use napi::bindgen_prelude::*;
|
||||||
|
|
||||||
use crate::r#enum::Kind;
|
use crate::r#enum::Kind;
|
||||||
|
@ -47,20 +45,3 @@ impl Animal {
|
||||||
Kind::Dog
|
Kind::Dog
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(constructor)]
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
struct PackageJson {
|
|
||||||
pub name: String,
|
|
||||||
pub version: String,
|
|
||||||
pub dependencies: Option<HashMap<String, String>>,
|
|
||||||
#[serde(rename = "devDependencies")]
|
|
||||||
pub dev_dependencies: Option<HashMap<String, String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi]
|
|
||||||
fn read_package_json() -> Result<PackageJson> {
|
|
||||||
let raw = fs::read_to_string("package.json")?;
|
|
||||||
let p: PackageJson = serde_json::from_str(&raw)?;
|
|
||||||
Ok(p)
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,4 +11,5 @@ mod error;
|
||||||
mod nullable;
|
mod nullable;
|
||||||
mod number;
|
mod number;
|
||||||
mod object;
|
mod object;
|
||||||
|
mod serde;
|
||||||
mod string;
|
mod string;
|
||||||
|
|
25
examples/napi/src/serde.rs
Normal file
25
examples/napi/src/serde.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use napi::bindgen_prelude::*;
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
struct PackageJson {
|
||||||
|
pub name: String,
|
||||||
|
pub version: String,
|
||||||
|
pub dependencies: Option<Map<String, Value>>,
|
||||||
|
#[serde(rename = "devDependencies")]
|
||||||
|
pub dev_dependencies: Option<Map<String, Value>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
fn read_package_json() -> Result<PackageJson> {
|
||||||
|
let raw = fs::read_to_string("package.json")?;
|
||||||
|
let p: PackageJson = serde_json::from_str(&raw)?;
|
||||||
|
Ok(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
fn get_package_json_name(package_json: PackageJson) -> String {
|
||||||
|
package_json.name
|
||||||
|
}
|
Loading…
Reference in a new issue