diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index af8db31..fe09048 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: - node-version: [10.x, 12.x] + node-version: [12.x, 14.x] os: [ubuntu-latest, windows-latest, macos-latest] steps: diff --git a/README.md b/README.md index fe1da60..1a3ee14 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,7 @@ var example = require('bindings')('my-genepi-addon'); Functions not belonging to any class can be exported inside a named or an anonymous namespace. The C++ function gets exported to JavaScript with the same name using `GENEPI_FUNCTION`, or it can be renamed by adding a second argument (without quotation marks) using `NAMED_GENEPI_FUNCTION`. +Addind double underscore in the new name will result in adding the function in nested objects on the js side. If the C++ function is overloaded, `GENEPI_MULTIFUNCTION` macro must be used instead. See [overloaded functions](#overloaded-functions). @@ -189,6 +190,11 @@ void sayBye( const std::string& name ) std::cout << "Bye, " << name << std::endl; } +void sayByeAgain( const std::string& name ) +{ + std::cout << "Bye again, " << name << std::endl; +} + namespace foo { void sayNamespacedHello( const std::string& name ) @@ -203,6 +209,7 @@ namespace { GENEPI_FUNCTION( sayHello ); NAMED_GENEPI_FUNCTION( sayBye, sayGoodbye ); + NAMED_GENEPI_FUNCTION( sayByeAgain, say__Goodbye ); } namespace foo @@ -220,6 +227,7 @@ var functions = require('genepi-functions.node'); functions.sayHello('you'); // Output: Hello, you functions.sayGoodbye('you'); // Output: Bye, you +functions.say.Goodbye('you'); // Output: Bye again, you functions.sayNamespacedHello('you'); // Output: Hello, you ``` diff --git a/examples/functions/functions.cpp b/examples/functions/functions.cpp index b83862a..be5b73f 100644 --- a/examples/functions/functions.cpp +++ b/examples/functions/functions.cpp @@ -35,6 +35,11 @@ void sayBye( const std::string& name ) std::cout << "Bye, " << name << std::endl; } +void sayBye2( const std::string& name ) +{ + std::cout << "Bye2, " << name << std::endl; +} + void displayArray( std::vector< double > values ) { for( const auto i : values ) @@ -67,6 +72,7 @@ namespace { GENEPI_FUNCTION( sayHello ); NAMED_GENEPI_FUNCTION( sayBye, sayGoodbye ); + NAMED_GENEPI_FUNCTION( sayBye2, say__Goodbye ); GENEPI_FUNCTION( displayArray ); GENEPI_FUNCTION( displayArray2 ); } // namespace diff --git a/examples/functions/functions.js b/examples/functions/functions.js index a69cc07..98d7b4d 100644 --- a/examples/functions/functions.js +++ b/examples/functions/functions.js @@ -25,6 +25,7 @@ var functions = require('bindings')('genepi-functions'); functions.sayHello('you'); functions.sayGoodbye('you'); +functions.say.Goodbye('you'); functions.sayNamespacedHello('you'); const array = [1, 2, 3] diff --git a/include/genepi/bind_class.h b/include/genepi/bind_class.h index 4cdb774..93fe753 100644 --- a/include/genepi/bind_class.h +++ b/include/genepi/bind_class.h @@ -54,11 +54,6 @@ namespace genepi void initialize( Napi::Env& env, Napi::Object& target ) final { - if( is_initialized_ ) - { - return; - } - is_initialized_ = true; std::deque< MethodDefinition > methods; std::unordered_set< const BindClassBase* > classes; initialize_api( methods, classes ); diff --git a/include/genepi/bind_class_base.h b/include/genepi/bind_class_base.h index d655ddf..95d0381 100644 --- a/include/genepi/bind_class_base.h +++ b/include/genepi/bind_class_base.h @@ -122,7 +122,6 @@ namespace genepi } protected: - bool is_initialized_{ false }; std::string name_; std::map< unsigned int, std::vector< Callable > > constructors_; std::deque< MethodDefinition > static_methods_; diff --git a/include/genepi/function_definition.h b/include/genepi/function_definition.h index 5699b1a..8a30033 100644 --- a/include/genepi/function_definition.h +++ b/include/genepi/function_definition.h @@ -41,22 +41,63 @@ namespace genepi void initialize( Napi::Env& env, Napi::Object& exports ) { - if( is_initialized_ ) - { - return; - } - is_initialized_ = true; auto param = new genepi::SignatureParam; param->method_number = number(); - exports.Set( Napi::String::New( env, name() ), + export_path( name(), Napi::Function::New( env, signature()->caller(), "", static_cast< void* >( Napi::External< genepi::SignatureParam >::New( env, param ) - .Data() ) ) ); + .Data() ) ), + exports ); } private: - bool is_initialized_{ false }; + void export_path( + const std::string& path, Napi::Value value, Napi::Object obj ) + { + auto last_object = obj; + const auto tokens = split( path ); + for( auto i = 0; i != tokens.size() - 1; i++ ) + { + const auto& property = tokens[i]; + Napi::Value prop_value = last_object.Get( property ); + if( prop_value.IsObject() ) + { + last_object = prop_value.As< Napi::Object >(); + } + else if( prop_value.IsUndefined() ) + { + auto new_object = Napi::Object::New( obj.Env() ); + last_object.DefineProperty( Napi::PropertyDescriptor::Value( + property, new_object, napi_default_jsproperty ) ); + last_object = new_object; + } + else + { + throw Napi::Error::New( + obj.Env(), "Attempted to set property \"" + property + + "\" on a non-object" ); + } + } + last_object.DefineProperty( Napi::PropertyDescriptor::Value( + tokens.back(), value, napi_default_jsproperty ) ); + } + + std::vector< std::string > split( const std::string& s ) + { + std::vector< std::string > output; + std::string::size_type prev_pos = 0, pos = 0; + while( ( pos = s.find( "__", pos ) ) != std::string::npos ) + { + std::string substring( s.substr( prev_pos, pos - prev_pos ) ); + output.push_back( substring ); + pos += 2; + prev_pos = pos; + } + output.push_back( + s.substr( prev_pos, pos - prev_pos ) ); // Last word + return output; + } }; } // namespace genepi