github.com/influx6/npkg@v0.8.8/njs/njs.go (about)

     1  // Package njs exists to provide a simple javascript text based code generation, exposing
     2  // composable functions that can be used to create generated javascript code.
     3  //
     4  // Code is taken from https://github.com/mistermoe/js-code-generator.
     5  //
     6  package njs
     7  
     8  import (
     9  	"regexp"
    10  	"strings"
    11  
    12  	"github.com/influx6/npkg/nerror"
    13  )
    14  
    15  const (
    16  	numTabs         = 1
    17  	numSpacesPerTab = 4
    18  )
    19  
    20  var alphabets = []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
    21  	"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}
    22  
    23  // Gen represents a generated code with attached input data.
    24  type Gen struct {
    25  	Data interface{}
    26  	Code string
    27  }
    28  
    29  // WrapWithData will wrap provided code and attached
    30  // giving data object into it returning a Gen object.
    31  func WrapWithData(code string, data interface{}) Gen {
    32  	return Gen{Data: data, Code: code}
    33  }
    34  
    35  // Args returns a joined string containing all values as comma seperated
    36  // values within returned string.
    37  func Args(args ...string) string {
    38  	return strings.Join(args, ", ")
    39  }
    40  
    41  // Join joins collection of string with newlines.
    42  func Join(cols ...string) string {
    43  	return JoinWith("\n", cols...)
    44  }
    45  
    46  // JoinWith joins giving collection of strings with combiner.
    47  func JoinWith(combiner string, cols ...string) string {
    48  	return CleanCode(strings.Join(cols, combiner))
    49  }
    50  
    51  type Producer func() string
    52  type Action func(builder *strings.Builder) error
    53  type ActionProducer func(builder *strings.Builder, producers ...Producer)
    54  
    55  // ComposeProducer composes series of string producers into a single Action
    56  // to be applied to a giving string.Builder instance.
    57  func ComposeProducers(indent bool, producers ...Producer) Action {
    58  	return func(builder *strings.Builder) error {
    59  		var total = len(producers)
    60  		for index, producer := range producers {
    61  			if _, err := builder.WriteString(producer()); err != nil {
    62  				return nerror.WrapOnly(err)
    63  			}
    64  			if indent && index < total-1 {
    65  				builder.WriteString("\n")
    66  			}
    67  		}
    68  		return nil
    69  	}
    70  }
    71  
    72  // ComposeActions composes series of Actions into a single action
    73  // to be applied to a giving string.Builder instance.
    74  func ComposeActions(actions ...Action) Action {
    75  	return func(builder *strings.Builder) error {
    76  		for _, action := range actions {
    77  			if err := action(builder); err != nil {
    78  				return nerror.WrapOnly(err)
    79  			}
    80  		}
    81  		return nil
    82  	}
    83  }
    84  
    85  type Var struct {
    86  	Name  string
    87  	Value string
    88  }
    89  
    90  /*
    91    @param Object data {
    92      {string} name,
    93      {string} value (optional)
    94    }
    95  */
    96  func Variable(data Var) string {
    97  	var code = `var ` + data.Name + ``
    98  	if data.Value != "" {
    99  		return CleanCode(code + ` = ` + data.Value + `;`)
   100  	}
   101  	return CleanCode(code + `;`)
   102  }
   103  
   104  /*
   105    @param Object data {
   106      {string} name,
   107      {string} value
   108    }
   109  */
   110  func ReAssignVariable(data Var) string {
   111  	return CleanCode(`` + data.Name + ` = ` + data.Value + `;`)
   112  }
   113  
   114  /*
   115    @param Object data {
   116      {string} name,
   117      {string} value (Optional)
   118    }
   119  */
   120  func IncrementVariable(data Var) string {
   121  	var code string
   122  	if data.Value != "" {
   123  		code = `` + data.Name + ` += ` + data.Value + `;`
   124  	} else {
   125  		code = `` + data.Name + `++`
   126  	}
   127  	return CleanCode(code)
   128  }
   129  
   130  /*
   131    @param Object data {
   132      {string} name,
   133      {string} value (Optional)
   134    }
   135  */
   136  func DecrementVariable(data Var) string {
   137  	var code string
   138  	if data.Value != "" {
   139  		code = `` + data.Name + ` -= ` + data.Value + `;`
   140  	} else {
   141  		code = `` + data.Name + `--`
   142  	}
   143  	return CleanCode(code)
   144  }
   145  
   146  type Func struct {
   147  	Name   string
   148  	Args   string
   149  	Body   string
   150  	Async  bool
   151  	Static bool
   152  }
   153  
   154  /*
   155    @param Object data {
   156      {string} name,
   157      {array} args (Optional),
   158  	{bool} async
   159      {func} body
   160    }
   161    @returns {
   162      data: data,
   163      code: code
   164    }
   165  */
   166  func VarFunction(data Func) string {
   167  	var code = `var ` + data.Name + ` = `
   168  	if data.Async {
   169  		code += `async `
   170  	}
   171  	code += `function(` + data.Args + `) {` + "\n" +
   172  		`` + data.Body + "\n" + `};`
   173  	return Indent(code)
   174  }
   175  
   176  /*
   177    @param Object data {
   178      {string} name,
   179      {array} args (Optional),
   180  	{bool} async
   181      {func} body
   182    }
   183    @returns {
   184      data: data,
   185      code: code
   186    }
   187  */
   188  func Function(data Func) string {
   189  	var code string
   190  	if data.Static {
   191  		code += `static `
   192  	}
   193  
   194  	if data.Async {
   195  		code += `async `
   196  	}
   197  
   198  	if !data.Static {
   199  		code += `function `
   200  	}
   201  
   202  	code += data.Name + `(` + data.Args + `) {` + "\n" +
   203  		`` + data.Body + "\n" + `};`
   204  	return Indent(code)
   205  }
   206  
   207  func FunctionCall(data Func) string {
   208  	var code string
   209  	if data.Async {
   210  		code += `await `
   211  	}
   212  	code += data.Name + `(` + data.Args + `);`
   213  	return CleanCode(code)
   214  }
   215  
   216  /*
   217    @param Object data {
   218      {string} type
   219      {array} args
   220    }
   221  
   222    @returns Object {
   223      {string} code
   224    }
   225  */
   226  func NewInstance(data struct {
   227  	Type string
   228  	Args string
   229  }) string {
   230  	var code = `new ` + data.Type
   231  	code += `(` + data.Args + `);`
   232  	return CleanCode(code)
   233  }
   234  
   235  func JSONParse(args string) string {
   236  	var code = `JSON.parse(` + args + `);`
   237  	return Indent(code)
   238  }
   239  
   240  func JSONStringify(args string) string {
   241  	var code = `JSON.stringify(` + args + `);`
   242  	return Indent(code)
   243  }
   244  
   245  func ChainObject(objName string) string {
   246  	return objName + `.`
   247  }
   248  
   249  func Self() string {
   250  	return ChainObject(`self`)
   251  }
   252  
   253  func Window() string {
   254  	return ChainObject(`window`)
   255  }
   256  
   257  func ParseInt(args string) string {
   258  	return FunctionCall(Func{
   259  		Name:  "parseInt",
   260  		Args:  args,
   261  		Body:  "",
   262  		Async: false,
   263  	})
   264  }
   265  
   266  func ParseFloat(args string) string {
   267  	return FunctionCall(Func{
   268  		Name:  "parseFloat",
   269  		Args:  args,
   270  		Body:  "",
   271  		Async: false,
   272  	})
   273  }
   274  
   275  func Math() string {
   276  	return ChainObject(`Math`)
   277  }
   278  
   279  func This() string {
   280  	return ChainObject(`this`)
   281  }
   282  
   283  func Object(body string) string {
   284  	var code = `{` + "\n" + body + "\n" + `};`
   285  	return Indent(code)
   286  }
   287  
   288  type Obj struct {
   289  	DotNotation bool
   290  	PropName    string
   291  	ObjName     string
   292  }
   293  
   294  /*
   295    @param Object data {
   296      {string} objName (Optional. Defaults to "this")
   297      {string} propName,
   298      {string} value,
   299      {boolean} dotNotation
   300    }
   301  */
   302  func ObjectPropertyName(data Obj) string {
   303  	var code string
   304  	if data.DotNotation == false {
   305  		if data.ObjName == "" {
   306  			code = `this["`
   307  		} else {
   308  			code = data.ObjName + `["`
   309  		}
   310  
   311  		code += data.PropName + `"] `
   312  		return CleanCode(code)
   313  	}
   314  
   315  	if data.ObjName == "" {
   316  		code = `this.` + data.PropName
   317  	} else {
   318  		code = data.ObjName + `.` + data.PropName
   319  	}
   320  
   321  	return CleanCode(code)
   322  }
   323  
   324  type ObjWithValue struct {
   325  	Obj
   326  	Value string
   327  }
   328  
   329  /*
   330    @param Object data {
   331      {string} objName (Optional. Defaults to "this")
   332      {string} propName,
   333      {string} value,
   334      {boolean} dotNotation
   335    }
   336  */
   337  func ObjectPropertyAssignment(data ObjWithValue) string {
   338  	var code string
   339  	if data.DotNotation == false {
   340  		if data.ObjName == "" {
   341  			code = `this["`
   342  		} else {
   343  			code = data.ObjName + `["`
   344  		}
   345  
   346  		code += data.PropName + `"] = ` + data.Value + `;`
   347  		//name = ``+ data.ObjName +`["`+ data.PropName +`"]`;
   348  		return CleanCode(code)
   349  	}
   350  
   351  	if data.ObjName == "" {
   352  		code = `this.` + data.PropName + ` = ` + data.Value + `;`
   353  	} else {
   354  		code = data.ObjName + `.` + data.PropName + ` = ` + data.Value + `;`
   355  	}
   356  
   357  	return CleanCode(code)
   358  }
   359  
   360  type ObjFuncCall struct {
   361  	ObjectName string
   362  	FuncName   string
   363  	Args       string
   364  	Async      bool
   365  }
   366  
   367  type ObjFunc struct {
   368  	ObjFuncCall
   369  	Body string
   370  }
   371  
   372  /*
   373    @param Object data {
   374      {string} objName (Optional. Defaults to "this")
   375      {string} funcName,
   376      {array} args (Optional),
   377  	{bool} async
   378      {func} body
   379    }
   380  */
   381  func ObjectFunction(data ObjFunc) string {
   382  	var code string
   383  	if data.ObjectName == "" {
   384  		code = `this.` + data.FuncName
   385  	} else {
   386  		code = data.ObjectName + `.` + data.FuncName
   387  	}
   388  
   389  	code += data.FuncName + ` = `
   390  	if data.Async {
   391  		code += `async `
   392  	}
   393  
   394  	code += `function(` + data.Args + `) {` + "\n" +
   395  		`` + data.Body + `` + "\n" +
   396  		`};`
   397  
   398  	return Indent(code)
   399  }
   400  
   401  /*
   402    @param Object data {
   403      {string} objName
   404      {string} funcName,
   405  	{bool} async
   406      {array} args
   407    }
   408  */
   409  func ObjectFunctionCall(data ObjFunc) string {
   410  	var code string
   411  	if data.Async {
   412  		code += `await `
   413  	}
   414  
   415  	if data.ObjectName == "" {
   416  		code = `this.` + data.FuncName
   417  	} else {
   418  		code = data.ObjectName + `.` + data.FuncName
   419  	}
   420  
   421  	code += `(` + data.Args + `);`
   422  	return CleanCode(code)
   423  }
   424  
   425  /*
   426    @param Object data {
   427      {string} condition,
   428      {func} body
   429    }
   430  */
   431  func IfStatement(
   432  	condition string,
   433  	body string,
   434  ) string {
   435  	var code = `if (` + condition + `) {` + "\n" +
   436  		`` + body + `` + "\n" +
   437  		`}`
   438  
   439  	return Indent(code)
   440  }
   441  
   442  /*
   443    @param Object data {
   444      {string} condition,
   445      {func} body
   446    }
   447  */
   448  func ElseIfStatement(
   449  	condition string,
   450  	body string,
   451  ) string {
   452  	var code = `else if (` + condition + `) {` + "\n" +
   453  		`` + body + `` + "\n" +
   454  		`}`
   455  
   456  	return Indent(code)
   457  }
   458  
   459  /*
   460    @param Object data {
   461      {func} body
   462    }
   463  */
   464  func ElseStatement(body string) string {
   465  	var code = `else {` + "\n" +
   466  		`` + body + `` + "\n" +
   467  		`}`
   468  
   469  	return Indent(code)
   470  }
   471  
   472  type Loop struct {
   473  	StartCondition  string
   474  	StopCondition   string
   475  	IncrementAction string
   476  	Body            string
   477  }
   478  
   479  /*
   480    @param Object data {
   481      {string} startCondition,
   482      {string} stopCondition,
   483      {string} incrementAction,
   484      {func} body
   485    }
   486  */
   487  func ForLoop(data Loop) string {
   488  	var code = `for (` + data.StartCondition + `; ` + data.StopCondition + `; ` + data.IncrementAction + `) {` + "\n" +
   489  		`` + data.Body + `` + "\n" +
   490  		`}`
   491  
   492  	return Indent(code)
   493  }
   494  
   495  type Class struct {
   496  	Name    string
   497  	Extends string
   498  	Body    string
   499  }
   500  
   501  func ClassBlock(data Class) string {
   502  	var code = `class ` + data.Name
   503  	if data.Extends != "" {
   504  		code += ` extends ` + data.Extends + ` `
   505  	}
   506  	code += ` {` + "\n" + data.Body + "\n" + `};`
   507  	return Indent(code)
   508  }
   509  
   510  /*
   511    @param Object data {
   512      {func} body,
   513    }
   514  */
   515  func TryBlock(body string) string {
   516  	var code = `try {` + "\n" + body + "\n" + `}`
   517  	return Indent(code)
   518  }
   519  
   520  /*
   521    @param Object data {
   522      {string} arg
   523      {func} body,
   524    }
   525  */
   526  func CatchBlock(
   527  	exception string,
   528  	body string,
   529  ) string {
   530  	var code = `catch(`
   531  	if exception != "" {
   532  		code += exception
   533  	}
   534  
   535  	code += `) {` + "\n"
   536  	code += `` + body + `` + "\n" + `}`
   537  	return Indent(code)
   538  }
   539  
   540  // Promise returns new Promise with resolve and reject function parameter.
   541  func Promise(args string, body string) string {
   542  	var code = `new Promise((resolve, reject) => {` + "\n" + body + "\n" + `})`
   543  	return Indent(code)
   544  }
   545  
   546  func ManyPromise(promiseList string) string {
   547  	return Indent(`Promise.all([` + "\n" + promiseList + "\n" + `]);`)
   548  }
   549  
   550  func PromiseThen(args string, body string) string {
   551  	var code = `.then((` + args + `) => {` + "\n" + body + "\n" + `})`
   552  	return Indent(code)
   553  }
   554  
   555  func PromiseCatch(err string, body string) string {
   556  	var code = `.catch((` + err + `) => {` + "\n" + body + "\n" + `})`
   557  	return Indent(code)
   558  }
   559  
   560  /*
   561    @param Object data {
   562      {string} name
   563      {array} args (optional)
   564    }
   565  */
   566  func ChainFunction(name string, args string) string {
   567  	return `.` + name + `(` + args + `)`
   568  }
   569  
   570  /*
   571    @param Object data {
   572      {string} value
   573    }
   574  */
   575  func ReturnStatement(value string) string {
   576  	return `return ` + value + `;`
   577  }
   578  
   579  /*
   580    @param { array<string> } args
   581  */
   582  func ConsoleLog(args ...string) string {
   583  	var code = `console.log(` + Args(args...) + `);`
   584  	return code + `);`
   585  }
   586  
   587  type TextBlock struct {
   588  	Text  string
   589  	Block bool
   590  }
   591  
   592  /*
   593    @param Object data {
   594      {string} text,
   595      {boolean} block (Optional. Defaults to false.)
   596    }
   597  */
   598  func Comment(data TextBlock) string {
   599  	if data.Block {
   600  		var code = `/*` + "\n" +
   601  			`` + data.Text + `` + "\n" +
   602  			`*/`
   603  		return Indent(code)
   604  	}
   605  
   606  	return `// ` + data.Text + ``
   607  }
   608  
   609  /*
   610    @param {string} code
   611  
   612    @description:
   613      Takes a rendered code and indents lines 2 through (n - 2),
   614      where n is the number of lines
   615  
   616    @returns: {string}
   617  */
   618  func Indent(code string) string {
   619  	var items = strings.Split(code, "\n")
   620  	var mapped = make([]string, len(items))
   621  	for i := 0; i < len(items); i++ {
   622  		var line = items[i]
   623  		if i == 0 || i == len(items)-1 {
   624  			mapped = append(mapped, GenerateTabs(0)+CleanCode(line))
   625  			continue
   626  		}
   627  		mapped = append(mapped, GenerateTabs(numTabs)+CleanCode(line))
   628  	}
   629  	return strings.Join(mapped, "\n")
   630  }
   631  
   632  // AddIndent returns a new line feed character.
   633  func AddIndent() string {
   634  	return "\n"
   635  }
   636  
   637  /*
   638    @param: {integer} numTabs
   639    @description: generates a string containing the number of tabs requested
   640    @returns: {string}
   641  */
   642  func GenerateTabs(numTabs int) string {
   643  	var tabs = ""
   644  	var tabCount = numSpacesPerTab * numTabs
   645  	for i := 0; i < tabCount; i += 1 {
   646  		tabs += " "
   647  	}
   648  	return tabs
   649  }
   650  
   651  var cleanRegExp = regexp.MustCompile(";{2,}")
   652  
   653  /*
   654    @param: {string} line
   655    @description: Cleans a line of code.
   656    @returns: {string}
   657  */
   658  func CleanCode(line string) string {
   659  	return cleanRegExp.ReplaceAllString(line, ";")
   660  }
   661  
   662  var iteratorPos = 8
   663  
   664  /*
   665    UniqueIteratorName
   666    @description:
   667      returns a name for an iterator variable that hasn't been used.
   668      An example of an iterator variable would be `var i` in a for loop.
   669      This func Is used to prevent undesired behavior in a nested
   670      loop. Starts at 'i'
   671  
   672    @returns: {string} iterator
   673  */
   674  func UniqueIteratorName() string {
   675  	var numChars = 1
   676  	var idx = iteratorPos
   677  
   678  	var iterator = ""
   679  
   680  	if iteratorPos >= 26 {
   681  		if iteratorPos%26 == 0 {
   682  			numChars = (iteratorPos / 26) + 1
   683  		} else {
   684  			numChars = iteratorPos / 26
   685  		}
   686  		idx = iteratorPos % 26
   687  	}
   688  
   689  	for i := 0; i < numChars; i++ {
   690  		iterator += alphabets[idx]
   691  	}
   692  
   693  	iteratorPos += 1
   694  	return iterator
   695  }
   696  
   697  // ResetIteratorPos resets the position of the current iterator to return back to 'i'
   698  func ResetIteratorPos() {
   699  	iteratorPos = 8
   700  }