ビジュアル化の弱点
Node-REDはビジュアルプログラミなングが可能で、効率的にプログラミングが可能です。その反面、検索機能はあるもののプログラム全体の分析は困難です。
そこで、所属のアドベントカレンダーにまとめた [#Node-RED]flows.jsonのフォーマットを見てみる の情報を使ってノードの一覧を作ってみました。
全体の構造
ツールの構造は難しくありません。以下のような構造になっています。
- データ入力
- 全体で使う情報の作成
- タブごとの情報作成
- タブ情報の結合
- HTML作成
HTMLを直接出力するとややこしくなるので、Markdownの出力をHTMLに変換しています。では、順番に説明します。
データ入力
以下のような入出力方法が考えられますが、今回はUIが簡単なWeb呼び出しをきっかけにファイルまたはテンプレートノードから入力しHTMLをhttp応答する方法にしました。
- 開始方法
- インジェクトノード
- ダッシュボード
- http in
- その他通信
- 出力方法
- ファイル(静的ページやCSVなど)
- ダッシュボード
- http応答(HTMLなど)
- その他応答
全体で使う情報の作成
flows.jsonは全体が一つの配列になっていますので、まずは解析情報を抜き出す必要があります。今回はタブ一覧を抜き出しました(ダッシュボードのタブやページをまたがる陸などを解析する場合はここで解析する必要があるでしょう)。
タブごとの情報作成
ここが一番難しい処理です。タブ一覧の配列をもとにsplit/joinノードを使ってタブ単位で処理をしています。タブごとの処理では、
- タブを抜き出す
- タブ情報のMarkdown出力
- 各ノードを上から下にMarkdown出力
- タブ詳細などの文章はMarkdownで書けますが、そのままだとリストなどで表が壊れるので、markdown-itで文章をHTML化しています。
- グループの名前はidをプロパティ名として参照できるようにしていますが、名前のないノードなどはidを表示しています。
全てのタブ情報の結合
タブごとの表は配列になっていますので、ヘッダーと共に一つの表に変換します。
HTML作成
Markdownノードを用いてHTMLに変換しています。Markdownノードはmarkdown-itを使っています。そのままだとヘッダーがないのでテンプレートノードで追加しています。
使い方と応用方法
コードを下につけていますので、Node-REDで読み込んで使ってください。ブラウザでNode-REDのURLの下のlist-localまたはlist-jsonを参照してください。list-localは~/.node-red/flows.jsonを、list-jsonはテンプレートノードにペーストしたflows.jsonを参照します。flows.jsonは少なくとも一つのタブが必要です。
特定のノードだけの一覧を作りたいときは、「解析とマークダウン作成」ノードのコメントを見て修正してください。その他の修正はよくコードを読んでから修正してください。説明はdisabled(trueの場合),mode,infoプロパティのみ表示します。必要に応じて追加してください。
入出力など必要に応じて自由に修正してお使いください。
新バージョン
Node-RED3.1で追加されたノードやグループの固有URLに対応した改訂版は以下をご参照ください。
[#Node-RED]:flows.jsonのノード一覧(固有URL対応版)
参考文献
[#Node-RED]flows.jsonのフォーマットを見てみる
コード
[{"id":"970e4ed7b944a9b6","type":"tab","label":"ノード一覧","disabled":false,"info":"flows.jsonのノード一覧を作成します。","env":[]},{"id":"86f312d94d774b20","type":"junction","z":"970e4ed7b944a9b6","x":580,"y":220,"wires":[["ac03cf7426581af1"]]},{"id":"d47fc116556c1d98","type":"debug","z":"970e4ed7b944a9b6","name":"HTML","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":430,"y":660,"wires":[]},{"id":"fdbe3d70e31de3c9","type":"file in","z":"970e4ed7b944a9b6","name":"","filename":"filename","filenameType":"msg","format":"utf8","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":480,"y":220,"wires":[["86f312d94d774b20"]]},{"id":"89278eb590ee4d16","type":"function","z":"970e4ed7b944a9b6","name":"~/.node-red/flows.json","func":"msg.filename = msg.payload + '/.node-red/flows.json'\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":220,"wires":[["fdbe3d70e31de3c9"]]},{"id":"ac03cf7426581af1","type":"json","z":"970e4ed7b944a9b6","name":"","property":"payload","action":"","pretty":false,"x":170,"y":340,"wires":[["db8d0c075506ec6b"]]},{"id":"db8d0c075506ec6b","type":"function","z":"970e4ed7b944a9b6","name":"タブ一覧","func":"msg.objs = msg.payload;\nmsg.payload = msg.objs.filter(elm => elm.type === 'tab');\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":340,"wires":[["9deebe2b3a79aefe"]]},{"id":"9deebe2b3a79aefe","type":"split","z":"970e4ed7b944a9b6","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":230,"y":440,"wires":[["3bea992d98e4d4d5"]]},{"id":"a24f81853c9f22b8","type":"join","z":"970e4ed7b944a9b6","name":"","mode":"auto","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":230,"y":500,"wires":[["d8cbbdc08d6d18d5"]]},{"id":"3bea992d98e4d4d5","type":"function","z":"970e4ed7b944a9b6","name":"解析とマークダウン作成","func":"// 表が壊れるので説明のマークダウンをHTMLにする\nfunction conv(str) {\n // if (!str) return '';\n let md = markdownIt({ html: true, linkify: true, typographer: true });\n return (md.render(str).trim().replace(/\\n/g, ''));\n}\n\n// 説明欄の文字列作成 disabled mode infoの結合(見たいプロパティはここに追記)\nfunction mkRem(obj) {\n return (obj.disabled ? 'disabled ' : '')+\n (obj.d ? 'disabled ' : '') +\n (obj.mode ? ('mode:'+obj.mode+' ') : '')+\n (obj.info ? conv(obj.info) : '');\n}\n\nlet tab = msg.payload;\n\n// タブ内のオブジェクト抽出\nlet objs = msg.objs.filter(elm => elm.z === tab.id);\n\n// 抽出するノードの「種類を限定する場合はさらにフィルタする\n// objs = objs.filter(elm => elm.type === 'comment');\n\n// オブジェクトの位置ソート\nobjs.sort((a, b) => {\n if (a.y == b.y) return a.x - b.x;\n else return a.y - b.y;\n});\n\n/// グループのMAP作成(リンクノード分析は全体に対して行う)\nlet groups = objs.filter(elm => elm.type === 'group');\nlet groupName = {};\ngroups.forEach(group => { groupName[group.id] = group.name ? group.name : '-'+group.id+'-'});\n\nmsg.payload = '';\n\n// タブマークダウン\nmsg.payload += '\\n| **'+tab.label+'** | **' +tab.type+'** | | '+mkRem(tab)+' |';\n\n// オブジェクトマークダウン\nobjs.forEach(obj => {\n msg.payload += '\\n| ' + (obj.name === '' ? '-' +obj.id+'-':obj.name) + \n ' | ' + obj.type + ' | ' + \n (obj.g ? groupName[obj.g] : '') + ' | ' + mkRem(obj) + ' |';\n});\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"markdownIt","module":"markdown-it"}],"x":430,"y":440,"wires":[["a24f81853c9f22b8"]]},{"id":"5c8d3dd566715bc3","type":"comment","z":"970e4ed7b944a9b6","name":"タブごとの情報の作成","info":"**split**+**join**を用いてタブ単位で並行処理しています。","x":140,"y":400,"wires":[]},{"id":"134559eded4d1e6c","type":"template","z":"970e4ed7b944a9b6","name":"ここにJSONをペーストする","field":"payload","fieldType":"msg","format":"json","syntax":"plain","template":"","output":"str","x":380,"y":100,"wires":[["86f312d94d774b20"]]},{"id":"d8cbbdc08d6d18d5","type":"function","z":"970e4ed7b944a9b6","name":"表をまとめる","func":"// ヘッダマークダウン追加\nmsg.payload.unshift(`| 名前 | 種類 | グループ | 説明 |\\n| ---- | ---- | ---- | ---- |`);\n\n// 表の結合\n// msg.payload = msg.payload.join('\\n');\nmsg.payload = msg.payload.join('');\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":280,"y":600,"wires":[["62a050f4f4e791b1","b1c87663ee56f99b"]]},{"id":"b1c87663ee56f99b","type":"markdown","z":"970e4ed7b944a9b6","name":"","x":230,"y":700,"wires":[["d47fc116556c1d98","69031c4cfc3d0fb5"]]},{"id":"49d8107f50af82f0","type":"http in","z":"970e4ed7b944a9b6","name":"","url":"/list-local","method":"get","upload":false,"swaggerDoc":"","x":150,"y":160,"wires":[["eb2cac95ebbde58a"]]},{"id":"eb2cac95ebbde58a","type":"change","z":"970e4ed7b944a9b6","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"HOME","tot":"env"}],"action":"","property":"","from":"","to":"","reg":false,"x":340,"y":160,"wires":[["89278eb590ee4d16"]]},{"id":"d7af628eac5c00e4","type":"http in","z":"970e4ed7b944a9b6","name":"","url":"/list-json","method":"get","upload":false,"swaggerDoc":"","x":150,"y":100,"wires":[["134559eded4d1e6c"]]},{"id":"29a1f1043ae1c1ea","type":"http response","z":"970e4ed7b944a9b6","name":"","statusCode":"","headers":{},"x":590,"y":700,"wires":[]},{"id":"69031c4cfc3d0fb5","type":"function","z":"970e4ed7b944a9b6","name":"ヘッダー追加","func":"msg.payload = msg.payload.replace('', '');\n\nmsg.payload =\n `\n\n Node List\n \n\n\nNode List\n\n \n ` +\n msg.payload +\n ` \n \n\n\n`\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":420,"y":700,"wires":[["29a1f1043ae1c1ea"]]},{"id":"62a050f4f4e791b1","type":"debug","z":"970e4ed7b944a9b6","name":"マークダウン","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":540,"y":600,"wires":[]},{"id":"94a234e2bbab804e","type":"comment","z":"970e4ed7b944a9b6","name":"データ入力","info":"ブラウザから以下のいずれかをを参照してください。\n- http(s)://ホスト:ポート/list-json\n- http(s)://ホスト:ポート/list-local","x":100,"y":60,"wires":[]},{"id":"4994573e34e9ede4","type":"comment","z":"970e4ed7b944a9b6","name":"全体で使う情報の作成","info":"","x":140,"y":280,"wires":[]},{"id":"793d31ab83d60a8b","type":"comment","z":"970e4ed7b944a9b6","name":"全てのタブ情報の結合","info":"","x":140,"y":560,"wires":[]},{"id":"5dc2829793d8a062","type":"comment","z":"970e4ed7b944a9b6","name":"HTML作成","info":"","x":100,"y":660,"wires":[]}]