Skip to content

Commit

Permalink
Hygen code generator (#508)
Browse files Browse the repository at this point in the history
* prop-generator

* hygen changes

* prop-generator regex injection and script

* adding injection comments

* Hygen method-generator

* adding comment

* changes to prop-generator template

* comments for method injection

* final change

* Updating JS files

* Spelling mistake

* extracting into a method

* more improvemets

* mistake from testing

* Updating JS files

* Updating package version

* refactoring method generator

* Updating package.json

* Updating package version

* improved prop generator

* fixed errors for method generator

* fixes and improvements for android side of method generator

* renamed and fixed bugs with helpers, made fixes and improvements for ios side of method generator

* finished prop and method generators

* new directory for listener generator

* Updating package version

* listener generator injection for DocumentView.tsx

* Updating JS files

* fixed method generator to handle 0 params + fix for listener generator

* Updating package version

* listener generator android side

* listener generator ios side

* fixed bug

* fixed indent and newline issues

* changed wording of some prompts

* added more comments

* Updating JS files

* re-check

* Updating package version

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Terry Yoon <[email protected]>
  • Loading branch information
3 people authored Jul 5, 2022
1 parent e4e20dc commit 92ff227
Show file tree
Hide file tree
Showing 42 changed files with 715 additions and 2 deletions.
169 changes: 169 additions & 0 deletions .hygen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
module.exports = {
helpers: {
/**
* Converts the parameter list of a React Native function into a parameter list of an Android function.
* e.g. 'flag: boolean, page: int' => 'boolean flag, int page' or 'final boolean flag, final int page'
* @param params React Native parameter list string
* @param setFinal Whether to add the 'final' keyword in front of each parameter
* @returns {string} Android parameter list string
*/
androidParams: (params, setFinal) => {
let arguments = ''
let finalWord = setFinal ? 'final ' : ''

// assuming no nested maps, remove the params inside maps (in between {} or Record<>)
// so that the top level parameters can be properly split by commas
params = params.replace(/((?<=\{)(.*?)(?=}))|((?<=Record<)(.*?)(?=>))/g, '')

params.split(',').forEach(param => {
let name = param.substring(0, param.indexOf(':')).trim()
let type = param.substring(param.indexOf(':') + 1).trim()

if ((type.startsWith('{') && type.endsWith('}')) ||
(type.startsWith('Record<') && type.endsWith('>')) ||
type.startsWith('AnnotOptions.')) {
type = 'ReadableMap'
} else if (type.startsWith('Config.') || type === 'string') {
type = 'String'
} else if (type.startsWith('Array<') && type.endsWith('>')) {
type = 'ReadableArray'
}

arguments += finalWord + type + ' ' + name + ', '
})

arguments = arguments.substring(0, arguments.length - 2)
return arguments
},
/**
* Converts the parameter list of a React Native function into a parameter list of an iOS function.
* e.g. 'flag: boolean, page: int' => 'flag:(BOOL)flag page:(NSInteger)page'
* @param params React Native parameter list string
* @param format Whether to separate each parameter by newlines
* @returns {string} iOS parameter list string
*/
iOSParams: (params, format) => {
let arguments = ''
let formatStr = format ? '\n ' : ' '

params = params.replace(/((?<=\{)(.*?)(?=}))|((?<=Record<)(.*?)(?=>))/g, '')

params.split(',').forEach(param => {
let name = param.substring(0, param.indexOf(':')).trim()
let type = param.substring(param.indexOf(':') + 1).trim()

if ((type.startsWith('{') && type.endsWith('}')) ||
(type.startsWith('Record<') && type.endsWith('>')) ||
type.startsWith('AnnotOptions.')) {
type = 'NSDictionary *'
} else if (type === 'boolean') {
type = 'BOOL'
} else if (type === 'int') {
type = 'NSInteger'
} else if (type.startsWith('Config.') || type === 'string') {
type = 'NSString *'
} else if (type.startsWith('Array<') && type.endsWith('>')) {
type = 'NSArray *'
}

arguments += name + ':(' + type + ')' + name + formatStr
})

arguments = arguments.substring(0, arguments.length - formatStr.length)
return arguments
},
/**
* Converts the parameter list of a React Native function into arguments when calling an Android function.
* e.g. 'flag: boolean, page: int' => 'flag, page'
* @param params React Native parameter list string
* @returns {string} Android arguments string
*/
androidArgs: params => {
let arguments = ''

params = params.replace(/((?<=\{)(.*?)(?=}))|((?<=Record<)(.*?)(?=>))/g, '')

params.split(',').forEach(param => {
arguments += param.substring(0, param.indexOf(':')).trim() + ', '
})

arguments = arguments.substring(0, arguments.length - 2)
return arguments
},
/**
* Converts the parameter list of a React Native function into arguments when calling an iOS function.
* e.g. 'flag: boolean, page: int' => 'flag:flag page:page'
* @param params React Native parameter list string
* @returns {string} iOS arguments string
*/
iOSArgs: params => {
let arguments = ''

params = params.replace(/((?<=\{)(.*?)(?=}))|((?<=Record<)(.*?)(?=>))/g, '')

params.split(',').forEach(param => {
let name = param.substring(0, param.indexOf(':')).trim()
arguments += name + ':' + name + ' '
})

arguments = arguments.substring(0, arguments.length - 1)
return arguments
},
/**
* Converts the React Native prop type into a corresponding Android type.
* @param type React Native prop type
* @returns {string|*} Android prop type; returns given param if no match is found
*/
androidPropType: type => {
if (type === 'bool') {
return 'boolean'
} else if (type === 'string' || type === 'oneOf') {
return 'String'
} else if (type === 'arrayOf') {
return '@NonNull ReadableArray'
} else {
return type
}
},
/**
* Converts the React Native function return type into a corresponding Android type.
* @param type React Native function return type
* @returns {string|*} Android return type; returns given param if no match is found
*/
androidReturnType: type => {
if (type.startsWith('Config.') || type === 'string') {
return 'String'
} else if ((type.startsWith('{') && type.endsWith('}')) ||
(type.startsWith('Record<') && type.endsWith('>')) ||
type.startsWith('AnnotOptions.')) {
return 'WritableMap'
} else if (type.startsWith('Array<') && type.endsWith('>')) {
return 'WritableArray'
} else {
return type
}
},
/**
* Converts the React Native function return type into a corresponding iOS type.
* @param type React Native function return type
* @returns {string|*} iOS return type; returns given param if no match is found
*/
iOSReturnType: type => {
if (type === 'boolean') {
return 'BOOL'
} else if (type === 'int') {
return 'NSInteger'
} else if (type.startsWith('Config.') || type === 'string') {
return 'NSString *'
} else if ((type.startsWith('{') && type.endsWith('}')) ||
(type.startsWith('Record<') && type.endsWith('>')) ||
type.startsWith('AnnotOptions.')) {
return 'NSDictionary *'
} else if (type.startsWith('Array<') && type.endsWith('>')) {
return 'NSArray *'
} else {
return type
}
}
}
}
5 changes: 5 additions & 0 deletions _templates/generator/help/index.ejs.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
message: |
hygen {bold generator new} --name [NAME] --action [ACTION]
hygen {bold generator with-prompt} --name [NAME] --action [ACTION]
---
18 changes: 18 additions & 0 deletions _templates/generator/new/hello.ejs.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
to: _templates/<%= name %>/<%= action || 'new' %>/hello.ejs.t
---
---
to: app/hello.js
---
const hello = ```
Hello!
This is your first hygen template.

Learn what it can do here:

https://github.com/jondot/hygen
```

console.log(hello)


18 changes: 18 additions & 0 deletions _templates/generator/with-prompt/hello.ejs.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
to: _templates/<%= name %>/<%= action || 'new' %>/hello.ejs.t
---
---
to: app/hello.js
---
const hello = ```
Hello!
This is your first prompt based hygen template.

Learn what it can do here:

https://github.com/jondot/hygen
```

console.log(hello)


14 changes: 14 additions & 0 deletions _templates/generator/with-prompt/prompt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
to: _templates/<%= name %>/<%= action || 'new' %>/prompt.js
---

// see types of prompts:
// https://github.com/enquirer/enquirer/tree/master/examples
//
module.exports = [
{
type: 'input',
name: 'message',
message: "What's your message?"
}
]
6 changes: 6 additions & 0 deletions _templates/listener-generator/new/ConstantsjavaEvent.ejs.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
inject: true
to: android/src/main/java/com/pdftron/reactnative/utils/Constants.java
after: // Hygen Generated Event Listeners
---
public static final String <%= h.changeCase.constantCase(name) %> = "<%= name %>";<% -%>
15 changes: 15 additions & 0 deletions _templates/listener-generator/new/ConstantsjavaKey.ejs.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
inject: true
to: android/src/main/java/com/pdftron/reactnative/utils/Constants.java
after: // Hygen Generated Keys
---
<% keys = ''
if (params !== '') {
params.split(',').forEach(param => {
argName = param.split(':')[0].trim()
keys += ' public static final String KEY_' + h.changeCase.constantCase(argName) + ' = "' + argName + '";\n'
})
keys = keys.substring(0, keys.length - 1)
}
-%>
<%- keys %><% -%>
31 changes: 31 additions & 0 deletions _templates/listener-generator/new/DocumentViewjava.ejs.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
inject: true
to: android/src/main/java/com/pdftron/reactnative/views/DocumentView.java
after: // Hygen Generated Event Listeners
---
<%# injecting lines to put the parameters into the WritableMap, e.g. params.putInt(KEY_PAGE_NUMBER, ); -%>
<% putArgs = ''
if (params !== '') {
params.split(',').forEach(param => {
argName = param.substring(0, param.indexOf(':')).trim()
argType = param.substring(param.indexOf(':') + 1).trim()
if (argType.startsWith('AnnotOptions.')) {
argType = 'Map'
} else if (argType.startsWith('Config.') || argType === 'string') {
argType = 'String'
} else if (argType.startsWith('Array<') && argType.endsWith('>')) {
argType = 'Array'
} else {
argType = h.changeCase.upperCaseFirst(argType)
}
putArgs += '\n // params.put' + argType + '(KEY_' + h.changeCase.constantCase(argName) + ', );'
})
}
-%>
// uncomment and use to implement <%= name %>
// WritableMap params = Arguments.createMap();
// params.putString(<%= h.changeCase.constantCase(name) %>, <%= h.changeCase.constantCase(name) %>);<%- putArgs %>

// onReceiveNativeEvent(params);
20 changes: 20 additions & 0 deletions _templates/listener-generator/new/DocumentViewtsxOnChange.ejs.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
inject: true
to: src/DocumentView/DocumentView.tsx
after: // Hygen Generated Event Listeners
---
<%# injecting lines for each parameter, e.g. 'pageNumber': event.nativeEvent.pageNumber, -%>
<% args = ''
if (params !== '') {
args += '{'
params.split(',').forEach(param => {
argName = param.split(':')[0].trim()
args += '\n \'' + argName + '\': event.nativeEvent.' + argName + ','
})
args += '\n }'
}
-%>
} else if (event.nativeEvent.<%= name %>) {
if (this.props.<%= name %>) {
this.props.<%= name %>(<%- args %>);
}<% -%>
11 changes: 11 additions & 0 deletions _templates/listener-generator/new/DocumentViewtsxProp.ejs.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
inject: true
to: src/DocumentView/DocumentView.tsx
after: // Hygen Generated Props
---
<% args = params.trim()
if (args !== '') {
args = 'event: { ' + args.replace(/\bint\b|\bdouble\b/g, 'number') + ' }'
}
-%>
<%= name %>: func<(<%- args %>) => void>(),<% -%>
28 changes: 28 additions & 0 deletions _templates/listener-generator/new/RNTPTDocumentViewManagerm.ejs.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
inject: true
to: ios/RNTPTDocumentViewManager.m
after: // Hygen Generated Event Listeners
---
<%# injecting lines for each parameter, e.g. @"pageNumber": @(pageNumber), -%>
<% args = ''
if (params !== '') {
params.split(',').forEach(param => {
argName = param.substring(0, param.indexOf(':')).trim()
argType = param.substring(param.indexOf(':') + 1).trim()
if (argType === 'int' || argType === 'double' || argType === 'boolean' ) {
args += '\n @"' + argName + '": @(' + argName + '),'
} else {
args += '\n @"' + argName + '": ' + argName + ','
}
})
}
-%>
- (void)<%= name %>:(RNTPTDocumentView *)sender<%- params === '' ? '' : ' ' + h.iOSParams(params, false) %>
{
if (sender.onChange) {
sender.onChange(@{
@"<%= name %>": @"<%= name %>",<%- args %>
});
}
}
6 changes: 6 additions & 0 deletions _templates/listener-generator/new/RNTPTDocumentViewh.ejs.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
inject: true
to: ios/RNTPTDocumentView.h
after: // Hygen Generated Event Listeners
---
- (void)<%= name %>:(RNTPTDocumentView *)sender<%- params === '' ? '' : ' ' + h.iOSParams(params, false) %>;
21 changes: 21 additions & 0 deletions _templates/listener-generator/new/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module.exports = {
prompt: ({ inquirer }) => {
// defining questions in arrays ensures all questions are asked before next prompt is executed
const questions = [
{
type: 'input',
name: 'name',
message: 'Name of event listener? (ex: onLayoutChanged)',
},
{
type: 'input',
name: 'params',
message: 'Parameter list of React Native listener (comma separated)? Use either int or double for number\n (ex: previousPageNumber: int, pageNumber: int, ...)\n',
}
]

// returning the answers to the prompt
return inquirer
.prompt(questions)
},
}
Loading

0 comments on commit 92ff227

Please sign in to comment.