Skip to content

Instantly share code, notes, and snippets.

@rymizuki
Last active September 1, 2016 14:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rymizuki/ab99a288dfafa841983182eddc0408c9 to your computer and use it in GitHub Desktop.
Save rymizuki/ab99a288dfafa841983182eddc0408c9 to your computer and use it in GitHub Desktop.
2016-09-02.gotandajs.
'use strict'
const fs = require('fs')
const path = require('path')
const _ = require('lodash')
const protagonist = require('protagonist')
const filepath = path.join(__dirname, 'user.md')
const raw = fs.readFileSync(filepath, 'utf8')
let result
try {
result = protagonist.parseSync(raw)
} catch (e) {
console.log('ERROR', e)
}
function parse_content (content, previous) {
previous || (previous = {})
previous.current || (previous.current = {
namespace_index: -1,
resource_index: -1,
response_index: -1,
method: null,
uri: null,
parameters: null,
req_name: null,
})
previous.data || (previous.data = [])
if (content && Array.isArray(content)) {
content.forEach((element) => {
previous = parse_element(element, previous)
})
return previous
} else {
return
}
}
function parse_element (content, previous) {
switch (content.element) {
case 'category':
var namespace = to_namespace(content.meta.title)
if (namespace) {
var ns_index = ++previous.current.namespace_index
previous.data[ns_index] = {
namespace,
data: []
}
}
break;
case 'resource':
previous.current.uri = content.attributes.href
previous.current.method = null
break;
case 'transition':
var action_name = to_action_name(content.meta.title)
var params = to_fields(content.attributes.hrefVariables.content)
previous.current.action_name = action_name || 'get'
previous.current.parameters = params
break;
case 'httpRequest':
var index = previous.current.resource_index
var ns_index = previous.current.namespace_index
var action_name = previous.current.action_name
var uri = previous.current.uri
var uri_template = to_uri_template(uri)
var params = previous.current.parameters
var prev_method = previous.current.method
var method = content.attributes.method
previous.current.req_name = content.meta && content.meta.title ? content.meta.title : 'basic'
if (!prev_method || (prev_method != method)) {
index++
previous.current.resource_index = index
previous.current.method = method
previous.current.response_index = -1
previous.data[ns_index].data[index] || (previous.data[ns_index].data[index] = {})
previous.data[ns_index].data[index] = {
action_name,
uri,
uri_template,
method,
params,
responses: []
}
}
break;
case 'httpResponse':
var ns_index = previous.current.namespace_index
var index = previous.current.resource_index
var response_index = ++previous.current.response_index
var req_name = previous.current.req_name
var status_code = content.attributes.statusCode
previous.data[ns_index].data[index].responses[response_index] = {
req_name,
status_code,
}
break;
case 'dataStructure':
var ns_index = previous.current.namespace_index
var index = previous.current.resource_index
var response_index = previous.current.response_index
previous.data[ns_index].data[index].responses[response_index].fields
= to_fields(content.content)
default:
}
if (content.content && Array.isArray(content.content)) {
return parse_content(content.content, previous)
} else {
return previous
}
}
// CamelCase
function to_namespace (stuff) {
return to_action_name(stuff)
.replace(/^[a-z]{1}/, function (target) { return target.toUpperCase() })
}
// camelCase
function to_action_name (stuff) {
return stuff
.toLowerCase()
.replace(/ /g, '_')
.replace(/_([a-z]{1})/g, function (m, target) {
return target.toUpperCase()
})
}
function to_fields (content) {
if (!content || !Array.isArray(content)) return
return content.map((member) => {
if (member.element == 'member') {
let nullable = member.attributes && (_.includes(member.attributes.typeAttributes, 'nullable') || _.includes(member.attributes.typeAttributes, 'optional'))
return {
key: member.content.key.content,
value: /^object|array$/.test(member.content.value.element) ? to_fields(member.content.value.content)
: null,
type: member.content.value.element,
nullable: !!nullable,
}
} else if (member.element == 'object') {
return to_fields(member.content)
}
})
}
function to_uri_template (stuff) {
return stuff
.split(/\?/)[0]
.replace(/\{(.+?)\}/g, function (m, target) {
return `:${ target }`
})
}
function to_response_body (fields) {
let kolon = '=>'
return to_response_object('$entity', '=>', fields[0], 'object')
}
function to_response_object (context, op, fields, type) {
if (!fields) return 'undef,'
return '+{\n' + fields.map((field) => {
if (field.type == 'object') {
return `\t${ field.key } ${ op } ${ to_response_object(context+'->'+field.key, op, field.value, 'object') }`
} else if (field.type == 'array') {
return `\t${ field.key } ${ op } [ map { ${ to_response_object('$_', op, field.value[0], 'object') }} @${ context }->${ field.key }]`
} else {
return `\t${ field.key } ${ op } ${ field.type }(${ context }->${ field.key }),`
}
}).join('\n') + '\n\t},'
}
function parse (result) {
return parse_content(result.content)
}
//console.log(JSON.stringify(result, null, 2))
//console.log(JSON.stringify(result.content, null, 2))
const data = parse(result)
//console.log(JSON.stringify(data, null, 2))
const tmpl = _.template(`
use strict;
use warnings;
use utf8;
package Entity::User {
sub new {
my ($class, %data) = @_;
bless \\%data => $class;
}
sub name {
shift->{user_name};
}
sub age {
shift->{age} // 17;
}
1;
}
package Factory::User {
sub get {
my ($class, %args) = @_;
return Entity::User->new(%args);
}
}
package main {
use Amon2::Lite;
use JSON::Types;
use Try::Lite;
__PACKAGE__->load_plugins('Web::JSON');
<% data.forEach(function (namespace) { %><% namespace.data.forEach(function (action) { %>
<%= action.method.toLowerCase() %> '<%= action.uri_template %>' => sub {
my ($c, $args) = @_;
my %params = map {
$_ => $c->req->parameters->{$_} // $args->{$_}
} qw(<%= action.params.map(function (p) { return p.key }).join(' ') %>);
my ($entity);
try {
$entity = Factory::<%= namespace.namespace %>-><%= action.action_name %>(%params);
} (
# FIXME: exceptionケースの把握
<% action.responses.forEach(function (res) { %><% if (/^exception:/.test(res.req_name)) { %>
'Exception::<%= res.req_name.replace(/^exception:/,'') %>' => sub {
my $e = $@;
return $c->render_json(+{
status => <%= res.status_code %>,
error => +{
type => '<%= res.req_name %>',
reason => $e,
}
});
},
<% } %><% }) %>
'*' => sub {
# rethorw
die $@;
}
);
<% action.responses.forEach(function (res) { %><% if (res.req_name == 'basic') { %>
return $c->render_json(<%= to_response_body(res.fields) %>);
<% } %><% }) %>
};
<% }) %><% }) %>
__PACKAGE__->to_app;
}
`)
console.log(tmpl({
_,
to_response_body,
data: data.data,
}))
requires 'Plack';
requires 'Amon2::Lite';
requires 'JSON::Types';
requires 'Try::Lite';
{
"name": "node-example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"lodash": "^4.15.0",
"protagonist": "^1.5.0-pre.0"
}
}

GET /api/user/{user_name}

  • Parameters

    • user_name (string) ... ユーザの識別子
  • Response 200 (application/json)

    • Attributes

      • name (string) - ユーザの識別子
      • age (number) - 年齢
    • Body

        {
            name: "mizuki",
            age: 17,
        }
      
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment