jhipsterでなにが、うれしいかというと、 クラス図をJDLという簡易な設定さえ書けば、
一応動作する画面が、できるということだと思う。
リレーションとかも、考慮済みのテンプレートが、いい感じに、頑張ってくれる。
でも、すでに、自分のプロジェクトのひな型ができている場合がある。
成果物として、望まれているのは、自分のプロジェクトの形式に沿ったコード
だったりする。
そうなるとひな形のほうを修正したい。
JDLからのパラメータは、どのように拾えるのか。
テンプレートは、yoというツールを使っていると思う。 JDLをyoで使える形式に変換するのが、JHIPSTERだとすると、テンプレートから、変数を引っ張ってくればいいんじゃなかろうか。
generator-jhipster-vuejs は、node_modulesフォルダに、格納されている さらにその配下の、generatorフォルダを、今回解析してみようと思う。
node_moduleフォルダにある、generatorフォルダが怪しいと思っておこう。
どうやら、jhipsterのコマンドは、
アプリフォルダ/node_moduless/generator-jhipster/cli の
commands.js
ファイルに記載があった。
ただし、ここから、どこかに行くというわけでは、ない。
モジュール側が、リスナーしているようだ。
例えば、import-jdl
というコマンドの場合、次のファイルが、このコマンドを待ち構えるモジュールとして作られている。
import-jdl.js
たぶん、次の文が、リスナーの実装っぽい。確認してないけど。。。GREPしたらここしか、import-jdlの文言含んでなかった。
const statistics = require('../generators/statistics'); statistics.sendSubGenEvent('generator', 'import-jdl');
yoのテンプレートの書き方だけでは、役不足だ。JDL言語で簡易に書いた、
クラス図の情報が、どのように、テンプレートの引数にわたってくるのかが、知りたいよね?
だから、テンプレートファイルをみて、解析してみるとする。
とりあえず、エンティティまわりをみてみたいので、下記のファイルを観察してみるとする。
EntityRepository?.java.ejs
<%# -%>
package <%=packageName%>.repository;
テーブル名とかに使用
<%=asEntity(entityClass)%>
または
<%=entityClass%>
<%_ if (fieldsContainOwnerManyToMany) { _%> <%_ } _%>
<%_ if (databaseType === 'mongodb') { _%> <%_ } _%>
select <%= entityInstance %>
<% for (idx in relationships) { if (relationships[idx].relationshipType === 'many-to-many' && relationships[idx].ownerSide === true) { %> left join fetch <%=entityInstance%>.<%=relationships[idx].relationshipFieldNamePlural%><%} }%>",
for (idx in relationships) { if (typeof relationships[idx].javadoc != 'undefined') { } }
<% for (idx in fields) { } _%>
for (idx in fields) { if (typeof fields[idx].javadoc != 'undefined') { } }
fieldsContainBlob importJsonIgnoreProperties importApiModelProperty importJsonIgnore fieldsContainUUID prodDatabaseType hasTextBlob validation searchEngine fieldsContainBigDecimal
たまたま、みつけたから、メモしておこう。
node_modules\generator-jhipster\generators\entity\prompts.js
にある。
jhiCore.JDLImporter
のimportメソッド
でパースされているようです。
JDLで、認識できる文法を、自分のプロジェクトに合わせれたら、いいんじゃないだろうか?
たとえば、案件ごとに、定義書は、大体似ては、いるが、若干、違う。
それをJDLに、まとめさせて、テンプレートに流すことができたら、すごくよさそうだ。
node_modules\jhipster-core\lib\jdl\jdl_importer.jsにて
import() { const parsedJDLContent = parseFiles(this.files);
って書いてありました。
function parseFiles(files) { return JDLReader.parseFromFiles(files); }
JDLReaderを調べる必要がありそうです。
const JDLReader = require('../reader/jdl_reader');
とあるので、
アプリのフォルダ/node_modules/jhipster-core/lib/reader/jdl_reader を調べる必要がありそうです。
ちょと、ここで、発見したのですが、removeInternalJDLComments というメソッドで、
JavaDoc?コメントをパース前に、削除して、台無しにしてしまっているようにみえます。
だれか、修正してくれないかぁ。まあ、いいや、解析を進めよう。
const parser = require('../dsl/api');
とあるので、
/node_modules/jhipster-core/jhipster-core/lib/dsl/api.js
を見てみると、
まず、JDL言語の字句解析は、
const lexResult = JDLLexer.tokenize(input);
でおこなっており、
パーサーは、
const parserSingleton = JDLParser.getParser();
でパーサーの配列で取得できるようになっており、
デフォルトでは、progが使われているのが分かった。
で、パースをうごかしているのは、
buildAst(cst);
で、行っているようだ。汎用的につくってあるので、buildAstは、解析しないことにする。
自分用にコマンドを追加するには、
アプリフォルダ/node_modules/jhipster-core\lib/dsl/lexer.js
に、下記のような感じで、認識させる単語を登録させる、必要があるだろう。
たとえば、認識させたい単語が
required
ならば、それをパーサーに伝える際には、
'REQUIRED'
として、伝えたいならば、
createToken({ name: 'REQUIRED', pattern: 'required' });
と書かなくてはならない。
この 'REQUIRED'としたものは、node_modules\jhipster-core\lib\dsl\jdl_parser.jsにて
const LexerTokens = require('./lexer').tokens;
としており、次のように文法のルール定義に記載されている。
{ ALT: () => this.CONSUME(LexerTokens.REQUIRED) },
項目の定義がエクセルで、設計済みで、JDLでは、不足しているカラムがあるんだよなー。ってのが、一番ありそうなんじゃ、ないだろうか?
となると、やりたいことは、
カラムと、その中身の定義を、字句解析と、構文解析に追加する、方法が分かりたいということだ。
近そうな記述は、最大値の認識させ方かなと思って、解析を進めてみる。
createToken({ name: 'MAXLENGTH', pattern: 'maxlength', categories: [tokens.MIN_MAX_KEYWORD] });
minMaxValidation() { this.RULE('minMaxValidation', () => { // Note that "MIN_MAX_KEYWORD" is an abstract token and could match 6 different concrete token types this.CONSUME(LexerTokens.MIN_MAX_KEYWORD); this.CONSUME(LexerTokens.LPAREN); this.OR([{ ALT: () => this.CONSUME(LexerTokens.INTEGER) }, { ALT: () => this.CONSUME(LexerTokens.NAME) }]); this.CONSUME(LexerTokens.RPAREN); }); }
validation() { this.RULE('validation', () => { this.OR([ { ALT: () => this.CONSUME(LexerTokens.REQUIRED) }, { ALT: () => this.CONSUME(LexerTokens.UNIQUE) }, { ALT: () => this.SUBRULE(this.minMaxValidation) }, { ALT: () => this.SUBRULE(this.pattern) } ]); }); }
以下、同様に解析していくと、呼び出しているメソッドが、次のようになっていました。
↑
fieldDeclaration
↑
entityBody
if (rules.includes('maxlength') && !rules.includes('minlength')) { validators.push('@Size(max = ' + field.fieldValidateRulesMaxlength + ')'); }
どこで、fieldValidateRulesMaxlength? 入れてるの?
自動かなぁ
どうも、テンプレートを見る限りでは、
field.fieldValidateRules, 'maxlength'
が定義されているなら、
field.fieldValidateRulesMaxlength
は、定義済みだよね~という記述だ。
で、パーサどう書けばいいの?ってなるが、JDLで使っているパーサは、chevrotain
を使っているようだ。
https://github.com/SAP/chevrotain
https://sap.github.io/chevrotain/docs/tutorial/step0_introduction.html