oohara.yuukia
更新日:
go new API Template Generator
新しい形式のAPIのテンプレをざっくり作るやつ
<html>
<head>
<title>%title%</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/phi-jp/meltline@v0.1.15/meltline.min.css">
%style("main.css")%
%script("main.js")%
</head>
<body>
<main class="p16">
<h1 class="fs18 mb16">APIのテンプレを作るやつ</h1>
<form id=form submit="event.preventDefault();">
<div class="row gap-16">
<!-- API 名 -->
<label class="f fm mb16">
<span class="mr8">API名:</span>
<input id="name_of_api" placeholder="User"></input>
</label>
<label class="f fm mb16">
<span class="mr8">メソッド名:</span>
<input id="name_of_method" placeholder="Get"></input>
</label>
<label class="f fm mb16">
<span class="mr8">出力型パッケージ名:</span>
<input id="name_of_output_struct_package" placeholder="model" value="model"></input>
</label>
</div>
<div>
<label class="block mb16">
<div class="mb8">URL(改行区切り)</div>
<textarea id="url_params" placeholder="user_id"></textarea>
</label>
</div>
<div class="row gap-16 mb16">
<!-- ログインについて -->
<label class="f fm mb4">
<input name="login" class="mr8" type="radio" value="auth" /> ログイン必須
</label>
<label class="f fm mb4">
<input name="login" class="mr8" type="radio" value="opt" /> ログイン任意
</label>
<label class="f fm mb4">
<input name="login" class="mr8" type="radio" value="no"/> ログイン不要
</label>
</div>
<div class="row gap-16 mb16">
<label class="f fm mb4">
<input id=permission class="mr8" type="checkbox" /> Permission
</label>
<label class="f fm mb4">
<input id=after_input class="mr8" type="checkbox" /> AfterInput
</label>
<label class="f fm mb4">
<input id=is_list_check class="mr8" type="checkbox" /> List
</label>
</div>
</form>
<div id="outputs" class="row gap-16 mb16">
<div class="w300 flex-fixed">
<div class="f fbw fm mb4">
<span>handler</span>
<button type="button" data-copy=output_handler>コピー</button>
</div>
<textarea id="output_handler" readonly class="block w-full h100" ></textarea>
</div>
<div class="w300 flex-fixed">
<div class="f fbw fm mb4">
<span>service_interface</span>
<button type="button" data-copy=output_service_interface>コピー</button>
</div>
<textarea id="output_service_interface" readonly class="block w-full h100" ></textarea>
</div>
<div class="w300 flex-fixed">
<div class="f fbw fm mb4">
<span>service_impl</span>
<button type="button" data-copy=output_service_impl>コピー</button>
</div>
<textarea id="output_service_impl" readonly class="block w-full h100" ></textarea>
</div>
<div class="w300 flex-fixed">
<div class="f fbw fm mb4">
<span>service_input</span>
<button type="button" data-copy=output_service_input>コピー</button>
</div>
<textarea id="output_service_input" class="block w-full h100" ></textarea>
</div>
<div class="w300 flex-fixed">
<div class="f fbw fm mb4">
<span>service_output</span>
<button type="button" data-copy=output_service_output>コピー</button>
</div>
<textarea id="output_service_output" class="block w-full h100" ></textarea>
</div>
</div>
</main>
</body>
</html>
body {
margin: 0px;
background-color: #eee;
}
.w300 {
width: 300px;
}
input, textarea {
border: 1px black solid;
background-color: white;
border-radius: 4px;
padding: 2px 6px;
}
button {
background-color: ghostwhite;
border: 1px solid;
border-radius: 4px;
padding: 2px 4px;
font-size: 12px;
}
button:hover {
background-color: #ddd;
}
.toast {
position: fixed;
z-index: 99999;
top: 16px;
left: 0;
right: 0;
margin: 0 auto;
background-color: rgba(40, 40, 40, 0.6);
backdrop-filter: blur(4px);
color: white;
border-radius: 4px;
padding: 4px 8px;
min-width: 100px;
max-width: 300px;
word-break: break-all;
line-break: anywhere;
animation: toast 0.2s ease;
pointer-events: none;
}
@keyframes toast {
from {
opacity: 0.5;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0px);
}
}
window.addEventListener('load', () => {
function execCopy(string){
// 空div 生成
var tmp = document.createElement("div");
// 選択用のタグ生成
var pre = document.createElement('pre');
// 親要素のCSSで user-select: none だとコピーできないので書き換える
pre.style.webkitUserSelect = 'auto';
pre.style.userSelect = 'auto';
tmp.appendChild(pre).textContent = string;
// 要素を画面外へ
var s = tmp.style;
s.position = 'fixed';
s.right = '200%';
// body に追加
document.body.appendChild(tmp);
// 要素を選択
document.getSelection().selectAllChildren(tmp);
// クリップボードにコピー
var result = document.execCommand("copy");
// 要素削除
document.body.removeChild(tmp);
return result;
}
const toast = (message = '', timeout = 3000) => {
const t = document.createElement('div');
t.className = 'toast';
t.textContent = message;
document.body.appendChild(t)
setTimeout(() => {
document.body.removeChild(t)
}, timeout);
};
const snake_to_upper_camel = v => v.split('_').map(v => v.slice(0, 1).toUpperCase() + v.slice(1)).join('');
const to_lower_camel = v=>v.slice(0, 1).toLowerCase()+v.slice(1);
const to_upper_camel = v=>v.slice(0, 1).toUpperCase()+v.slice(1);
const to_plural_form = (word) => {
if (word.endsWith('s')) {
return word + 'es';
}
else if (word.endsWith('y')) {
return word.slice(0, -1) + 'ies';
}
else {
return word + 's';
}
};
const handler = (api, method) =>
func (h *${api}) ${method}() rapi.RouterElement {
return handler.NewHandlerMethod(h.s${api}.${method})
}
;
const handler_after_input = (api, method) =>
func (h *${api}) ${method}() rapi.RouterElement {
hm := handler.NewHandlerMethod(h.s${api}.${method})
hm.AfterInput(func(ctx context.Context, r *http.Request, param *service.${api}${method}Input) error {
if param.Limit == 0 {
param.Limit = 16
}
return nil
})
return hm
}
;
const service_method_interface = (api, method) =>
${method}(
ctx context.Context,
param *${api}${method}Input,
) (*${api}${method}Output, error)
;
const service_method_impl = (api, method) => func (s *${to_lower_camel(api)}) ${service_method_interface(api, method).trim()} {;
const param_me_user_id = (auth) => {
switch(auth) {
case 'auth':
return MeUserID string \assign:"user_id" validate:"required"\\n;
case 'opt':
return MeUserID string \assign:"user_id"\\n;
}
return '';
};
const param_permission = (use_perm) => use_perm ? Permission *model.Permission \assign:"permission" validate:"required"\
: '';
const param_urls = (urls) => urls.length ? ${urls.map(v => {
return ${snake_to_upper_camel(v).replace(/Id$/, 'ID')} string \url:"${v}" validate:"required"\;
}).join('\n')}\n: '';
const service_list_input = (is_list) => is_list ? Limit int \form:"limit" validate:"max=100"\
Cursor string \form:"cursor"\\n : '';
const service_input = (api, method, auth, use_perm, is_list, urls) =>
type ${api}${method}Input struct {
${param_me_user_id(auth)}${param_permission(use_perm)}${param_urls(urls)}${service_list_input(is_list)}}
;
const service_output_struct = (api, is_list, output_struct_package = 'model') => is_list ?
${to_plural_form(api)} []*${output_struct_package}.${api} \json:"${to_plural_form(to_lower_camel(api))}"\
NextCursor string \json:"next_cursor"\
:
${api} *${output_struct_package}.${api} \json:"${to_lower_camel(api)}"\
;
const service_ouptut = (api, method, is_list, output_struct_package = 'model') =>
type ${api}${method}Output struct {
${service_output_struct(api, is_list, output_struct_package)}
}
/**
*
* @param {(...args: any) => any} func
* @param {number} ms
* @returns {((...args: any) => any) & { cancel: () => void }}
*/
const debounce = (func, ms) => {
/** @type {NodeJS.Timeout} */
let id;
return Object.assign((/** @type {any[]} */ ...args) => {
clearTimeout(id);
id = setTimeout(() => func(...args), ms);
}, {
cancel: () => clearTimeout(id),
});
};
const d_update = debounce(update, 256);
form.oninput = () => {
d_update();
};
outputs.onclick = (e) => {
const target = e.target;
const c_target = target.dataset.copy;
if (c_target) {
const c_target_elm = document.getElementById(c_target);
if (c_target_elm) {
if (execCopy(c_target_elm.value)) {
toast('コピーしました');
}
else {
toast('コピーに失敗しました。選択してコピーしてください');
}
}
}
};
function update() {
let [api, method, output_struct_package] = [name_of_api, name_of_method, name_of_output_struct_package].map(e=>e.value.trim());
api = to_upper_camel(api);
method = to_upper_camel(method);
const urls = url_params.value.split('\n').map(v => v.trim()).filter(Boolean);
const [use_perm, use_after_input, is_list] = [permission, after_input, is_list_check].map(e=>e.checked);
const auth = form.login.value;
let handler_func;
if (use_after_input) {
handler_func = handler_after_input;
}
else {
handler_func = handler;
}
[
[output_handler, handler_func(api, method)],
[output_service_interface, service_method_interface(api, method)],
[output_service_impl, service_method_impl(api, method)],
[output_service_input, service_input(api, method, auth, use_perm, is_list, urls)],
[output_service_output, service_ouptut(api, method, is_list, output_struct_package)]
].forEach(([elm, value]) => {
elm.value = value;
});
}
});