Skip to content

Commit dae9178

Browse files
author
Robert Yokota
committedJan 17, 2014
First commit
0 parents  commit dae9178

38 files changed

+1382
-0
lines changed
 

‎.editorconfig

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# http://editorconfig.org
2+
root = true
3+
4+
[*]
5+
indent_style = space
6+
indent_size = 4
7+
end_of_line = lf
8+
charset = utf-8
9+
trim_trailing_whitespace = true
10+
insert_final_newline = true
11+
12+
[*.md]
13+
trim_trailing_whitespace = false

‎.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* text=auto

‎.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
temp/

‎.jshintrc

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"node": true,
3+
"esnext": true,
4+
"bitwise": true,
5+
"camelcase": true,
6+
"curly": true,
7+
"eqeqeq": true,
8+
"immed": true,
9+
"indent": 4,
10+
"latedef": true,
11+
"newcap": true,
12+
"noarg": true,
13+
"quotmark": "single",
14+
"regexp": true,
15+
"undef": true,
16+
"unused": true,
17+
"strict": true,
18+
"trailing": true,
19+
"smarttabs": true,
20+
"white": true
21+
}

‎.travis.yml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
language: node_js
2+
node_js:
3+
- '0.10'
4+
- '0.8'
5+
before_install:
6+
- currentfolder=${PWD##*/}
7+
- if [ "$currentfolder" != 'generator-angular-go-martini' ]; then cd .. && eval "mv $currentfolder generator-angular-go-martini" && cd generator-angular-go-martini; fi
8+

‎LICENSE

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright 2014 Robert Yokota
2+
3+
Permission is hereby granted, free of charge, to any person obtaining
4+
a copy of this software and associated documentation files (the
5+
"Software"), to deal in the Software without restriction, including
6+
without limitation the rights to use, copy, modify, merge, publish,
7+
distribute, sublicense, and/or sell copies of the Software, and to
8+
permit persons to whom the Software is furnished to do so, subject to
9+
the following conditions:
10+
11+
The above copyright notice and this permission notice shall be
12+
included in all copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

‎README.md

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# The Angular-Sinatra generator
2+
3+
A [Yeoman](http://yeoman.io) generator for [AngularJS](http://angularjs.org) and [Sinatra](http://www.sinatrarb.com/).
4+
5+
Sinatra is a Ruby-based micro-framework. For AngularJS integration with a Java-based micro-framework, see the [Angular-Dropwizard generator](https://github.com/rayokota/generator-angular-dropwizard).
6+
7+
## Installation
8+
9+
Install [Git](http://git-scm.com), [node.js](http://nodejs.org), and [Ruby](https://www.ruby-lang.org/). The development mode also requires [SQLite](http://www.sqlite.org).
10+
11+
Install Yeoman:
12+
13+
npm install -g yo
14+
15+
Install the Angular-Sinatra generator:
16+
17+
npm install -g generator-angular-sinatra
18+
19+
## Creating a Sinatra service
20+
21+
In a new directory, generate the service:
22+
23+
yo angular-sinatra
24+
25+
Run the service:
26+
27+
rackup
28+
29+
Your service will run at [http://localhost:9292](http://localhost:9292).
30+
31+
32+
## Creating a persistent entity
33+
34+
Generate the entity:
35+
36+
yo angular-sinatra:entity [myentity]
37+
38+
You will be asked to specify attributes for the entity, where each attribute has the following:
39+
40+
- a name
41+
- a type (String, Integer, Float, Boolean, Date, Enum)
42+
- for a String attribute, an optional minimum and maximum length
43+
- for a numeric attribute, an optional minimum and maximum value
44+
- for a Date attribute, an optional constraint to either past values or future values
45+
- for an Enum attribute, a list of enumerated values
46+
- whether the attribute is required
47+
48+
Files that are regenerated will appear as conflicts. Allow the generator to overwrite these files as long as no custom changes have been made.
49+
50+
Run the service:
51+
52+
rackup
53+
54+
A client-side AngularJS application will now be available by running
55+
56+
grunt server
57+
58+
The Grunt server will run at [http://localhost:9000](http://localhost:9000). It will proxy REST requests to the Sinatra service running at [http://localhost:9292](http://localhost:9292).
59+
60+
At this point you should be able to navigate to a page to manage your persistent entities.
61+
62+
The Grunt server supports hot reloading of client-side HTML/CSS/Javascript file changes.
63+

‎app/index.js

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
'use strict';
2+
var util = require('util'),
3+
path = require('path'),
4+
yeoman = require('yeoman-generator'),
5+
_ = require('lodash'),
6+
_s = require('underscore.string'),
7+
pluralize = require('pluralize'),
8+
asciify = require('asciify');
9+
10+
var AngularGoMartiniGenerator = module.exports = function AngularGoMartiniGenerator(args, options, config) {
11+
yeoman.generators.Base.apply(this, arguments);
12+
13+
this.on('end', function () {
14+
this.installDependencies({ skipInstall: options['skip-install'] });
15+
if (!options['skip-install']) {
16+
return this.spawnCommand('bundle', ['install']);
17+
}
18+
});
19+
20+
this.pkg = JSON.parse(this.readFileAsString(path.join(__dirname, '../package.json')));
21+
};
22+
23+
util.inherits(AngularGoMartiniGenerator, yeoman.generators.Base);
24+
25+
AngularGoMartiniGenerator.prototype.askFor = function askFor() {
26+
27+
var cb = this.async();
28+
29+
console.log('\n' +
30+
'+-+-+-+-+-+-+-+ +-+-+ +-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+\n' +
31+
'|a|n|g|u|l|a|r| |g|o| |m|a|r|t|i|n|i| |g|e|n|e|r|a|t|o|r|\n' +
32+
'+-+-+-+-+-+-+-+ +-+-+ +-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+\n' +
33+
'\n');
34+
35+
var prompts = [{
36+
type: 'input',
37+
name: 'baseName',
38+
message: 'What is the name of your application?',
39+
default: 'myapp'
40+
}];
41+
42+
this.prompt(prompts, function (props) {
43+
this.baseName = props.baseName;
44+
45+
cb();
46+
}.bind(this));
47+
};
48+
49+
AngularGoMartiniGenerator.prototype.app = function app() {
50+
51+
this.copy('gitignore', '.gitignore');
52+
this.entities = [];
53+
this.resources = [];
54+
this.generatorConfig = {
55+
"baseName": this.baseName,
56+
"entities": this.entities,
57+
"resources": this.resources
58+
};
59+
this.generatorConfigStr = JSON.stringify(this.generatorConfig, null, '\t');
60+
61+
this.template('_generator.json', 'generator.json');
62+
this.template('_package.json', 'package.json');
63+
this.template('_bower.json', 'bower.json');
64+
this.template('bowerrc', '.bowerrc');
65+
this.template('Gruntfile.js', 'Gruntfile.js');
66+
this.copy('gitignore', '.gitignore');
67+
68+
var modelsDir = 'models/'
69+
var publicDir = 'public/'
70+
var routesDir = 'routes/'
71+
this.mkdir(modelsDir);
72+
this.mkdir(publicDir);
73+
this.mkdir(routesDir);
74+
75+
this.template('_server.go', 'server.go');
76+
this.template('models/_gorp.go', modelsDir + 'gorp.go');
77+
this.template('routes/_encoding.go', routesDir + 'encoding.go');
78+
79+
var publicCssDir = publicDir + 'css/';
80+
var publicJsDir = publicDir + 'js/';
81+
var publicViewDir = publicDir + 'views/';
82+
this.mkdir(publicCssDir);
83+
this.mkdir(publicJsDir);
84+
this.mkdir(publicViewDir);
85+
this.template('public/_index.html', publicDir + 'index.html');
86+
this.copy('public/css/app.css', publicCssDir + 'app.css');
87+
this.template('public/js/_app.js', publicJsDir + 'app.js');
88+
this.template('public/js/home/_home-controller.js', publicJsDir + 'home/home-controller.js');
89+
this.template('public/views/home/_home.html', publicViewDir + 'home/home.html');
90+
};
91+
92+
AngularGoMartiniGenerator.prototype.projectfiles = function projectfiles() {
93+
this.copy('editorconfig', '.editorconfig');
94+
this.copy('jshintrc', '.jshintrc');
95+
};

‎app/templates/Gruntfile.js

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
'use strict';
2+
3+
var proxySnippet = require('grunt-connect-proxy/lib/utils').proxyRequest;
4+
5+
module.exports = function (grunt) {
6+
require('load-grunt-tasks')(grunt);
7+
require('time-grunt')(grunt);
8+
9+
grunt.initConfig({
10+
yeoman: {
11+
// configurable paths
12+
app: require('./bower.json').appPath || 'public',
13+
dist: 'public'
14+
},
15+
sync: {
16+
dist: {
17+
files: [{
18+
cwd: '<%%= yeoman.app %>',
19+
dest: '<%%= yeoman.dist %>',
20+
src: '**'
21+
}]
22+
}
23+
},
24+
watch: {
25+
options: {
26+
livereload: 35729
27+
},
28+
src: {
29+
files: [
30+
'<%%= yeoman.app %>/*.html',
31+
'<%%= yeoman.app %>/css/**/*',
32+
'<%%= yeoman.app %>/js/**/*',
33+
'<%%= yeoman.app %>/views/**/*'
34+
],
35+
//tasks: ['sync:dist']
36+
}
37+
},
38+
connect: {
39+
proxies: [
40+
{
41+
context: '/<%= baseName %>',
42+
host: 'localhost',
43+
port: 9292,
44+
https: false,
45+
changeOrigin: false
46+
}
47+
],
48+
options: {
49+
port: 9000,
50+
// Change this to '0.0.0.0' to access the server from outside.
51+
hostname: 'localhost',
52+
livereload: 35729
53+
},
54+
livereload: {
55+
options: {
56+
open: true,
57+
base: [
58+
'<%%= yeoman.app %>'
59+
],
60+
middleware: function (connect) {
61+
return [
62+
proxySnippet,
63+
connect.static(require('path').resolve('public'))
64+
];
65+
}
66+
}
67+
},
68+
/*
69+
dist: {
70+
options: {
71+
base: '<%%= yeoman.dist %>'
72+
}
73+
}
74+
*/
75+
},
76+
// Put files not handled in other tasks here
77+
copy: {
78+
dist: {
79+
files: [{
80+
expand: true,
81+
dot: true,
82+
cwd: '<%%= yeoman.app %>',
83+
dest: '<%%= yeoman.dist %>',
84+
src: '**'
85+
}]
86+
},
87+
},
88+
// Test settings
89+
karma: {
90+
unit: {
91+
configFile: 'test/config/karma.conf.js',
92+
singleRun: true
93+
}
94+
},
95+
bowercopy: {
96+
options: {
97+
destPrefix: '<%%= yeoman.app %>'
98+
},
99+
test: {
100+
files: {
101+
'test/lib/angular-mocks': 'angular-mocks',
102+
'test/lib/angular-scenario': 'angular-scenario'
103+
}
104+
}
105+
}
106+
});
107+
108+
grunt.registerTask('server', function (target) {
109+
grunt.task.run([
110+
//'copy:dist',
111+
'configureProxies',
112+
'connect:livereload',
113+
'watch'
114+
]);
115+
});
116+
};

‎app/templates/README.md.erb

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## Overview
2+
3+
Blah blah
4+

‎app/templates/_app.rb

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# encoding: utf-8
2+
require 'multi_json'
3+
require 'sinatra'
4+
require 'data_mapper'
5+
require 'dm-migrations'
6+
7+
class <%= _.capitalize(baseName) %> < Sinatra::Application
8+
enable :sessions
9+
10+
configure :development do
11+
DataMapper::Logger.new($stdout, :debug)
12+
DataMapper.setup(
13+
:default,
14+
'sqlite:///tmp/my.db'
15+
)
16+
end
17+
18+
configure :production do
19+
DataMapper.setup(
20+
:default,
21+
'postgres://postgres:12345@localhost/sinatra_service'
22+
)
23+
end
24+
end
25+
26+
require_relative 'helpers/init'
27+
require_relative 'models/init'
28+
require_relative 'routes/init'
29+
30+
DataMapper.finalize

‎app/templates/_bower.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "<%= _.camelize(baseName) %>",
3+
"version": "0.0.0",
4+
"dependencies": {
5+
"angular-bootstrap": "~0.7.0",
6+
"angular-resource": "~1.2.6",
7+
"angular-route": "~1.2.6",
8+
"angular-ui-date": "~0.0.3",
9+
"angular": "~1.2.6",
10+
"bootstrap-css": "~2.3.2",
11+
"flat-ui-official": "~2.0.0",
12+
"jquery": "~2.0.3",
13+
"lodash": "~2.4.1"
14+
},
15+
"devDependencies": {
16+
"angular-mocks": "~1.2.6",
17+
"angular-scenario": "~1.2.6"
18+
}
19+
}

‎app/templates/_generator.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<%= generatorConfigStr %>

‎app/templates/_package.json

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"name": "<%= _.slugify(baseName) %>",
3+
"version": "0.0.0",
4+
"description": "Description for <%= baseName %>",
5+
"dependencies": {},
6+
"devDependencies": {
7+
"grunt": "~0.4.2",
8+
"grunt-autoprefixer": "~0.4.2",
9+
"grunt-bowercopy": "~0.4.1",
10+
"grunt-bower-install": "~0.7.0",
11+
"grunt-concurrent": "~0.4.2",
12+
"grunt-connect-proxy": "~0.1.7",
13+
"grunt-contrib-clean": "~0.5.0",
14+
"grunt-contrib-concat": "~0.3.0",
15+
"grunt-contrib-connect": "~0.5.0",
16+
"grunt-contrib-copy": "~0.4.1",
17+
"grunt-contrib-cssmin": "~0.7.0",
18+
"grunt-contrib-htmlmin": "~0.1.3",
19+
"grunt-contrib-imagemin": "~0.4.0",
20+
"grunt-contrib-jshint": "~0.7.2",
21+
"grunt-contrib-uglify": "~0.2.7",
22+
"grunt-contrib-watch": "~0.5.3",
23+
"grunt-karma": "~0.6.2",
24+
"grunt-modernizr": "~0.4.1",
25+
"grunt-ngmin": "~0.0.3",
26+
"grunt-rev": "~0.1.0",
27+
"grunt-svgmin": "~0.3.0",
28+
"grunt-sync": "~0.0.5",
29+
"grunt-usemin": "~2.0.2",
30+
"load-grunt-tasks": "~0.2.0",
31+
"time-grunt": "0.2.3",
32+
"karma" : "~0.10.8",
33+
"karma-junit-reporter" : "~0.1.0",
34+
"karma-jasmine" : "~0.1.0",
35+
"karma-ng-scenario" : "~0.1.0"
36+
},
37+
"engines": {
38+
"node": ">=0.8.15"
39+
}
40+
}

‎app/templates/_server.go

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package main
2+
3+
import (
4+
"./models"
5+
"./routes"
6+
//"log"
7+
"net/http"
8+
"regexp"
9+
"strings"
10+
"github.com/codegangsta/martini"
11+
"github.com/coopernurse/gorp"
12+
)
13+
14+
// The one and only martini instance.
15+
var m *martini.Martini
16+
17+
func init() {
18+
m = martini.New()
19+
// Setup middleware
20+
m.Use(martini.Recovery())
21+
m.Use(martini.Logger())
22+
m.Use(martini.Static("public"))
23+
m.Use(MapEncoder)
24+
// Setup routes
25+
r := martini.NewRouter()
26+
<% _.each(entities, function (entity) { %>
27+
r.Get(`/<%= baseName %>/<%= pluralize(entity.name) %>`, routes.Get<%= _.capitalize(pluralize(entity.name)) %>)
28+
r.Get(`/<%= baseName %>/<%= pluralize(entity.name) %>/:id`, routes.Get<%= _.capitalize(entity.name) %>)
29+
r.Post(`/<%= baseName %>/<%= pluralize(entity.name) %>`, routes.Add<%= _.capitalize(entity.name) %>)
30+
r.Put(`/<%= baseName %>/<%= pluralize(entity.name) %>/:id`, routes.Update<%= _.capitalize(entity.name) %>)
31+
r.Delete(`/<%= baseName %>/<%= pluralize(entity.name) %>/:id`, routes.Delete<%= _.capitalize(entity.name) %>)
32+
<% }); %>
33+
// Inject database
34+
m.MapTo(models.Dbm, (*gorp.SqlExecutor)(nil))
35+
// Add the router action
36+
m.Action(r.Handle)
37+
}
38+
39+
// The regex to check for the requested format (allows an optional trailing
40+
// slash).
41+
var rxExt = regexp.MustCompile(`(\.(?:xml|text|json))\/?$`)
42+
43+
// MapEncoder intercepts the request's URL, detects the requested format,
44+
// and injects the correct encoder dependency for this request. It rewrites
45+
// the URL to remove the format extension, so that routes can be defined
46+
// without it.
47+
func MapEncoder(c martini.Context, w http.ResponseWriter, r *http.Request) {
48+
// Get the format extension
49+
matches := rxExt.FindStringSubmatch(r.URL.Path)
50+
ft := ".json"
51+
if len(matches) > 1 {
52+
// Rewrite the URL without the format extension
53+
l := len(r.URL.Path) - len(matches[1])
54+
if strings.HasSuffix(r.URL.Path, "/") {
55+
l--
56+
}
57+
r.URL.Path = r.URL.Path[:l]
58+
ft = matches[1]
59+
}
60+
// Inject the requested encoder
61+
switch ft {
62+
case ".xml":
63+
c.MapTo(routes.XmlEncoder{}, (*routes.Encoder)(nil))
64+
w.Header().Set("Content-Type", "application/xml")
65+
case ".text":
66+
c.MapTo(routes.TextEncoder{}, (*routes.Encoder)(nil))
67+
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
68+
default:
69+
c.MapTo(routes.JsonEncoder{}, (*routes.Encoder)(nil))
70+
w.Header().Set("Content-Type", "application/json")
71+
}
72+
}
73+
74+
func main() {
75+
m.Run()
76+
}

‎app/templates/bowerrc

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"directory": "public/lib",
3+
"json": "bower.json"
4+
}

‎app/templates/editorconfig

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# http://editorconfig.org
2+
root = true
3+
4+
[*]
5+
indent_style = space
6+
indent_size = 4
7+
end_of_line = lf
8+
charset = utf-8
9+
trim_trailing_whitespace = true
10+
insert_final_newline = true
11+
12+
[*.md]
13+
trim_trailing_whitespace = false

‎app/templates/encoding

7.3 MB
Binary file not shown.

‎app/templates/gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.DS_Store
2+
*.swp
3+
*~
4+
node_modules

‎app/templates/jshintrc

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"node": true,
3+
"esnext": true,
4+
"bitwise": true,
5+
"camelcase": true,
6+
"curly": true,
7+
"eqeqeq": true,
8+
"immed": true,
9+
"indent": 4,
10+
"latedef": true,
11+
"newcap": true,
12+
"noarg": true,
13+
"quotmark": "single",
14+
"regexp": true,
15+
"undef": true,
16+
"unused": true,
17+
"strict": true,
18+
"trailing": true,
19+
"smarttabs": true,
20+
"white": true
21+
}

‎app/templates/models/_gorp.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package models
2+
3+
import (
4+
"database/sql"
5+
"log"
6+
//"time"
7+
"github.com/coopernurse/gorp"
8+
_ "github.com/mattn/go-sqlite3"
9+
)
10+
11+
var (
12+
Dbm *gorp.DbMap
13+
)
14+
15+
func init() {
16+
log.Println("Opening db...")
17+
db, err := sql.Open("sqlite3", "/tmp/my.db")
18+
checkErr(err, "sql.Open failed")
19+
Dbm = &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}}
20+
21+
<% _.each(entities, function (entity) { %>
22+
Dbm.AddTableWithName(<%= _.capitalize(entity.name) %>{}, "<%= pluralize(entity.name) %>").SetKeys(true, "Id")
23+
<% }); %>
24+
25+
//Dbm.TraceOn("[gorp]", r.INFO)
26+
err = Dbm.CreateTablesIfNotExists()
27+
checkErr(err, "Create tables failed")
28+
29+
}
30+
31+
func checkErr(err error, msg string) {
32+
if err != nil {
33+
log.Fatalln(msg, err)
34+
}
35+
}

‎app/templates/public/_index.html

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<!doctype html>
2+
<html lang="en" ng-app="<%= baseName %>">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title><%= _.capitalize(baseName) %></title>
8+
<link href="lib/bootstrap-css/css/bootstrap.css" rel="stylesheet">
9+
<link href="lib/bootstrap-css/css/bootstrap-responsive.css" rel="stylesheet">
10+
<link href="lib/flat-ui-official/css/flat-ui.css" rel="stylesheet">
11+
<link href="lib/jquery-ui/themes/smoothness/jquery-ui.css" rel="stylesheet"/>
12+
<link href="css/app.css" rel="stylesheet">
13+
</head>
14+
<body>
15+
<div class="container-fluid">
16+
<div class="row-fluid">
17+
<div class="navbar navbar-inverse">
18+
<div class="navbar-inner">
19+
<div class="container-fluid">
20+
<a class="brand" href="#"><%= _.capitalize(baseName) %></a>
21+
<ul class="nav">
22+
<% _.each(entities, function (entity) { %>
23+
<li>
24+
<a href="/#/<%= pluralize(entity.name) %>"><%= _.capitalize(pluralize(entity.name)) %></a>
25+
</li>
26+
<% }); %>
27+
</ul>
28+
</div>
29+
</div>
30+
</div>
31+
</div>
32+
</div>
33+
<div class="container-fluid" ng-view></div>
34+
<script src="lib/jquery/jquery.js"></script>
35+
<script src="lib/jquery-ui/ui/jquery-ui.js"></script>
36+
<script src="lib/lodash/dist/lodash.js"></script>
37+
<script src="lib/angular/angular.js"></script>
38+
<script src="lib/angular-resource/angular-resource.js"></script>
39+
<script src="lib/angular-route/angular-route.js"></script>
40+
<script src="lib/angular-bootstrap/ui-bootstrap-tpls.js"></script>
41+
<script src="lib/angular-ui-date/src/date.js"></script>
42+
43+
<script src="js/app.js"></script>
44+
<script src="js/home/home-controller.js"></script>
45+
<% _.each(entities, function (entity) { %>
46+
<script src="js/<%= entity.name %>/<%= entity.name %>-controller.js"></script>
47+
<script src="js/<%= entity.name %>/<%= entity.name %>-router.js"></script>
48+
<script src="js/<%= entity.name %>/<%= entity.name %>-service.js"></script>
49+
<% }); %>
50+
</body>
51+
</html>

‎app/templates/public/css/app.css

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
body {
2+
padding: 10px;
3+
background-color: #ECF0F1;
4+
}

‎app/templates/public/js/_app.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Declare app level module which depends on filters, and services
2+
angular.module('<%= baseName %>', ['ngResource', 'ngRoute', 'ui.bootstrap', 'ui.date'])
3+
.config(['$routeProvider', function ($routeProvider) {
4+
$routeProvider
5+
.when('/', {
6+
templateUrl: 'views/home/home.html',
7+
controller: 'HomeController'})
8+
.otherwise({redirectTo: '/'});
9+
}]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
angular.module('<%= baseName %>')
2+
.controller('HomeController', ['$scope', function ($scope) {
3+
}]);
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<div class="container-fluid">
2+
<div class="row">
3+
<div class="col-md-8">
4+
<h1>Welcome to <%= _.capitalize(baseName) %>!</h1>
5+
<p class="lead">This is your homepage, ready for editing</p>
6+
7+
<p>
8+
<span translate="main.like">If you like this Angular-Go-Martini generator, please give us a star at </span>&nbsp;<a href="https://github.com/rayokota/generator-angular-go-martini" target="_blank" translate="main.github">Github</a>!
9+
</p>
10+
</div>
11+
</div>
12+
</div>

‎app/templates/routes/_encoding.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package routes
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"encoding/xml"
7+
"fmt"
8+
)
9+
10+
// An Encoder implements an encoding format of values to be sent as response to
11+
// requests on the API endpoints.
12+
type Encoder interface {
13+
EncodeOne(v interface{}) (string, error)
14+
Encode(v ...interface{}) (string, error)
15+
}
16+
17+
// Because `panic`s are caught by martini's Recovery handler, it can be used
18+
// to return server-side errors (500). Some helpful text message should probably
19+
// be sent, although not the technical error (which is printed in the log).
20+
func Must(data string, err error) string {
21+
if err != nil {
22+
panic(err)
23+
}
24+
return data
25+
}
26+
27+
type JsonEncoder struct{}
28+
29+
// JsonEncoder is an Encoder that produces JSON-formatted responses.
30+
func (_ JsonEncoder) EncodeOne(v interface{}) (string, error) {
31+
var data interface{} = v
32+
b, err := json.Marshal(data)
33+
return string(b), err
34+
}
35+
36+
func (_ JsonEncoder) Encode(v ...interface{}) (string, error) {
37+
var data interface{} = v
38+
if v == nil {
39+
// So that empty results produces `[]` and not `null`
40+
data = []interface{}{}
41+
}
42+
b, err := json.Marshal(data)
43+
return string(b), err
44+
}
45+
46+
type XmlEncoder struct{}
47+
48+
// XmlEncoder is an Encoder that produces XML-formatted responses.
49+
func (e XmlEncoder) EncodeOne(v interface{}) (string, error) {
50+
return e.Encode(v)
51+
}
52+
53+
func (_ XmlEncoder) Encode(v ...interface{}) (string, error) {
54+
var buf bytes.Buffer
55+
if _, err := buf.Write([]byte(xml.Header)); err != nil {
56+
return "", err
57+
}
58+
if _, err := buf.Write([]byte("<result>")); err != nil {
59+
return "", err
60+
}
61+
b, err := xml.Marshal(v)
62+
if err != nil {
63+
return "", err
64+
}
65+
if _, err := buf.Write(b); err != nil {
66+
return "", err
67+
}
68+
if _, err := buf.Write([]byte("</result>")); err != nil {
69+
return "", err
70+
}
71+
return buf.String(), nil
72+
}
73+
74+
type TextEncoder struct{}
75+
76+
// TextEncoder is an Encoder that produces plain text-formatted responses.
77+
func (e TextEncoder) EncodeOne(v ...interface{}) (string, error) {
78+
return e.Encode(v)
79+
}
80+
81+
func (_ TextEncoder) Encode(v ...interface{}) (string, error) {
82+
var buf bytes.Buffer
83+
for _, v := range v {
84+
if _, err := fmt.Fprintf(&buf, "%s\n", v); err != nil {
85+
return "", err
86+
}
87+
}
88+
return buf.String(), nil
89+
}

‎entity/index.js

+194
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
'use strict';
2+
var util = require('util'),
3+
yeoman = require('yeoman-generator'),
4+
fs = require('fs'),
5+
_ = require('lodash'),
6+
_s = require('underscore.string'),
7+
pluralize = require('pluralize');
8+
9+
var EntityGenerator = module.exports = function EntityGenerator(args, options, config) {
10+
// By calling `NamedBase` here, we get the argument to the subgenerator call
11+
// as `this.name`.
12+
yeoman.generators.NamedBase.apply(this, arguments);
13+
14+
console.log('You called the entity subgenerator with the argument ' + this.name + '.');
15+
16+
fs.readFile('generator.json', 'utf8', function (err, data) {
17+
if (err) {
18+
console.log('Error: ' + err);
19+
return;
20+
}
21+
this.generatorConfig = JSON.parse(data);
22+
}.bind(this));
23+
};
24+
25+
util.inherits(EntityGenerator, yeoman.generators.NamedBase);
26+
27+
EntityGenerator.prototype.askFor = function askFor() {
28+
var cb = this.async();
29+
30+
console.log('\nPlease specify an attribute:');
31+
32+
var prompts = [{
33+
type: 'input',
34+
name: 'attrName',
35+
message: 'What is the name of the attribute?',
36+
default: 'myattr'
37+
},
38+
{
39+
type: 'list',
40+
name: 'attrType',
41+
message: 'What is the type of the attribute?',
42+
choices: ['String', 'Integer', 'Float', 'Boolean', 'Date', 'Enum'],
43+
default: 'String'
44+
},
45+
{
46+
when: function (props) { return (/String/).test(props.attrType); },
47+
type: 'input',
48+
name: 'minLength',
49+
message: 'Enter the minimum length for the String attribute, or hit enter:',
50+
validate: function (input) {
51+
if (input && isNaN(input)) {
52+
return "Please enter a number.";
53+
}
54+
return true;
55+
}
56+
},
57+
{
58+
when: function (props) { return (/String/).test(props.attrType); },
59+
type: 'input',
60+
name: 'maxLength',
61+
message: 'Enter the maximum length for the String attribute, or hit enter:',
62+
validate: function (input) {
63+
if (input && isNaN(input)) {
64+
return "Please enter a number.";
65+
}
66+
return true;
67+
}
68+
},
69+
{
70+
when: function (props) { return (/Integer|Float/).test(props.attrType); },
71+
type: 'input',
72+
name: 'min',
73+
message: 'Enter the minimum value for the numeric attribute, or hit enter:',
74+
validate: function (input) {
75+
if (input && isNaN(input)) {
76+
return "Please enter a number.";
77+
}
78+
return true;
79+
}
80+
},
81+
{
82+
when: function (props) { return (/Integer|Float/).test(props.attrType); },
83+
type: 'input',
84+
name: 'max',
85+
message: 'Enter the maximum value for the numeric attribute, or hit enter:',
86+
validate: function (input) {
87+
if (input && isNaN(input)) {
88+
return "Please enter a number.";
89+
}
90+
return true;
91+
}
92+
},
93+
{
94+
when: function (props) { return (/Date/).test(props.attrType); },
95+
type: 'list',
96+
name: 'dateConstraint',
97+
message: 'Constrain the date as follows:',
98+
choices: ['None', 'Past dates only', 'Future dates only'],
99+
filter: function (input) {
100+
if (/Past/.test(input)) return 'Past';
101+
if (/Future/.test(input)) return 'Future';
102+
return '';
103+
},
104+
default: 'None'
105+
},
106+
{
107+
when: function (props) { return (/Enum/).test(props.attrType); },
108+
type: 'input',
109+
name: 'enumValues',
110+
message: 'Enter an enumeration of values, separated by commas'
111+
},
112+
{
113+
type: 'confirm',
114+
name: 'required',
115+
message: 'Is the attribute required to have a value?',
116+
default: true
117+
},
118+
{
119+
type: 'confirm',
120+
name: 'again',
121+
message: 'Would you like to enter another attribute or reenter a previous attribute?',
122+
default: true
123+
}];
124+
125+
this.prompt(prompts, function (props) {
126+
this.attrs = this.attrs || [];
127+
var attrType = props.attrType;
128+
var attrImplType = props.attrType;
129+
if (attrType === 'String') {
130+
attrImplType = 'string';
131+
} else if (attrType === 'Integer') {
132+
attrImplType = 'int64';
133+
} else if (attrType === 'Float') {
134+
attrImplType = 'float64';
135+
} else if (attrType === 'Boolean') {
136+
attrImplType = 'bool';
137+
} else if (attrType === 'Date') {
138+
attrImplType = 'time.Time';
139+
} else if (attrType === 'Enum') {
140+
attrImplType = 'enum';
141+
}
142+
this.attrs = _.reject(this.attrs, function (attr) { return attr.attrName === props.attrName; });
143+
this.attrs.push({
144+
attrName: props.attrName,
145+
attrType: attrType,
146+
attrImplType: attrImplType,
147+
minLength: props.minLength,
148+
maxLength: props.maxLength,
149+
min: props.min,
150+
max: props.max,
151+
dateConstraint: props.dateConstraint,
152+
enumValues: props.enumValues ? props.enumValues.split(',') : [],
153+
required: props.required
154+
});
155+
156+
if (props.again) {
157+
this.askFor();
158+
} else {
159+
cb();
160+
}
161+
}.bind(this));
162+
};
163+
164+
EntityGenerator.prototype.files = function files() {
165+
166+
this.baseName = this.generatorConfig.baseName;
167+
this.packageName = this.generatorConfig.packageName;
168+
this.entities = this.generatorConfig.entities;
169+
this.entities = _.reject(this.entities, function (entity) { return entity.name === this.name; }.bind(this));
170+
this.entities.push({ name: this.name, attrs: this.attrs});
171+
this.pluralize = pluralize;
172+
this.generatorConfig.entities = this.entities;
173+
this.generatorConfigStr = JSON.stringify(this.generatorConfig, null, '\t');
174+
175+
this.template('_generator.json', 'generator.json');
176+
this.template('../../app/templates/_server.go', 'server.go');
177+
this.template('../../app/templates/models/_gorp.go', 'models/gorp.go');
178+
this.template('models/_entity.go', 'models/' + this.name + '.go');
179+
this.template('routes/_entities.go', 'routes/' + pluralize(this.name) + '.go');
180+
181+
var publicDir = 'public/';
182+
var publicCssDir = publicDir + 'css/';
183+
var publicJsDir = publicDir + 'js/';
184+
var publicViewDir = publicDir + 'views/';
185+
var publicEntityJsDir = publicJsDir + this.name + '/';
186+
var publicEntityViewDir = publicViewDir + this.name + '/';
187+
this.mkdir(publicEntityJsDir);
188+
this.mkdir(publicEntityViewDir);
189+
this.template('../../app/templates/public/_index.html', publicDir + 'index.html');
190+
this.template('public/js/entity/_entity-controller.js', publicEntityJsDir + this.name + '-controller.js');
191+
this.template('public/js/entity/_entity-router.js', publicEntityJsDir + this.name + '-router.js');
192+
this.template('public/js/entity/_entity-service.js', publicEntityJsDir + this.name + '-service.js');
193+
this.template('public/views/entity/_entities.html', publicEntityViewDir + pluralize(this.name) + '.html');
194+
};

‎entity/templates/_generator.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<%= generatorConfigStr %>

‎entity/templates/models/_entity.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package models
2+
3+
import "time"
4+
5+
var date time.Time
6+
7+
type <%= _.capitalize(name) %> struct {
8+
Id int `json:"id"`
9+
<% _.each(attrs, function (attr) { %>
10+
<%= _.capitalize(attr.attrName) %> <% if (attr.attrType == 'Enum') { %>string<% } else { %><%= attr.attrImplType %><% }; %> `json:"<%= attr.attrName %>"`
11+
<% }); %>
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
'use strict';
2+
3+
angular.module('<%= baseName %>')
4+
.controller('<%= _.capitalize(name) %>Controller', ['$scope', '$modal', 'resolved<%= _.capitalize(name) %>', '<%= _.capitalize(name) %>',
5+
function ($scope, $modal, resolved<%= _.capitalize(name) %>, <%= _.capitalize(name) %>) {
6+
7+
$scope.<%= pluralize(name) %> = resolved<%= _.capitalize(name) %>;
8+
9+
$scope.create = function () {
10+
$scope.clear();
11+
$scope.open();
12+
};
13+
14+
$scope.update = function (id) {
15+
$scope.<%= name %> = <%= _.capitalize(name) %>.get({id: id});
16+
$scope.open(id);
17+
};
18+
19+
$scope.delete = function (id) {
20+
<%= _.capitalize(name) %>.delete({id: id},
21+
function () {
22+
$scope.<%= pluralize(name) %> = <%= _.capitalize(name) %>.query();
23+
});
24+
};
25+
26+
$scope.save = function (id) {
27+
if (id) {
28+
<%= _.capitalize(name) %>.update({id: id}, $scope.<%= name %>,
29+
function () {
30+
$scope.<%= pluralize(name) %> = <%= _.capitalize(name) %>.query();
31+
$scope.clear();
32+
});
33+
} else {
34+
<%= _.capitalize(name) %>.save($scope.<%= name %>,
35+
function () {
36+
$scope.<%= pluralize(name) %> = <%= _.capitalize(name) %>.query();
37+
$scope.clear();
38+
});
39+
}
40+
};
41+
42+
$scope.clear = function () {
43+
$scope.<%= name %> = {
44+
<% _.each(attrs, function (attr) { %>
45+
"<%= attr.attrName %>": "",
46+
<% }); %>
47+
"id": ""
48+
};
49+
};
50+
51+
$scope.open = function (id) {
52+
var <%= name %>Save = $modal.open({
53+
templateUrl: '<%= name %>-save.html',
54+
controller: <%= _.capitalize(name) %>SaveController,
55+
resolve: {
56+
<%= name %>: function () {
57+
return $scope.<%= name %>;
58+
}
59+
}
60+
});
61+
62+
<%= name %>Save.result.then(function (entity) {
63+
$scope.<%= name %> = entity;
64+
$scope.save(id);
65+
});
66+
};
67+
}]);
68+
69+
var <%= _.capitalize(name) %>SaveController =
70+
function ($scope, $modalInstance, <%= name %>) {
71+
$scope.<%= name %> = <%= name %>;
72+
73+
<% _.each(attrs, function (attr) { if (attr.attrType === 'Date') { %>
74+
$scope.<%= attr.attrName %>DateOptions = {
75+
dateFormat: 'yy-mm-dd',
76+
<% if (attr.dateConstraint === 'Past') { %>maxDate: -1<% } %>
77+
<% if (attr.dateConstraint === 'Future') { %>minDate: 1<% } %>
78+
};<% }}); %>
79+
80+
$scope.ok = function () {
81+
$modalInstance.close($scope.<%= name %>);
82+
};
83+
84+
$scope.cancel = function () {
85+
$modalInstance.dismiss('cancel');
86+
};
87+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
angular.module('<%= baseName %>')
4+
.config(['$routeProvider', function ($routeProvider) {
5+
$routeProvider
6+
.when('/<%= pluralize(name) %>', {
7+
templateUrl: 'views/<%= name %>/<%= pluralize(name) %>.html',
8+
controller: '<%= _.capitalize(name) %>Controller',
9+
resolve:{
10+
resolved<%= _.capitalize(name) %>: ['<%= _.capitalize(name) %>', function (<%= _.capitalize(name) %>) {
11+
return <%= _.capitalize(name) %>.query();
12+
}]
13+
}
14+
})
15+
}]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
'use strict';
2+
3+
angular.module('<%= baseName %>')
4+
.factory('<%= _.capitalize(name) %>', ['$resource', function ($resource) {
5+
return $resource('<%= baseName %>/<%= pluralize(name) %>/:id', {}, {
6+
'query': { method: 'GET', isArray: true},
7+
'get': { method: 'GET'},
8+
'update': { method: 'PUT'}
9+
});
10+
}]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<div>
2+
3+
<h2><%= _.capitalize(pluralize(name)) %></h2>
4+
5+
<script type = "text/ng-template" id="<%= name %>-save.html">
6+
<div class="modal-dialog">
7+
<div class="modal-content">
8+
<form name="form" role="form" novalidate
9+
class="ng-scope ng-invalid ng-invalid-required ng-dirty"
10+
ng-submit="ok()">
11+
12+
<div class="modal-header">
13+
<button type="button" class="close"
14+
ng-click="cancel()">&times;</button>
15+
<h4 class="modal-title" id="my<%= _.capitalize(name) %>Label">Create or edit a <%= _.capitalize(name) %></h4>
16+
</div>
17+
<div class="modal-body">
18+
<div class="form-group">
19+
<label>ID</label>
20+
<input type="text" class="form-control" name="id"
21+
ng-model="<%= name %>.id" readonly>
22+
</div>
23+
24+
<% _.each(attrs, function (attr) { %>
25+
<div class="form-group">
26+
<label><%= attr.attrName %>:</label>
27+
<% if (attr.attrType === "String") { %>
28+
<input type="text" class="form-control" name="<%= attr.attrName %>"
29+
ng-model="<%= name %>.<%= attr.attrName %>" ng-required="<%= attr.required %>"
30+
<% if (attr.minLength) { %>ng-minlength=<%= attr.minLength %> <% } %><% if (attr.maxLength) { %>ng-maxlength=<%= attr.maxLength %><% } %>/>
31+
<span class="error" ng-show="form.<%= attr.attrName %>.$error.minlength">Must be at least <%= attr.minLength %> characters.</span>
32+
<span class="error" ng-show="form.<%= attr.attrName %>.$error.maxlength">Must be at most <%= attr.maxLength %> characters.</span><%
33+
} else if (attr.attrType === "Integer") { %>
34+
<input type="number" class="form-control" name="<%= attr.attrName %>"
35+
ng-model="<%= name %>.<%= attr.attrName %>" ng-required="<%= attr.required %>"
36+
<% if (attr.min) { %>min=<%= attr.min%> <% } %><% if (attr.max) { %>max=<%= attr.max %><% } %>/>
37+
<span class="error" ng-show="form.<%= attr.attrName %>.$error.min">Must be greater than or equal to <%= attr.min %>.</span>
38+
<span class="error" ng-show="form.<%= attr.attrName %>.$error.max">Must be less than or equal to <%= attr.max %>.</span><%
39+
} else if (attr.attrType === "Float") { %>
40+
<input type="number" step="any" class="form-control" name="<%= attr.attrName %>"
41+
ng-model="<%= name %>.<%= attr.attrName %>" ng-required="<%= attr.required %>"
42+
<% if (attr.min) { %>min=<%= attr.min%> <% } %><% if (attr.max) { %>max=<%= attr.max %><% } %>/>
43+
<span class="error" ng-show="form.<%= attr.attrName %>.$error.min">Must be greater than or equal to <%= attr.min %>.</span>
44+
<span class="error" ng-show="form.<%= attr.attrName %>.$error.max">Must be less than or equal to <%= attr.max %>.</span><%
45+
} else if (attr.attrType === "Boolean") { %>
46+
<input type="checkbox" class="form-control" name="<%= attr.attrName %>"
47+
ng-model="<%= name %>.<%= attr.attrName %>" ng-required="<%= attr.required %>"/><%
48+
} else if (attr.attrType === "Date") { %>
49+
<input type="text" class="form-control" name="<%= attr.attrName %>" ui-date="<%= attr.attrName %>DateOptions" ui-date-format="yy-mm-dd"
50+
ng-model="<%= name %>.<%= attr.attrName %>" ng-required="<%= attr.required %>"/><%
51+
} else if (attr.attrType === "Enum") {
52+
_.each(attr.enumValues, function (value) { %>
53+
<input type="radio" class="form-control" name="<%= attr.attrName %>" value="<%= value %>"
54+
ng-model="<%= name %>.<%= attr.attrName %>" ng-required="<%= attr.required %>"/> <%= value %> <br/><% })} %>
55+
</div>
56+
<% }); %>
57+
58+
</div>
59+
<div class="modal-footer">
60+
<button type="button" class="btn btn-default" ng-click="cancel()">Cancel
61+
</button>
62+
<button type="submit" ng-disabled="form.$invalid" class="btn btn-primary">Save</button>
63+
</div>
64+
</form>
65+
</div>
66+
</div>
67+
</script>
68+
69+
<button class="btn btn-primary btn-lg" ng-click="create()">
70+
<span class="glyphicon glyphicon-flash"></span> Create a new <%= _.capitalize(name) %>
71+
</button>
72+
73+
<div class="table-responsive">
74+
<table class="table table-striped">
75+
<thead>
76+
<tr>
77+
<th>ID</th>
78+
<% _.each(attrs, function (attr) { %>
79+
<th><%= attr.attrName %></th>
80+
<% }); %>
81+
</tr>
82+
</thead>
83+
<tbody>
84+
<tr ng-repeat="<%= name %> in <%= pluralize(name) %>">
85+
<td>{{<%= name %>.id}}</td>
86+
<% _.each(attrs, function (attr) { %>
87+
<td>{{<%= name %>.<%= attr.attrName %> <% if (attr.attrType === 'Date') { %> | date:'yyyy-MM-dd'<% } %>}}</td>
88+
<% }); %>
89+
<td>
90+
<button type="submit"
91+
ng-click="update(<%= name %>.id)"
92+
class="btn">
93+
<span class="glyphicon glyphicon-pencil"></span> Edit
94+
</button>
95+
<button type="submit"
96+
ng-click="delete(<%= name %>.id)"
97+
class="btn btn-danger">
98+
<span class="glyphicon glyphicon-remove-circle"></span> Delete
99+
</button>
100+
</td>
101+
</tr>
102+
</tbody>
103+
</table>
104+
</div>
105+
</div>

‎entity/templates/routes/_entities.go

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package routes
2+
3+
import (
4+
"../models"
5+
"encoding/json"
6+
"fmt"
7+
"log"
8+
"net/http"
9+
"strconv"
10+
"github.com/codegangsta/martini"
11+
"github.com/coopernurse/gorp"
12+
)
13+
14+
func Get<%= _.capitalize(pluralize(name)) %>(r *http.Request, enc Encoder, db gorp.SqlExecutor) (int, string) {
15+
var <%= pluralize(name) %> []models.<%= _.capitalize(name) %>
16+
_, err := db.Select(&<%= pluralize(name) %>, "select * from <%= pluralize(name) %> order by id")
17+
if err != nil {
18+
checkErr(err, "select failed")
19+
return http.StatusInternalServerError, ""
20+
}
21+
return http.StatusOK, Must(enc.Encode(toIface(<%= pluralize(name) %>)...))
22+
}
23+
24+
func Get<%= _.capitalize(name) %>(enc Encoder, db gorp.SqlExecutor, parms martini.Params) (int, string) {
25+
id, err := strconv.Atoi(parms["id"])
26+
obj, _ := db.Get(models.<%= _.capitalize(name) %>{}, id)
27+
if err != nil || obj == nil {
28+
checkErr(err, "get failed")
29+
// Invalid id, or does not exist
30+
return http.StatusNotFound, ""
31+
}
32+
entity := obj.(*models.<%= _.capitalize(name) %>)
33+
return http.StatusOK, Must(enc.EncodeOne(entity))
34+
}
35+
36+
func Add<%= _.capitalize(name) %>(w http.ResponseWriter, r *http.Request, enc Encoder, db gorp.SqlExecutor) (int, string) {
37+
decoder := json.NewDecoder(r.Body)
38+
var entity models.<%= _.capitalize(name) %>
39+
decoder.Decode(&entity)
40+
err := db.Insert(&entity)
41+
if err != nil {
42+
checkErr(err, "insert failed")
43+
return http.StatusConflict, ""
44+
}
45+
w.Header().Set("Location", fmt.Sprintf("/<%= baseName %>/<%= pluralize(name) %>/%d", entity.Id))
46+
return http.StatusCreated, Must(enc.EncodeOne(entity))
47+
}
48+
49+
func Update<%= _.capitalize(name) %>(r *http.Request, enc Encoder, db gorp.SqlExecutor, parms martini.Params) (int, string) {
50+
id, err := strconv.Atoi(parms["id"])
51+
obj, _ := db.Get(models.<%= _.capitalize(name) %>{}, id)
52+
if err != nil || obj == nil {
53+
checkErr(err, "get failed")
54+
// Invalid id, or does not exist
55+
return http.StatusNotFound, ""
56+
}
57+
oldEntity := obj.(*models.<%= _.capitalize(name) %>)
58+
59+
decoder := json.NewDecoder(r.Body)
60+
var entity models.<%= _.capitalize(name) %>
61+
decoder.Decode(&entity)
62+
entity.Id = oldEntity.Id
63+
_, err = db.Update(&entity)
64+
if err != nil {
65+
checkErr(err, "update failed")
66+
return http.StatusConflict, ""
67+
}
68+
return http.StatusOK, Must(enc.EncodeOne(entity))
69+
}
70+
71+
func Delete<%= _.capitalize(name) %>(enc Encoder, db gorp.SqlExecutor, parms martini.Params) (int, string) {
72+
id, err := strconv.Atoi(parms["id"])
73+
obj, _ := db.Get(models.<%= _.capitalize(name) %>{}, id)
74+
if err != nil || obj == nil {
75+
checkErr(err, "get failed")
76+
// Invalid id, or does not exist
77+
return http.StatusNotFound, ""
78+
}
79+
entity := obj.(*models.<%= _.capitalize(name) %>)
80+
_, err = db.Delete(entity)
81+
if err != nil {
82+
checkErr(err, "delete failed")
83+
return http.StatusConflict, ""
84+
}
85+
return http.StatusNoContent, ""
86+
}
87+
88+
func toIface(v []models.<%= _.capitalize(name) %>) []interface{} {
89+
if len(v) == 0 {
90+
return nil
91+
}
92+
ifs := make([]interface{}, len(v))
93+
for i, v := range v {
94+
ifs[i] = v
95+
}
96+
return ifs
97+
}
98+
99+
func checkErr(err error, msg string) {
100+
if err != nil {
101+
log.Fatalln(msg, err)
102+
}
103+
}

‎package.json

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"name": "generator-angular-go-martini",
3+
"version": "0.1.0",
4+
"description": "A Yeoman generator for AngularJS + Go + Martini",
5+
"keywords": [
6+
"yeoman-generator",
7+
"go",
8+
"golang",
9+
"Martini",
10+
"gorp",
11+
"AngularJS",
12+
"Twitter Bootstrap"
13+
],
14+
"homepage": "https://github.com/rayokota/generator-angular-go-martini",
15+
"bugs": "https://github.com/rayokota/generator-angular-go-martini/issues",
16+
"author": {
17+
"name": "Robert Yokota",
18+
"email": "",
19+
"url": "https://github.com/rayokota"
20+
},
21+
"main": "app/index.js",
22+
"repository": {
23+
"type": "git",
24+
"url": "git://github.com/rayokota/generator-angular-go-martini.git"
25+
},
26+
"scripts": {
27+
"test": "mocha"
28+
},
29+
"dependencies": {
30+
"yeoman-generator": "~0.14.0",
31+
"URIjs": "~1.11.2",
32+
"lodash": "~2.4.1",
33+
"underscore.string": "~2.3.3",
34+
"pluralize": "~0.0.6",
35+
"asciify": "~1.3.3"
36+
},
37+
"devDependencies": {
38+
"mocha": "~1.14.0"
39+
},
40+
"peerDependencies": {
41+
"yo": ">=1.0.0"
42+
},
43+
"engines": {
44+
"node": ">=0.8.0",
45+
"npm": ">=1.2.10"
46+
},
47+
"licenses": [
48+
{
49+
"type": "MIT"
50+
}
51+
]
52+
}

‎test/test-creation.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*global describe, beforeEach, it*/
2+
'use strict';
3+
4+
var path = require('path');
5+
var helpers = require('yeoman-generator').test;
6+
7+
8+
describe('angular-go-martini generator', function () {
9+
beforeEach(function (done) {
10+
helpers.testDirectory(path.join(__dirname, 'temp'), function (err) {
11+
if (err) {
12+
return done(err);
13+
}
14+
15+
this.app = helpers.createGenerator('angular-go-martini:app', [
16+
'../../app'
17+
]);
18+
done();
19+
}.bind(this));
20+
});
21+
22+
it('creates expected files', function (done) {
23+
var expected = [
24+
// add files you expect to exist here.
25+
'.jshintrc',
26+
'.editorconfig'
27+
];
28+
29+
helpers.mockPrompt(this.app, {
30+
'someOption': true
31+
});
32+
this.app.options['skip-install'] = true;
33+
this.app.run({}, function () {
34+
helpers.assertFiles(expected);
35+
done();
36+
});
37+
});
38+
});

‎test/test-load.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*global describe, beforeEach, it*/
2+
'use strict';
3+
4+
var assert = require('assert');
5+
6+
describe('angular-go-martini generator', function () {
7+
it('can be imported without blowing up', function () {
8+
var app = require('../app');
9+
assert(app !== undefined);
10+
});
11+
});

0 commit comments

Comments
 (0)
Please sign in to comment.