Skip to content

Commit e6b553b

Browse files
committed
update, conflict resolution
1 parent 309e021 commit e6b553b

File tree

24 files changed

+537
-15
lines changed

24 files changed

+537
-15
lines changed

src/generators.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ReactCrudGenerator from './generators/ReactCrudGenerator';
2+
import ReactNativeCrudGenerator from './generators/ReactNativeCrudGenerator';
23
import TypescriptInterfaceGenerator from './generators/TypescriptInterfaceGenerator';
34

45
function wrap (cl) {
@@ -9,6 +10,8 @@ function generators (generator = 'react') {
910
switch (generator) {
1011
case 'react':
1112
return wrap(ReactCrudGenerator);
13+
case 'react-native':
14+
return wrap(ReactNativeCrudGenerator);
1215
case 'typescript':
1316
return wrap(TypescriptInterfaceGenerator);
1417
}

src/generators/ReactCrudGenerator.js

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,20 @@ export default class ReactCrudGenerator {
88
templates = {};
99

1010
constructor({hydraPrefix, templateDirectory}) {
11+
const templatePathCommon = `${templateDirectory}/react-common/`;
1112
const templatePath = `${templateDirectory}/react/`;
1213

1314
this.hydraPrefix = hydraPrefix;
1415

1516
// actions
16-
this.registerTemplate(templatePath, 'actions/foo/create.js');
17-
this.registerTemplate(templatePath, 'actions/foo/delete.js');
18-
this.registerTemplate(templatePath, 'actions/foo/list.js');
19-
this.registerTemplate(templatePath, 'actions/foo/update.js');
20-
this.registerTemplate(templatePath, 'actions/foo/show.js');
17+
this.registerTemplate(templatePathCommon, 'actions/foo/create.js');
18+
this.registerTemplate(templatePathCommon, 'actions/foo/delete.js');
19+
this.registerTemplate(templatePathCommon, 'actions/foo/list.js');
20+
this.registerTemplate(templatePathCommon, 'actions/foo/update.js');
21+
this.registerTemplate(templatePathCommon, 'actions/foo/show.js');
2122

2223
// api
23-
this.registerTemplate(templatePath, 'api/fooFetch.js');
24+
this.registerTemplate(templatePathCommon, 'api/fooFetch.js');
2425

2526
// components
2627
this.registerTemplate(templatePath, 'components/foo/Create.js');
@@ -31,25 +32,25 @@ export default class ReactCrudGenerator {
3132
this.registerTemplate(templatePath, 'components/foo/Show.js');
3233

3334
// reducers
34-
this.registerTemplate(templatePath, 'reducers/foo/create.js');
35-
this.registerTemplate(templatePath, 'reducers//foo/delete.js');
36-
this.registerTemplate(templatePath, 'reducers/foo/index.js');
37-
this.registerTemplate(templatePath, 'reducers/foo/list.js');
38-
this.registerTemplate(templatePath, 'reducers/foo/update.js');
39-
this.registerTemplate(templatePath, 'reducers/foo/show.js');
35+
this.registerTemplate(templatePathCommon, 'reducers/foo/create.js');
36+
this.registerTemplate(templatePathCommon, 'reducers//foo/delete.js');
37+
this.registerTemplate(templatePathCommon, 'reducers/foo/index.js');
38+
this.registerTemplate(templatePathCommon, 'reducers/foo/list.js');
39+
this.registerTemplate(templatePathCommon, 'reducers/foo/update.js');
40+
this.registerTemplate(templatePathCommon, 'reducers/foo/show.js');
4041

4142
// routes
4243
this.registerTemplate(templatePath, 'routes/foo.js');
4344

4445
// entrypoint
45-
this.registerTemplate(templatePath, 'api/_entrypoint.js');
46+
this.registerTemplate(templatePathCommon, 'api/_entrypoint.js');
4647

4748
// utils
4849
this.registerTemplate(templatePath, 'utils/helpers.js');
4950
}
5051

5152
registerTemplate(templatePath, path) {
52-
this.templates[path] = handlebars.compile(fs.readFileSync(templatePath+path).toString());
53+
this.templates[path] = handlebars.compile(fs.readFileSync(templatePath + path).toString());
5354
}
5455

5556
help(resource) {
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import mkdirp from 'mkdirp';
2+
import handlebars from 'handlebars';
3+
import fs from 'fs';
4+
import urlapi from 'url';
5+
import chalk from 'chalk';
6+
7+
export default class ReactNativeCrudGenerator {
8+
templates = {};
9+
10+
constructor({hydraPrefix, templateDirectory}) {
11+
const templatePathCommon = `${templateDirectory}/react-common/`;
12+
const templatePath = `${templateDirectory}/react/`;
13+
14+
this.hydraPrefix = hydraPrefix;
15+
16+
// actions
17+
this.registerTemplate(templatePathCommon, 'actions/foo/create.js');
18+
this.registerTemplate(templatePathCommon, 'actions/foo/delete.js');
19+
this.registerTemplate(templatePathCommon, 'actions/foo/list.js');
20+
this.registerTemplate(templatePathCommon, 'actions/foo/update.js');
21+
this.registerTemplate(templatePathCommon, 'actions/foo/show.js');
22+
23+
// api
24+
this.registerTemplate(templatePathCommon, 'api/fooFetch.js');
25+
26+
// components
27+
this.registerTemplate(templatePath, 'components/foo/Create.js');
28+
this.registerTemplate(templatePath, 'components/foo/Form.js');
29+
this.registerTemplate(templatePath, 'components/foo/index.js');
30+
this.registerTemplate(templatePath, 'components/foo/List.js');
31+
this.registerTemplate(templatePath, 'components/foo/Update.js');
32+
this.registerTemplate(templatePath, 'components/foo/Show.js');
33+
34+
// reducers
35+
this.registerTemplate(templatePathCommon, 'reducers/foo/create.js');
36+
this.registerTemplate(templatePathCommon, 'reducers//foo/delete.js');
37+
this.registerTemplate(templatePathCommon, 'reducers/foo/index.js');
38+
this.registerTemplate(templatePathCommon, 'reducers/foo/list.js');
39+
this.registerTemplate(templatePathCommon, 'reducers/foo/update.js');
40+
this.registerTemplate(templatePathCommon, 'reducers/foo/show.js');
41+
42+
// entrypoint
43+
this.registerTemplate(templatePathCommon, 'api/_entrypoint.js');
44+
}
45+
46+
registerTemplate(templatePath, path) {
47+
this.templates[path] = handlebars.compile(fs.readFileSync(templatePath+path).toString());
48+
}
49+
50+
help(resource) {
51+
const titleLc = resource.title.toLowerCase()
52+
53+
console.log('Code for the "%s" resource type has been generated!', resource.title);
54+
console.log('Paste the following definitions in your application configuration:');
55+
console.log(chalk.green(`
56+
// import reducers
57+
import ${titleLc} from './reducers/${titleLc}/';
58+
// Add the reducer
59+
combineReducers(${titleLc},{/* ... */}),
60+
`));
61+
}
62+
63+
generate(api, resource, dir) {
64+
const lc = resource.title.toLowerCase();
65+
const titleUcFirst = resource.title.charAt(0).toUpperCase() + resource.title.slice(1);
66+
67+
const context = {
68+
title: resource.title,
69+
name: resource.name,
70+
lc,
71+
uc: resource.title.toUpperCase(),
72+
fields: resource.readableFields,
73+
formFields: this.buildFields(resource.writableFields),
74+
hydraPrefix: this.hydraPrefix,
75+
titleUcFirst
76+
};
77+
78+
79+
// Create directories
80+
// These directories may already exist
81+
mkdirp.sync(`${dir}/api`);
82+
83+
this.createDir(`${dir}/actions/${lc}`);
84+
this.createDir(`${dir}/components/${lc}`);
85+
this.createDir(`${dir}/reducers/${lc}`);
86+
87+
// actions
88+
this.createFile('actions/foo/create.js', `${dir}/actions/${lc}/create.js`, context);
89+
this.createFile('actions/foo/delete.js', `${dir}/actions/${lc}/delete.js`, context);
90+
this.createFile('actions/foo/list.js', `${dir}/actions/${lc}/list.js`, context);
91+
this.createFile('actions/foo/update.js', `${dir}/actions/${lc}/update.js`, context);
92+
this.createFile('actions/foo/show.js', `${dir}/actions/${lc}/show.js`, context);
93+
94+
// api
95+
this.createFile('api/fooFetch.js', `${dir}/api/${lc}Fetch.js`, context);
96+
97+
// components
98+
this.createFile('components/foo/Create.js', `${dir}/components/${lc}/Create.js`, context);
99+
this.createFile('components/foo/Form.js', `${dir}/components/${lc}/Form.js`, context);
100+
this.createFile('components/foo/index.js', `${dir}/components/${lc}/index.js`, context);
101+
this.createFile('components/foo/List.js', `${dir}/components/${lc}/List.js`, context);
102+
this.createFile('components/foo/Update.js', `${dir}/components/${lc}/Update.js`, context);
103+
this.createFile('components/foo/Show.js', `${dir}/components/${lc}/Show.js`, context);
104+
105+
// reducers
106+
this.createFile('reducers/foo/create.js', `${dir}/reducers/${lc}/create.js`, context);
107+
this.createFile('reducers//foo/delete.js', `${dir}/reducers/${lc}/delete.js`, context);
108+
this.createFile('reducers/foo/index.js', `${dir}/reducers/${lc}/index.js`, context);
109+
this.createFile('reducers/foo/list.js', `${dir}/reducers/${lc}/list.js`, context);
110+
this.createFile('reducers/foo/update.js', `${dir}/reducers/${lc}/update.js`, context);
111+
this.createFile('reducers/foo/show.js', `${dir}/reducers/${lc}/show.js`, context);
112+
}
113+
114+
entrypoint(apiEntry, dir) {
115+
const url = urlapi.parse(apiEntry);
116+
const {protocol, host, port, pathname} = url;
117+
const hostUrl = `${protocol}//${host}${port ? `:${port}` : ''}`;
118+
119+
const context = {
120+
host: hostUrl,
121+
path: pathname
122+
}
123+
124+
this.createFile('api/_entrypoint.js', `${dir}/api/_entrypoint.js`, context);
125+
}
126+
127+
getInputTypeFromField(field) {
128+
switch (field.id) {
129+
case 'http://schema.org/email':
130+
return {type: 'email'};
131+
132+
case 'http://schema.org/url':
133+
return {type: 'url'};
134+
}
135+
136+
switch (field.range) {
137+
case 'http://www.w3.org/2001/XMLSchema#integer':
138+
return {type: 'number'};
139+
140+
case 'http://www.w3.org/2001/XMLSchema#decimal':
141+
return {type: 'number', step: '0.1'};
142+
143+
case 'http://www.w3.org/2001/XMLSchema#boolean':
144+
return {type: 'checkbox'};
145+
146+
case 'http://www.w3.org/2001/XMLSchema#date':
147+
return {type: 'date'};
148+
149+
case 'http://www.w3.org/2001/XMLSchema#time':
150+
return {type: 'time'};
151+
152+
default:
153+
return {type: 'text'};
154+
}
155+
}
156+
157+
buildFields(apiFields) {
158+
let fields = [];
159+
for (let apiField of apiFields) {
160+
let field = this.getInputTypeFromField(apiField);
161+
field.required = apiField.required;
162+
field.name = apiField.name;
163+
field.description = apiField.description.replace(/"/g, "'"); // fix for Form placeholder description
164+
165+
fields.push(field)
166+
}
167+
168+
return fields;
169+
}
170+
171+
createDir(dir) {
172+
if (fs.existsSync(dir)) throw new Error(`The directory "${dir}" already exists`);
173+
mkdirp.sync(dir);
174+
}
175+
176+
createFile(template, dest, context) {
177+
fs.writeFileSync(dest, this.templates[template](context));
178+
}
179+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import Api from 'api-doc-parser/lib/Api';
2+
import Resource from 'api-doc-parser/lib/Resource';
3+
import Field from 'api-doc-parser/lib/Field';
4+
import fs from 'fs';
5+
import tmp from 'tmp';
6+
import ReactNativeCrudGenerator from './ReactNativeCrudGenerator';
7+
8+
9+
test('Generate a React app', () => {
10+
const generator = new ReactNativeCrudGenerator({hydraPrefix: 'hydra:', templateDirectory: `${__dirname}/../../templates`});
11+
const tmpobj = tmp.dirSync({unsafeCleanup: true});
12+
13+
const fields = [new Field('bar', {
14+
id: 'http://schema.org/url',
15+
range: 'http://www.w3.org/2001/XMLSchema#string',
16+
reference: null,
17+
required: true,
18+
description: 'An URL'
19+
})];
20+
const resource = new Resource('abc', 'http://example.com/foos', {
21+
id: 'foo',
22+
title: 'Foo',
23+
readableFields: fields,
24+
writableFields: fields
25+
});
26+
const api = new Api('http://example.com', {
27+
title: 'My API',
28+
resources: [resource]
29+
});
30+
generator.generate(api, resource, tmpobj.name);
31+
32+
expect(fs.existsSync(tmpobj.name+'/actions/abc/create.js'), true);
33+
expect(fs.existsSync(tmpobj.name+'/actions/abc/delete.js'), true);
34+
expect(fs.existsSync(tmpobj.name+'/actions/abc/list.js'), true);
35+
expect(fs.existsSync(tmpobj.name+'/actions/abc/update.js'), true);
36+
37+
expect(fs.existsSync(tmpobj.name+'/api/abcFetch.js'), true);
38+
39+
expect(fs.existsSync(tmpobj.name+'/components/abc/Create.js'), true);
40+
expect(fs.existsSync(tmpobj.name+'/components/abc/Form.js'), true);
41+
expect(fs.existsSync(tmpobj.name+'/components/abc/List.js'), true);
42+
expect(fs.existsSync(tmpobj.name+'/components/abc/Update.js'), true);
43+
44+
expect(fs.existsSync(tmpobj.name+'/reducers/abc/create.js'), true);
45+
expect(fs.existsSync(tmpobj.name+'/reducers/abc/delete.js'), true);
46+
expect(fs.existsSync(tmpobj.name+'/reducers/abc/index.js'), true);
47+
expect(fs.existsSync(tmpobj.name+'/reducers/abc/list.js'), true);
48+
expect(fs.existsSync(tmpobj.name+'/reducers/abc/update.js'), true);
49+
50+
tmpobj.removeCallback();
51+
});

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ program
1212
.usage('entrypoint outputDirectory')
1313
.option('-r, --resource [resourceName]', 'Generate CRUD for the given resource')
1414
.option('-p, --hydra-prefix [hydraPrefix]', 'The hydra prefix used by the API', 'hydra:')
15-
.option('-g, --generator [generator]', 'The generator to use, one of "react", "angular" etc.', 'react')
15+
.option('-g, --generator [generator]', 'The generator to use, one of "react", "react-native", "angular", etc.', 'react')
1616
.option('-t, --template-directory [templateDirectory]', 'The templates directory base to use. Final directory will be ${templateDirectory}/${generator}', `${__dirname}/../templates/`)
1717
.parse(process.argv);
1818

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React, {Component} from 'react';
2+
import {ScrollView, Text} from 'react-native';
3+
import {connect} from 'react-redux';
4+
import PropTypes from 'prop-types';
5+
import Form from './Form';
6+
import { create, loading, error } from '../../actions/{{{ lc }}}/create';
7+
8+
class Create extends Component {
9+
static propTypes = {
10+
error: PropTypes.string,
11+
loading: PropTypes.bool.isRequired,
12+
created: PropTypes.object,
13+
create: PropTypes.func.isRequired,
14+
reset: PropTypes.func.isRequired,
15+
};
16+
17+
componentWillUnmount() {
18+
this.props.reset();
19+
}
20+
21+
render() {
22+
return (
23+
<ScrollView>
24+
<Text>Create component {{lc}}</Text>
25+
</ScrollView>
26+
);
27+
}
28+
}
29+
30+
const mapStateToProps = (state) => {
31+
return {
32+
created: state.{{{ lc }}}.create.created,
33+
error: state.{{{ lc }}}.create.error,
34+
loading: state.{{{ lc }}}.create.loading,
35+
};
36+
};
37+
38+
const mapDispatchToProps = (dispatch) => {
39+
return {
40+
create: values => dispatch(create(values)),
41+
reset: () => {
42+
dispatch(loading(false));
43+
dispatch(error(null));
44+
},
45+
};
46+
};
47+
48+
export default connect(mapStateToProps, mapDispatchToProps)(Create);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React, { Component } from 'react';
2+
import { Field, reduxForm } from 'redux-form';
3+
4+
class Form extends Component {
5+
renderField(data) {
6+
const hasError = data.meta.touched && !!data.meta.error;
7+
if (hasError) {
8+
data.input['aria-describedby'] = `{{{ lc }}}_${data.input.name}_helpBlock`;
9+
data.input['aria-invalid'] = true;
10+
}
11+
12+
return <div className={`form-group${hasError ? ' has-error' : ''}`}>
13+
<label htmlFor={`{{{ lc }}}_${data.input.name}`} className="control-label">{data.input.name}</label>
14+
<input {...data.input} type={data.type} step={data.step} required={data.required} placeholder={data.placeholder} id={`{{{ lc }}}_${data.input.name}`} className="form-control"/>
15+
{hasError && <span className="help-block" id={`{{{ lc }}}_${data.input.name}_helpBlock`}>{data.meta.error}</span>}
16+
</div>;
17+
}
18+
19+
render() {
20+
const { handleSubmit } = this.props;
21+
22+
return <form onSubmit={handleSubmit}>
23+
{{#each formFields}}
24+
<Field component={this.renderField} name="{{{ name }}}" type="{{{ type }}}"{{#if step}} step="{{{ step }}}"{{/if}} placeholder="{{{ description }}}" {{#if required}}required={true}{{/if}}/>
25+
{{/each}}
26+
27+
<button type="submit" className="btn btn-primary">Submit</button>
28+
</form>;
29+
}
30+
}
31+
32+
export default reduxForm({form: '{{{ lc }}}', enableReinitialize: true, keepDirtyOnReinitialize: true})(Form);

0 commit comments

Comments
 (0)