diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ea91b80..7298196 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ permissions: contents: write jobs: - build: + release: runs-on: ubuntu-latest timeout-minutes: 10 steps: diff --git a/e2e/mds/gencoder.yaml b/e2e/mds/gencoder.yaml index d9d4a8b..4e6165d 100644 --- a/e2e/mds/gencoder.yaml +++ b/e2e/mds/gencoder.yaml @@ -7,21 +7,21 @@ databases: dsn: 'mysql://root:root@localhost:3306/testdb' schema: testdb properties: - package: defaultpackage + entityPkg: com.example.entity + mapperPkg: com.example.mapper + dynamicSQLPkg: com.example.mapper tables: - name: 'user' - properties: - package: mysqlpackage ignoreColumns: - deleted_at - name: postgres dsn: 'postgres://root:root@localhost:5432/testdb?sslmode=disable' schema: public properties: - package: defaultpackage + entityPkg: com.example.pg.entity + mapperPkg: com.example.pg.mapper + dynamicSQLPkg: com.example.pg.mapper tables: - name: 'user' - properties: - package: postgrespackage ignoreColumns: - deleted_at diff --git a/e2e/mds/templates/dynamic_support.java.hbs b/e2e/mds/templates/dynamic_support.java.hbs index 6f011c5..8790f48 100644 --- a/e2e/mds/templates/dynamic_support.java.hbs +++ b/e2e/mds/templates/dynamic_support.java.hbs @@ -1,7 +1,9 @@ /** - * @gencoder.generated: src/main/java/{{replaceAll properties.package '\.' '/'}}/{{pascalCase table.name}}DynamicSqlSupport.java + * @gencoder.generated: src/main/java/{{replaceAll properties.dynamicSQLPkg '\.' '/'}}/{{pascalCase table.name}}DynamicSqlSupport.java */ +package {{properties.dynamicSQLPkg}}; + import org.mybatis.dynamic.sql.SqlColumn; import org.mybatis.dynamic.sql.AliasableSqlTable; diff --git a/e2e/mds/templates/entity.java.hbs b/e2e/mds/templates/entity.java.hbs index 8283bfc..ed33246 100644 --- a/e2e/mds/templates/entity.java.hbs +++ b/e2e/mds/templates/entity.java.hbs @@ -1,8 +1,8 @@ /** - * @gencoder.generated: src/main/java/{{replaceAll properties.package '\.' '/'}}/{{pascalCase table.name}}.java + * @gencoder.generated: src/main/java/{{replaceAll properties.entityPkg '\.' '/'}}/{{pascalCase table.name}}.java */ -package {{properties.package}}; +package {{properties.entityPkg}}; /** * @gencoder.block.start: table diff --git a/e2e/mds/templates/java_type.partial.hbs b/e2e/mds/templates/java_type.partial.hbs deleted file mode 100644 index f1c0192..0000000 --- a/e2e/mds/templates/java_type.partial.hbs +++ /dev/null @@ -1,8 +0,0 @@ -{{~#if (match 'varchar.*|text' columnType)}}String -{{~else if (match 'bigint|int64' columnType)}}Long -{{~else if (match '.*int.*' columnType)}}Integer -{{~else if (match 'timestamp' columnType)}}java.util.Date -{{~else if (match 'enum.*' columnType)}}String -{{~else if (match 'decimal.*' columnType)}}java.math.BigDecimal -{{~else}}String -{{~/if}} \ No newline at end of file diff --git a/e2e/mds/templates/mapper.java.hbs b/e2e/mds/templates/mapper.java.hbs new file mode 100644 index 0000000..1919011 --- /dev/null +++ b/e2e/mds/templates/mapper.java.hbs @@ -0,0 +1,160 @@ +/** + * @gencoder.generated: src/main/java/{{replaceAll properties.mapperPkg '\.' '/'}}/{{pascalCase table.name}}Mapper.java + */ + +package {{properties.mapperPkg}}; + +import static {{properties.dynamicSQLPkg}}.{{pascalCase table.name}}DynamicSqlSupport.*; +import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo; + +import {{properties.entityPkg}}.{{pascalCase table.name}}; +import jakarta.annotation.Generated; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.apache.ibatis.annotations.InsertProvider; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Result; +import org.apache.ibatis.annotations.ResultMap; +import org.apache.ibatis.annotations.Results; +import org.apache.ibatis.annotations.SelectKey; +import org.apache.ibatis.annotations.SelectProvider; +import org.apache.ibatis.type.JdbcType; +import org.mybatis.dynamic.sql.BasicColumn; +import org.mybatis.dynamic.sql.delete.DeleteDSLCompleter; +import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider; +import org.mybatis.dynamic.sql.select.CountDSLCompleter; +import org.mybatis.dynamic.sql.select.SelectDSLCompleter; +import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; +import org.mybatis.dynamic.sql.update.UpdateDSL; +import org.mybatis.dynamic.sql.update.UpdateDSLCompleter; +import org.mybatis.dynamic.sql.update.UpdateModel; +import org.mybatis.dynamic.sql.util.SqlProviderAdapter; +import org.mybatis.dynamic.sql.util.mybatis3.CommonCountMapper; +import org.mybatis.dynamic.sql.util.mybatis3.CommonDeleteMapper; +import org.mybatis.dynamic.sql.util.mybatis3.CommonUpdateMapper; +import org.mybatis.dynamic.sql.util.mybatis3.MyBatis3Utils; + +public class {{pascalCase table.name}}Mapper extends CommonCountMapper, CommonDeleteMapper, CommonUpdateMapper { + + // @gencoder.block.start: mapper + + BasicColumn[] selectList = BasicColumn.columnList({{#each table.columns}}{{camelCase name}}{{#unless @last}}, {{/unless}}{{/each}}); + + @InsertProvider(type=SqlProviderAdapter.class, method="insert") + @SelectKey(statement="SELECT LAST_INSERT_ID()", keyProperty="row.id", before=false, resultType={{> 'id_type.partial.hbs' columns=table.columns}}.class) + int insert(InsertStatementProvider<{{pascalCase table.name}}> insertStatement); + + @SelectProvider(type=SqlProviderAdapter.class, method="select") + @Results(id="{{pascalCase table.name}}Result", value = { + {{#each table.columns}} + @Result(column="{{name}}", property="{{camelCase name}}"/**, jdbcType={{jdbcType}}*/{{#if isPrimaryKey}}, id=true{{/if}}){{#unless @last}},{{/unless}} + {{/each}} + }) + List<{{pascalCase table.name}}> selectMany(SelectStatementProvider selectStatement); + + @SelectProvider(type=SqlProviderAdapter.class, method="select") + @ResultMap("{{pascalCase table.name}}Result") + Optional<{{pascalCase table.name}}> selectOne(SelectStatementProvider selectStatement); + + default long count(CountDSLCompleter completer) { + return MyBatis3Utils.countFrom(this::count, {{camelCase table.name}}, completer); + } + + default int delete(DeleteDSLCompleter completer) { + return MyBatis3Utils.deleteFrom(this::delete, {{camelCase table.name}}, completer); + } + + default int deleteByPrimaryKey(Integer id_) { + return delete(c -> + c.where(id, isEqualTo(id_)) + ); + } + + default int insert({{pascalCase table.name}} row) { + return MyBatis3Utils.insert(this::insert, row, {{camelCase table.name}}, c -> c + {{#each table.columns}} + {{#if (ne name 'id')}} + .map({{camelCase name}}).toProperty("{{camelCase name}}") + {{/if}} + {{/each}} + ); + } + + default int insertSelective({{pascalCase table.name}} row) { + return MyBatis3Utils.insert(this::insert, row, user, c -> c + {{#each table.columns}} + {{#if (ne name 'id')}} + .map({{camelCase name}}).toPropertyWhenPresent("{{camelCase name}}") + {{/if}} + {{/each}} + ); + } + + default Optional<{{pascalCase table.name}}> selectOne(SelectDSLCompleter completer) { + return MyBatis3Utils.selectOne(this::selectOne, selectList, {{camelCase table.name}}, completer); + } + + default List<{{pascalCase table.name}}> select(SelectDSLCompleter completer) { + return MyBatis3Utils.selectList(this::selectMany, selectList, {{camelCase table.name}}, completer); + } + + default List<{{pascalCase table.name}}> selectDistinct(SelectDSLCompleter completer) { + return MyBatis3Utils.selectDistinct(this::selectMany, selectList, {{camelCase table.name}}, completer); + } + + default Optional<{{pascalCase table.name}}> selectByPrimaryKey(Integer id_) { + return selectOne(c -> + c.where(id, isEqualTo(id_)) + ); + } + + default int update(UpdateDSLCompleter completer) { + return MyBatis3Utils.update(this::update, {{camelCase table.name}}, completer); + } + + static UpdateDSL updateAllColumns({{pascalCase table.name}} row, UpdateDSL dsl) { + return dsl + {{#each table.columns}} + {{#if (ne name 'id')}} + .set({{camelCase name}}).equalTo(row::get{{upperFirst (camelCase name)}}){{#if @last}};{{/if}} + {{/if}} + {{/each}} + } + + static UpdateDSL updateSelectiveColumns({{pascalCase table.name}} row, UpdateDSL dsl) { + return dsl + {{#each table.columns}} + {{#if (ne name 'id')}} + .set({{camelCase name}}).equalToWhenPresent(row::get{{upperFirst (camelCase name)}}){{#if @last}};{{/if}} + {{/if}} + {{/each}} + } + + default int updateByPrimaryKey({{pascalCase table.name}} row) { + return update(c -> c + {{#each table.columns}} + {{#if (ne name 'id')}} + .set({{camelCase name}}).equalTo(row::get{{upperFirst (camelCase name)}}) + {{/if}} + {{/each}} + .where(id, isEqualTo(row::getId)) + ); + } + + default int updateByPrimaryKeySelective({{pascalCase table.name}} row) { + return update(c -> c + {{#each table.columns}} + {{#unless isPrimaryKey}} + .set({{camelCase name}}).equalToWhenPresent(row::get{{upperFirst (camelCase name)}}) + {{/unless}} + {{/each}} + .where({{> 'id_name.partial.hbs' columns=table.columns}}, isEqualTo(row::getId)) + ); + } + + List> generalSelect(SelectStatementProvider selectStatement); + + // @gencoder.block.end: mapper + +} diff --git a/e2e/mds/templates/partials/id_name.partial.hbs b/e2e/mds/templates/partials/id_name.partial.hbs new file mode 100644 index 0000000..d7af9e5 --- /dev/null +++ b/e2e/mds/templates/partials/id_name.partial.hbs @@ -0,0 +1,5 @@ +{{#each table.columns}} +{{~#if isPrimaryKey}} +{{name}} +{{~/if}} +{{/each}} \ No newline at end of file diff --git a/e2e/mds/templates/partials/id_type.partial.hbs b/e2e/mds/templates/partials/id_type.partial.hbs new file mode 100644 index 0000000..3909a51 --- /dev/null +++ b/e2e/mds/templates/partials/id_type.partial.hbs @@ -0,0 +1,5 @@ +{{#each columns}} +{{~#if isPrimaryKey}} +{{> 'java_type.partial.hbs' columnType=type}} +{{~/if}} +{{/each}} \ No newline at end of file diff --git a/e2e/mds/templates/partials/java_type.partial.hbs b/e2e/mds/templates/partials/java_type.partial.hbs new file mode 100644 index 0000000..dfddb7b --- /dev/null +++ b/e2e/mds/templates/partials/java_type.partial.hbs @@ -0,0 +1,16 @@ +{{~#if (match 'varchar\(\d+\)|char|tinytext|text|mediumtext|longtext' columnType)}}String +{{~else if (match 'bigint' columnType)}}Long +{{~else if (match 'int|integer|mediumint' columnType)}}Integer +{{~else if (match 'smallint' columnType)}}Short +{{~else if (match 'tinyint' columnType)}}Byte +{{~else if (match 'bit|bool|boolean' columnType)}}Boolean +{{~else if (match 'decimal' columnType)}}java.math.BigDecimal +{{~else if (match 'float' columnType)}}Double +{{~else if (match 'datetime' columnType)}}java.time.LocalDateTime +{{~else if (match 'date' columnType)}}java.time.LocalDate +{{~else if (match 'time' columnType)}}java.time.LocalTime +{{~else if (match 'timestamp' columnType)}}java.time.LocalDateTime +{{~else if (match 'varbinary' columnType)}}byte[] +{{~else if (match 'enum.*' columnType)}}String +{{~else}}Object +{{~/if}} \ No newline at end of file diff --git a/e2e/mds/templates/jdbc_type.partial.hbs b/e2e/mds/templates/partials/jdbc_type.partial.hbs similarity index 100% rename from e2e/mds/templates/jdbc_type.partial.hbs rename to e2e/mds/templates/partials/jdbc_type.partial.hbs diff --git a/go.mod b/go.mod index 3df25b4..668ee58 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/DanielLiu1123/gencoder go 1.23.1 require ( - github.com/dop251/goja v0.0.0-20240828124009-016eb7256539 + github.com/dop251/goja v0.0.0-20240919115326-6c7d1df7ff05 github.com/go-sql-driver/mysql v1.8.1 github.com/lib/pq v1.10.9 github.com/mattn/go-sqlite3 v1.14.23 diff --git a/go.sum b/go.sum index 6169e66..7482d59 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,8 @@ github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yA github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dop251/goja v0.0.0-20240828124009-016eb7256539 h1:YIxvsQAoCLGScK2c9ag+4sFCgiQFpMzywJG6dQZFu9k= github.com/dop251/goja v0.0.0-20240828124009-016eb7256539/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4= +github.com/dop251/goja v0.0.0-20240919115326-6c7d1df7ff05 h1:oK4+QcKsczZjHYTHD0JAdkvq5w74JEkG95J0XNBx/BI= +github.com/dop251/goja v0.0.0-20240919115326-6c7d1df7ff05/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4= github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q= github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= diff --git a/pkg/cmd/init/init.go b/pkg/cmd/init/init.go new file mode 100644 index 0000000..7c00de7 --- /dev/null +++ b/pkg/cmd/init/init.go @@ -0,0 +1,131 @@ +package init + +import ( + "github.com/DanielLiu1123/gencoder/pkg/model" + "github.com/spf13/cobra" + "log" + "os" +) + +type initOptions struct { +} + +func NewCmdInit(globalOptions *model.GlobalOptions) *cobra.Command { + + opt := &initOptions{} + + c := &cobra.Command{ + Use: "init", + Short: "Init basic configuration for gencoder", + PreRun: func(cmd *cobra.Command, args []string) { + }, + Run: func(cmd *cobra.Command, args []string) { + run(cmd, args, opt, globalOptions) + }, + } + + return c +} + +func run(_ *cobra.Command, _ []string, _ *initOptions, _ *model.GlobalOptions) { + + // init gencoder.yaml + gencoderYaml := `templatesDir: templates +databases: + - name: mysql + dsn: 'mysql://root:root@localhost:3306/testdb' + tables: + - name: 'user' + properties: + package: 'com.example' + ignoreColumns: + - deleted_at +` + + // init gencoder.yaml + if _, err := os.Stat("gencoder.yaml"); err != nil { + err := os.WriteFile("gencoder.yaml", []byte(gencoderYaml), 0644) + if err != nil { + log.Fatal(err) + } + } + + // init templates dir + if _, err := os.Stat("templates"); err != nil { + err = os.Mkdir("templates", 0755) + if err != nil { + log.Fatal(err) + } + } + + // init templates + entityJava := `/** + * @gencoder.generated: src/main/java/{{replaceAll properties.package '\.' '/'}}/{{pascalCase table.name}}.java + */ + +package {{properties.package}}; + +/** + * @gencoder.block.start: table + *

table: {{table.name}} + *

comment: {{table.comment}} + *

indexes: + {{#each table.indexes}} + *

{{name}}: ({{#each columns}}{{name}}{{#unless @last}}, {{/unless}}{{/each}}) + {{/each}} + */ +public record {{pascalCase table.name}} ( + {{#each table.columns}} + /** + * {{comment}} + */ + {{> 'java_type.partial.hbs' columnType=type}} {{camelCase name}}{{#unless @last}},{{/unless}} + {{/each}} + + // NOTE: you can't make changes in the block, it will be overwritten by generating again + + // @gencoder.block.end: table +) { + + // TIP: you can make changes outside the block, it will not be overwritten by generating again + public void hello() { + System.out.println("Hello, World!"); + } +} +` + + if _, err := os.Stat("templates/entity.java.hbs"); err != nil { + err = os.WriteFile("templates/entity.java.hbs", []byte(entityJava), 0644) + if err != nil { + log.Fatal(err) + } + } + + javaTypePartial := `{{~#if (match 'varchar\(\d+\)|char|tinytext|text|mediumtext|longtext' columnType)}}String +{{~else if (match 'bigint' columnType)}}Long +{{~else if (match 'int|integer|mediumint' columnType)}}Integer +{{~else if (match 'smallint' columnType)}}Short +{{~else if (match 'tinyint' columnType)}}Byte +{{~else if (match 'bit|bool|boolean' columnType)}}Boolean +{{~else if (match 'decimal' columnType)}}java.math.BigDecimal +{{~else if (match 'float' columnType)}}Double +{{~else if (match 'datetime' columnType)}}java.time.LocalDateTime +{{~else if (match 'date' columnType)}}java.time.LocalDate +{{~else if (match 'time' columnType)}}java.time.LocalTime +{{~else if (match 'timestamp' columnType)}}java.time.LocalDateTime +{{~else if (match 'varbinary' columnType)}}byte[] +{{~else if (match 'enum.*' columnType)}}String +{{~else}}Object +{{~/if}}` + + if _, err := os.Stat("templates/java_type.partial.hbs"); err != nil { + err = os.WriteFile("templates/java_type.partial.hbs", []byte(javaTypePartial), 0644) + if err != nil { + log.Fatal(err) + } + } + + log.Println("Init success! Please modify the gencoder.yaml and templates to fit your project needs.") + log.Println() + log.Println("Thank you for using gencoder!") +} diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index cc072bb..4b216d6 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -2,6 +2,7 @@ package cmd import ( "github.com/DanielLiu1123/gencoder/pkg/cmd/generate" + initCmd "github.com/DanielLiu1123/gencoder/pkg/cmd/init" "github.com/DanielLiu1123/gencoder/pkg/cmd/introspect" "github.com/DanielLiu1123/gencoder/pkg/model" "github.com/spf13/cobra" @@ -27,6 +28,7 @@ func NewCmdRoot(buildInfo *model.BuildInfo) *cobra.Command { c.AddCommand(generate.NewCmdGenerate(opt)) c.AddCommand(introspect.NewCmdIntrospect(opt)) + c.AddCommand(initCmd.NewCmdInit(opt)) return c } diff --git a/pkg/jsruntime/gen/helper.gen.go b/pkg/jsruntime/gen/helper.gen.go index e2691b8..9a193a4 100644 --- a/pkg/jsruntime/gen/helper.gen.go +++ b/pkg/jsruntime/gen/helper.gen.go @@ -2,63 +2,114 @@ package gen -const HelperJS = `// Add custom Handlebars helpers here +const HelperJS = ` +function isUndefinedOrNull(value) { + return value === undefined || value === null; +} -Handlebars.registerHelper('replaceAll', function(target, old, newV) { +function hasUndefinedOrNull(value) { + return Array.isArray(value) + ? value.some(e => isUndefinedOrNull(e)) + : isUndefinedOrNull(value); +} + +// Add custom Handlebars helpers below + +Handlebars.registerHelper('replaceAll', function (target, old, newV) { + if (hasUndefinedOrNull([target, old, newV])) { + return target; + } return target.replace(new RegExp(old, 'g'), newV); }); -Handlebars.registerHelper('match', function(pattern, target) { +Handlebars.registerHelper('match', function (pattern, target) { + if (hasUndefinedOrNull([pattern, target])) { + return false; + } return new RegExp(pattern).test(target); }); -Handlebars.registerHelper('ne', function(left, right) { +Handlebars.registerHelper('eq', function (left, right) { + return left === right; +}); + +Handlebars.registerHelper('ne', function (left, right) { return left !== right; }); -Handlebars.registerHelper('snakeCase', function(s) { +Handlebars.registerHelper('snakeCase', function (s) { + if (hasUndefinedOrNull(s)) { + return s; + } return s.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase(); }); -Handlebars.registerHelper('camelCase', function(s) { - return s.replace(/([-_][a-z])/ig, function($1) { +Handlebars.registerHelper('camelCase', function (s) { + if (hasUndefinedOrNull(s)) { + return s; + } + return s.replace(/([-_][a-z])/ig, function ($1) { return $1.toUpperCase() .replace('-', '') .replace('_', ''); }); }); -Handlebars.registerHelper('pascalCase', function(s) { - return s.replace(/(\w)(\w*)/g, function($0, $1, $2) { +Handlebars.registerHelper('pascalCase', function (s) { + if (hasUndefinedOrNull(s)) { + return s; + } + return s.replace(/(\w)(\w*)/g, function ($0, $1, $2) { return $1.toUpperCase() + $2.toLowerCase(); }); }); -Handlebars.registerHelper('upperFirst', function(s) { +Handlebars.registerHelper('upperFirst', function (s) { + if (hasUndefinedOrNull(s)) { + return s; + } return s.charAt(0).toUpperCase() + s.slice(1); }); -Handlebars.registerHelper('lowerFirst', function(s) { +Handlebars.registerHelper('lowerFirst', function (s) { + if (hasUndefinedOrNull(s)) { + return s; + } return s.charAt(0).toLowerCase() + s.slice(1); }); -Handlebars.registerHelper('uppercase', function(s) { +Handlebars.registerHelper('uppercase', function (s) { + if (hasUndefinedOrNull(s)) { + return s; + } return s.toUpperCase(); }); -Handlebars.registerHelper('lowercase', function(s) { +Handlebars.registerHelper('lowercase', function (s) { + if (hasUndefinedOrNull(s)) { + return s; + } return s.toLowerCase(); }); -Handlebars.registerHelper('trim', function(s) { +Handlebars.registerHelper('trim', function (s) { + if (hasUndefinedOrNull(s)) { + return s; + } return s.trim(); }); -Handlebars.registerHelper('removePrefix', function(s, prefix) { +Handlebars.registerHelper('removePrefix', function (s, prefix) { + if (hasUndefinedOrNull([s, prefix])) { + return s; + } return s.startsWith(prefix) ? s.slice(prefix.length) : s; }); -Handlebars.registerHelper('removeSuffix', function(s, suffix) { +Handlebars.registerHelper('removeSuffix', function (s, suffix) { + if (hasUndefinedOrNull([s, suffix])) { + return s; + } return s.endsWith(suffix) ? s.slice(0, -suffix.length) : s; }); ` diff --git a/pkg/jsruntime/helper.js b/pkg/jsruntime/helper.js index ff8bff4..ad8590a 100644 --- a/pkg/jsruntime/helper.js +++ b/pkg/jsruntime/helper.js @@ -1,59 +1,110 @@ -// Add custom Handlebars helpers here -Handlebars.registerHelper('replaceAll', function(target, old, newV) { +function isUndefinedOrNull(value) { + return value === undefined || value === null; +} + +function hasUndefinedOrNull(value) { + return Array.isArray(value) + ? value.some(e => isUndefinedOrNull(e)) + : isUndefinedOrNull(value); +} + +// Add custom Handlebars helpers below + +Handlebars.registerHelper('replaceAll', function (target, old, newV) { + if (hasUndefinedOrNull([target, old, newV])) { + return target; + } return target.replace(new RegExp(old, 'g'), newV); }); -Handlebars.registerHelper('match', function(pattern, target) { +Handlebars.registerHelper('match', function (pattern, target) { + if (hasUndefinedOrNull([pattern, target])) { + return false; + } return new RegExp(pattern).test(target); }); -Handlebars.registerHelper('ne', function(left, right) { +Handlebars.registerHelper('eq', function (left, right) { + return left === right; +}); + +Handlebars.registerHelper('ne', function (left, right) { return left !== right; }); -Handlebars.registerHelper('snakeCase', function(s) { +Handlebars.registerHelper('snakeCase', function (s) { + if (hasUndefinedOrNull(s)) { + return s; + } return s.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase(); }); -Handlebars.registerHelper('camelCase', function(s) { - return s.replace(/([-_][a-z])/ig, function($1) { +Handlebars.registerHelper('camelCase', function (s) { + if (hasUndefinedOrNull(s)) { + return s; + } + return s.replace(/([-_][a-z])/ig, function ($1) { return $1.toUpperCase() .replace('-', '') .replace('_', ''); }); }); -Handlebars.registerHelper('pascalCase', function(s) { - return s.replace(/(\w)(\w*)/g, function($0, $1, $2) { +Handlebars.registerHelper('pascalCase', function (s) { + if (hasUndefinedOrNull(s)) { + return s; + } + return s.replace(/(\w)(\w*)/g, function ($0, $1, $2) { return $1.toUpperCase() + $2.toLowerCase(); }); }); -Handlebars.registerHelper('upperFirst', function(s) { +Handlebars.registerHelper('upperFirst', function (s) { + if (hasUndefinedOrNull(s)) { + return s; + } return s.charAt(0).toUpperCase() + s.slice(1); }); -Handlebars.registerHelper('lowerFirst', function(s) { +Handlebars.registerHelper('lowerFirst', function (s) { + if (hasUndefinedOrNull(s)) { + return s; + } return s.charAt(0).toLowerCase() + s.slice(1); }); -Handlebars.registerHelper('uppercase', function(s) { +Handlebars.registerHelper('uppercase', function (s) { + if (hasUndefinedOrNull(s)) { + return s; + } return s.toUpperCase(); }); -Handlebars.registerHelper('lowercase', function(s) { +Handlebars.registerHelper('lowercase', function (s) { + if (hasUndefinedOrNull(s)) { + return s; + } return s.toLowerCase(); }); -Handlebars.registerHelper('trim', function(s) { +Handlebars.registerHelper('trim', function (s) { + if (hasUndefinedOrNull(s)) { + return s; + } return s.trim(); }); -Handlebars.registerHelper('removePrefix', function(s, prefix) { +Handlebars.registerHelper('removePrefix', function (s, prefix) { + if (hasUndefinedOrNull([s, prefix])) { + return s; + } return s.startsWith(prefix) ? s.slice(prefix.length) : s; }); -Handlebars.registerHelper('removeSuffix', function(s, suffix) { +Handlebars.registerHelper('removeSuffix', function (s, suffix) { + if (hasUndefinedOrNull([s, suffix])) { + return s; + } return s.endsWith(suffix) ? s.slice(0, -suffix.length) : s; });