チュートリアル
Markdownでブログコンテンツが書けるCLIクライアントを作ろう!(その1)
ECHOPFのJavaScript SDKを使ってMarkdown記法で書いたドキュメントをブログにアップロードできるソフトウェアを作ります。JavaScript SDKはNode.jsで利用します。
作るもの
作るのはコマンドラインで使うECHOPF向けのブログクライアントです。記法として開発者の間でよく知られているMarkdownを採用します。例えば次のように使います。
初期設定コマンド
初期設定ファイルを生成します。
echopf-blog init .
新しい記事を書くテンプレートを生成
ブログ記事を書くためのテンプレートを生成します。
echopf-blog new
ブログを投稿
ブログ記事を投稿します。
echopf-blog push 生成したMarkdownファイル.md
ブログ記事をダウンロード
クラウドにあるブログ記事のデータをローカルコンピュータ内にダウンロードします。
echopf-blog pull
必要なもの
Node.js
Node.jsはサーバサイド、ローカルコンピュータ上で動作するJavaScriptエンジンになります。Node.jsを使うことでJavaScriptを使ってWebアプリケーションであったり、ローカルコンピュータ上で動作するソフトウェアが開発できるようになります。
ダウンロードはNode.jsの公式プロジェクトサイトから行えます。インストーラーを実行するだけで完了します。
JavaScript SDK
ECHOPFのJavaScript SDKが必要です。こちらからダウンロードできます。
なお、このチュートリアルは作成時点での最新版である1.2.6に対応しています。ファイルをダウンロードしたら解凍し、解凍したフォルダの中にある ECHO.min.js を利用します。
下準備
まずインスタンスやメンバーの追加を行っていきます。これらはすべてECHOPFの管理画面上で行う作業です。
サイトアカウントの作成
サイトアカウントを作成する際にはREST APIを提供しているライセンスプランを用いてください。具体的にはポータルサイト、ECサイト、アプリケーションバックグラウンドになります。スタンダードサイトではREST APIがありませんので注意してください。
ブログインスタンスの作成
サイトアカウントを作り、管理画面にログインします。
まず最初にブログインスタンスを作成します。インスタンス管理を開きます。そして、中央上部にあるプラスアイコンをクリックします。
出てきたインスタンスの中で、ブログを選択してください。
モーダルウィンドウが出てきますので、以下のように情報を入力します。
項目 | 値 |
---|---|
インスタンスID | information |
インスタンス名 | お知らせ |
所属ページ | Home |
メインメニューに表示 | 表示する |
入力したら生成ボタンを押します。下の画像のようにインスタンスが追加されたら完了です。
APIアプリの作成
REST APIを扱うためのAPIアプリを作成します。設定メニューの中にあるAPIアプリ管理を選択します。
画面中央にあるプラスアイコンをクリックします。そうするとモーダルウィンドウが表示されます。アプリケーション名は適当で構いませんが、今回は「InformationClient」としておきます。
そうすると X-ECHO-APP-ID と X-ECHO-APP-KEY という二つの文字列が生成されます。このIDとキーを使ってREST APIを操作しますので覚えておきましょう。
メンバー管理
ブログインスタンスにアクセスするメンバーを作成します。インスタンス管理に移動し、プラスアイコンをクリックします。そしてメンバーをクリックします。
モーダルウィンドウが出てきますので、以下のように情報を入力します。
項目 | 値 |
---|---|
インスタンスID | blogger |
インスタンス名 | ブロガー |
所属ページ | Home |
メインメニューに表示 | 表示する |
生成ボタンをクリックして、インスタンスの一覧に追加されれば完了です。
メンバーを作成する
左側のメニューに追加されたブロガーメニューのメンバー管理を選択します。
表示されたメンバー管理で中央上部にあるプラスアイコンをクリックします。そうするとユーザ追加するためのモーダルウィンドウが表示されますので、入力してください。ログインID、パスワード、名前、メールアドレスは任意のものを入力してください。
項目 | 値 |
---|---|
ログインID | 任意 |
パスワード | 任意 |
パスワード(確認) | 任意 |
ステータス | 有効 |
グループ | 指定しない |
名前 | 任意 |
メールアドレス | 任意 |
作成するとメンバー管理の一覧にメンバーが追加されます。これで完了です。
権限設定
左側にあるブログインスタンスの設定メニューを開きます。
さらにアクセスコントロール(ACL)をクリックします。この機能を使って、メンバーやグループ単位に利用できる機能を制限できます。
記事とカテゴリについて、先ほど作ったメンバー(画面上はブロガーとなっています)に対して一覧表示、個別表示、追加、編集、削除権限を付与します。
設定したら保存ボタンを押します。
投稿時のステータス設定
さらにブログインスタンスの設定メニューから、外部記事投稿設定をクリックします。これはREST APIを使って投稿した記事の公開、非公開を設定するものです。
モーダルウィンドウが表示されますので有効に指定します。
保存を押せば完了です。
ここまでで集まった情報についてまとめます。
項目 | 内容 | 例 |
---|---|---|
ドメイン | サイトアカウント作成時に入力したドメインです | blog-sample.echopf.com |
X-ECHO-APP-ID | APIアプリで作成されたアプリIDです | 5176c253e43db45a4eaf84cd36916ee7 |
X-ECHO-APP-KEY | APIアプリで作成されたアプリキーです | fb1e85828fd47c6b3342bce4a4fda021 |
ブログインスタンスID | ブログインスタンスのIDです | information |
メンバーインスタンスID | メンバーインスタンスのIDです | blogger |
ログインID | メンバーインスタンスで追加したメンバーのログインIDです | blogmember |
パスワード | メンバーインスタンスで追加したメンバーのパスワードです | password |
これらの情報を使っていきますのでメモしておいてください。
Node.jsアプリケーションのベースを作る
続いてNode.jsアプリケーションのベースを作成します。Windowsであればコマンドプロンプト、macOSやLinuxであればターミナルを開きます。開いたウィンドウで次のように入力します。
node -v
これで v7.9.0
といった文字が表示されればNode.jsがインストールされています。コマンドがありません、Command not foundといったメッセージが出る場合にはNode.jsがきちんとインストールされているか確認してください。
フォルダの作成
フォルダを作成し、その中に移動します。コマンドの細かな解説はしませんが、一行目がechopf-blogというフォルダの作成、二行目がechopf-blogの中に移動(cdはchange directoryの略です)という意味になります。
mkdir echopf-blog
cd echopf-blog
Node.jsアプリケーションの初期化
移動したら Node.js アプリケーションの初期化を行います。この時使うのはNode.jsアプリケーションのパッケージ(ライブラリ)管理システムであるnpmというコマンドになります。最後のドット「.」を忘れずに入力してください。
npm init .
このコマンドを入力すると対話型で入力を求められます。基本的にすべてエンターキーで構いません。全体像は次のようになるはずです。
$ npm init .
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
name: (echopf-blog)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /path/to/echopf-blog/package.json:
{
"name": "echopf-blog",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Is this ok? (yes) yes
この結果、package.json
という設定ファイルが生成されます。
コマンドを受け付けられるようにする
例えば echopf-blog new
や echopf-blog pull
といったコマンドで操作できるようにします。そのためのライブラリとして Commander をインストールします。ライブラリのインストールは先ほど使った npm
コマンドを使います。 --save
と付けると自動的に package.json
を更新してくれます。
npm install commander --save
さらに コマンドで使えるようにするために package.json
を更新します。具体的には下記のキーを追加します。
{
"name": "echopf-blog",
...(省略)
"main": "index.js",
// 追加ここから
"bin": {
"echopf": "./index.js"
},
// 追加ここまで
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
...(省略)
}
index.js を作成する
では index.js
を新規作成します。内容は以下のようになります。 一行目はコマンドとして動かすために必要な記述になります。
#!/usr/bin/env node
// commanderライブラリの読み込み
const program = require('commander');
// commanderの実行内容です
program
.version('1.0.0') // バージョン番号
.command('init [directory]') // コマンドの実行方法
.description('初期設定ファイルを生成します。最初に実行してください。') // コマンドの説明
// 処理を書きます
.action((dir) => {
console.log(`ディレクトリは ${dir} です`)
});
// commanderでコマンドラインの引数を解釈
program.parse(process.argv);
ローカルで実行してみる
ではこの内容を試してみます。ローカルで書いた内容をそのまま実行するためには npm link
というコマンドを使います。最後のドット「.」を忘れないでください。
npm link .
これが終わると echopf
というコマンドが使えるようになっているでしょう。そして --help
でコマンドの説明が表示できます。
$ echopf --help
Usage: echopf [options] [command]
Options:
-V, --version output the version number
-h, --help output usage information
Commands:
init [directory] 初期設定ファイルを生成します。最初に実行してください。
後はコマンドの内容を作っていきます。
ECHOPFのJavaScript SDKを配置する
ダウンロードしたECHOPFのZipファイルを解凍し、ECHO.min.jsをプロジェクトのルートディレクトリに配置します。このJavaScript SDKはNode.jsにも対応しています。
初期設定を行うコマンドの作成
初期設定で行うのは設定ファイルの生成です。echopf init .
で行えるようにします。これにより毎回APIのIDやキーを入力することなくechopfコマンドが使えるようになります。さらに手入力で設定ファイルを作るのは面倒なので、コマンドでできるようにします。
ライブラリのインストール
まずコマンドを対話型にしてくれるライブラリ inquirer
をインストールします。
npm install inquirer --save
次に設定ファイルに書かれるJSONが見やすく整形される pretty-data
をインストールします。
npm install pretty-data --save
クラスの作成
メンテナンス、可読性を考慮してファイルを分割して開発します。次のような構成になります。
├── index.js
├── ECHO.min.js
└── libs
├── base.js // 新規作成
└── init.js // 新規作成
base.js にはファイル書き込みなどの共通処理を記述します。そして init.js は初期化処理に必要なコードを記述します。
まず libs/base.js
というファイルを作成します。内容はBlogBaseクラスの定義で、次のようになります。このBlogBaseクラスで行っているのは設定情報をクラス内部の変数として保存する処理になります。
const ECHOPF = require('../ECHO');
const path = require('path');
const fs = require('fs');
module.exports = (() => {
class BlogBase {
constructor(options) {
// 記事を保存するディレクトリ
this.dir = `${path.resolve(options.dir)}/entries/`;
// 設定ファイルのパス
this.configPath = `${path.resolve(options.dir)}/echopf.config.json`;
// ECHOPFで用いる設定があれば保存します
this.config = options.config;
if (this.configPath) {
try{
// 設定ファイルを読み込みます
this.config = require(this.configPath);
// ECHOPFを初期化します
ECHOPF.initialize(
this.config.domain,
this.config.applicationId,
this.config.applicationKey
);
// 初期化したECHOPFを保存します
this.ECHOPF = ECHOPF;
} catch (e) {
// 設定ファイルがない場合
}
}
}
}
return BlogBase;
})();
libs/init.js
というファイルを作成します。これはBlogInitというクラスを持ち、先ほど作成したBlogBaseを継承しています。内容は次のようになります。
const pd = require('pretty-data').pd;
const fs = require('fs');
const inquirer = require('inquirer');
const BlogInit = require('./libs/init');
module.exports = (() => {
class BlogInit extends BlogBase {
// コンストラクタ
constructor(dir) {
// BlogBaseクラスのコンストラクタを呼び出します
super({ dir });
}
// 設定ファイルの存在チェックを行います
checkConfig() {
}
// 設定ファイルを作成します
createConfigFile() {
}
// 記事を保存するentriesというディレクトリを作成します
createDirectory() {
}
}
return BlogInit;
})();
ではここから checkConfig
、createConfigFile
、 createDirectory
をそれぞれ作成していきます。
設定ファイルの存在チェック(checkConfig)
設定ファイルを誤って上書きしないよう存在を確認し、あれば開発者に上書き確認を行います。この時、対話型インタフェースを実現する inquirer が使えます。Node.jsのファイルチェック fs.access
は非同期処理になりますので、 checkConfig
全体を非同期処理 Promise
で囲んで処理します。
ファイルの存在を確認し、ユーザが上書きを no
とした場合はreject処理にします。エラーコードとして exit
を指定しておきます。
checkConfig() {
const me = this;
return new Promise((res, rej) => {
fs.access(me.configPath, (err) => {
// エラーがない = ファイルがない場合は処理を抜ける
if (err) return res();
// ファイルがある場合は確認
inquirer.prompt([{
name: 'file',
message: '設定ファイルが存在します。上書きしていいですか?',
type: 'confirm',
}])
// 選択した場合、answer.file に true または false が入ります
.then(answer => (answer.file ? res() : rej({ code: 'exit' })));
});
});
}
設定ファイルの作成(createConfigFile)
設定ファイルの作成は、先ほどの checkConfig
を呼び出し、問題ない(設定ファイルが存在しない、または上書きの許可が出ている)場合には設定内容を書き込みます。この設定内容は askConfig
を実行して得られる内容になりますが、後述します。
設定内容はJSONで得られますが、その内容を pretty-data
を使って見やすく整形します。そして設定ファイルのパス me.configPath
に書き込みます。
createConfigFile() {
const me = this;
// 全体を非同期処理にします
return new Promise((res, rej) => {
me.checkConfig()
.then(() => askConfig())
.then((config) => {
// 設定内容のJSONを見やすく整形
const json = pd.json(config);
// ファイルに書き込み
fs.writeFile(me.configPath, json, err => (err ? rej(err) : res()));
})
.catch((err) => {
// err.code === 'exit' なのは上書きを拒否した場合です
// その場合はエラー出ないので res() を呼び出します
// それ以外の場合はエラーオブジェクトをそのまま次の処理に送ります
if (err.code === 'exit') res();
rej(err);
});
});
}
設定内容を対話型にする
設定ファイルに書き込む内容は対話型インタフェースで取得するようにします。設定ファイルの存在チェックを行った時と同じく、inquirer が利用できます。
ちょっと長いですが、次のようになります。ドメイン、アプリケーションID、アプリケーションキー、インスタンスID(ブログとメンバー管理)、メンバー管理のログインIDとパスワードになります。そして簡易的に入力チェックも行います。
// 入力チェック
const alphabetValidate = (input, label) => (
input.match(/^[a-zA-Z0-9\?\-\+\.\_]*$/) ? true : `${label}は英数字しか使えません。`
);
// 対話型で入力してもらう内容
const askConfig = () => {
// ドメイン
const questions = [{
name: 'domain',
message: 'ドメイン名(例:your-domain.echopf.com): ',
}];
// 他は入力チェックやメッセージの作り方が共通なのでまとめてしまいます
const sections = [
{ name: 'applicationId', label: 'アプリケーションID' },
{ name: 'applicationKey', label: 'アプリケーションキー' },
{ name: 'blogInstanceId', label: 'ブログのインスタンスID' },
{ name: 'memberInstanceId', label: 'メンバー管理のインスタンスID' },
{ name: 'login_id', label: 'ログインID' },
{ name: 'password', label: 'パスワード' },
];
for (let i = 0; i < sections.length; i += 1) {
// 対話型の内容を作成します
const section = sections[i];
questions.push({
name: section.name,
message: `${section.label}: `,
validate: input => alphabetValidate(input, section.label),
});
}
return inquirer.prompt(questions);
};
記事を保存するディレクトリの作成
記事をクラウドから取得したら、entriesというディレクトリに保存することとします。そこで、フォルダを作成する処理を作ります。
// 記事を保存するentriesというディレクトリを作成します
createDirectory() {
// ディレクトリのパス
const dirName = this.dir;
return new Promise((res, rej) => {
// ディレクトリの存在確認
fs.access(dirName, (err) => {
// エラーがない = すでにフォルダが存在している
if (!err) {
return res(err);
}
// フォルダがなけれあ作成
fs.mkdir(dirName, (err) => {
err ? rej(err) : res();
});
});
});
}
処理実行部の作成
ではこの init.js を読み込んで、処理を組み込みます。対象になるのは index.js
です。
元々次のようになっていた部分を直します。
// commanderの実行内容です
program
.version('1.0.0') // バージョン番号
.command('init [directory]') // コマンドの実行方法
.description('初期設定ファイルを生成します。最初に実行してください。') // コマンドの説明
// 処理を書きます
.action((dir) => {
console.log(`ディレクトリは ${dir} です`)
});
下記のように修正します。 libs/init.js
を読み込んで、設定ファイルの作成 createConfigFile
と ディレクトリの作成 createDirectory
を実行します。
const BlogInit = require('./libs/init');
program
.version('1.0.0') // バージョン番号
.command('init [directory]') // コマンドの実行方法
.description('初期設定ファイルを生成します。最初に実行してください。') // コマンドの説明
// 処理を書きます
.action((dir) => {
// 設定ファイルの生成
const init = new BlogInit(dir);
init
// 設定ファイルの作成
.createConfigFile()
.then(() =>
// ディレクトリの作成
init.createDirectory())
.then(() => {
console.log('初期設定が完了しました。続けて pull コマンドを実行してみましょう');
})
.catch((err) => {
console.log(`エラーが発生しました。${JSON.stringify(err)}`);
});
});
ここまでで初期設定の処理が完了です。
コマンドを実行する
では初期設定コマンドを実行してみましょう。
$ echopf init .
次のように対話型で入力が求められれば成功です。入力内容はサンプルなので、ご自身のものを入力してください。
$ echopf init .
? ドメイン名(例:your-domain.echopf.com): example.echopf.com
? アプリケーションID: 087...a73
? アプリケーションキー: 9d9...426
? ブログのインスタンスID: information
? メンバー管理のインスタンスID: blogger
? ログインID: blogger
? パスワード: Mf6...ZDW
初期設定が完了しました。続けて pull コマンドを実行してみましょう
完了すると、ディレクトリ構成が次のようになっているはずです。
├── ECHO.min.js
├── echopf.config.json
├── entries
├── index.js
├── libs
│ ├── base.js
│ └── init.js
├── node_modules
├── package-lock.json
└── package.json
echopf.config.json
が設定ファイルで、次のような内容になっていれば問題ありません。値は例で、実際にはさきほど入力した情報が表示されるはずです。
{
"domain": "example.echopf.com",
"applicationId": "087...a73",
"applicationKey": "9d9...426",
"blogInstanceId": "information",
"memberInstanceId": "blogger",
"login_id": "blogger",
"password": "Mf6...ZDW"
}
もう一度初期設定コマンドを実行すると、設定ファイルが存在するというメッセージが出るはずです。ここで Y を入力するともう一度設定内容を対話型に聞かれます。 N を押すとキャンセルされます。
$ echopf init .
? 設定ファイルが存在します。上書きしていいですか? No
初期設定が完了しました。続けて pull コマンドを実行してみましょう
管理画面で記事を投稿する
ECHOPFの管理画面にて、テストで一つ記事を投稿しておきます。これは次の「記事の取得処理」を行うためです。左のメニューからお知らせの記事追加を選択します。
記事を書き、保存してください。
既存の記事を取得する
ここから記事を作成するコマンド pull
の作成に入ります。このコマンドはECHOPFのJavaScript SDKを使い、サーバからデータをダウンロードします。次のように実行します。
$ echopf pull
まず処理を実装する libs/pull.js
を作成します。内容は次の通りです。 BlogBase
を継承するクラスで、まだ中身はありません。
const BlogBase = require('./base');
module.exports = (() => {
class BlogPull extends BlogBase {
// 記事取得処理全体
pull() {
}
// ブログ記事を取得
fetchBlogEntries() {
}
}
return BlogPull;
})();
そしてこのBlogPullクラスを扱う処理を index.js
に記述します。
const BlogPull = require('./libs/pull');
program
// コマンド pull を有効にします
.command('pull')
.description('サーバ上の記事をダウンロードします。')
.action(() => {
// BlogPullの処理
const blogPull = new BlogPull({
// 処理はカレントディレクトが対象
dir: '.',
});
// 記事取得処理を実行します
blogPull
.pull()
// 取得成功
.then(() => {
console.log('記事を取得しました');
})
// 取得失敗
.catch((err) => {
console.log(`エラーが発生しました。${JSON.stringify(err)}`);
});
});
記事の取得処理(fetchBlogEntries)
記事の取得処理 BlogPull.fetchBlogEntries
は次のように、JavaScript SDKの Blogs
を使います。そして find
メソッドに対してブログインスタンスのIDを指定します。
コードとして書くと次のように書きます。
ECHOPF
.Blogs
.find("ブログのインスタンスID")
.then((results) => {
// 取得結果が入ります
});
ではこの書き方を fetchBlogEntries
に記述します。記事を取得する部分はネットワークを使うので全体を非同期処理 Promise
で囲みます。
// ブログ記事を取得
fetchBlogEntries() {
const me = this;
return new Promise((res, rej) => {
// ECHOPFのJavaScript SDKで記事を取得します
me.ECHOPF
.Blogs
.find(me.config.blogInstanceId)
// 記事取得が成功した場合
.then((results) => {
// 結果から記事だけを取り出します
const entries = results.map((entry) => {
if (!entry ||
!entry.constructor ||
entry.constructor.name !== 'EntryObject') return null;
return entry;
});
// 処理成功に送ります
res(entries);
}, (err) => {
// リソースが見つからない場合
rej(err);
});
});
}
記事が取得された場合、 results
の内容は次のようになります。記事 EntryObject
以外にも記事数であったり、次のページの有無と言った情報も入ってきますので、記事情報だけ取り出します。
List {
'0':
EntryObject {
instanceId: 'information',
resourceType: 'entry',
refid: '20171027063553',
_data:
{ refid: '20171027063553',
title: 'バージョン1.0をリリースしました',
description: '',
keywords: '',
robots: '',
contents: [Object],
link_status: 1,
owner: null,
modified: 2017-10-26T21:40:49.000Z,
modified_user: '[email protected]',
created: 2017-10-26T21:40:49.000Z,
published: 2017-10-26T21:35:00.000Z,
categories: [Array],
resource_type: 'entry',
url_path: '/information/entry/20171027063553',
url: 'http://example.echopf.com/information/entry/20171027063553' },
_newACL: null,
_currentACL:
ACL {
_all: [Object],
_allMembers: [Object],
_specificGroups: {},
_specificMembers: {} },
_multipart: false },
page: 1,
prevPage: null,
nextPage: null,
pageCount: 1,
count: 1,
limit: 10,
order: '*',
asc: false,
length: 1 }
記事取得処理全体(pull)
次に記事取得処理全体を処理する BlogPull.pull
メソッドの実装です。先ほど作成した fetchBlogEntries
の実行と、その結果 entries
をファイルとして保存する saveEntries
を実行します。 fetchBlogEntries
などは非同期処理になりますので、pull
メソッド全体も非同期処理 Promise
で囲みます。
// 記事取得処理全体
pull() {
const me = this;
return new Promise((res, rej) => {
me
// 記事を取得します
.fetchBlogEntries()
// 取得した記事内容を保存します
.then(entries => me.saveEntries(entries))
// 保存が完了したら res を実行します
.then(() => {
res();
})
// エラーの場合です
.catch((err) => {
rej(err);
});
});
}
記事をファイルとして保存する(saveEntries)
サーバから取得した記事をMarkdownファイルとして保存します。この処理は受け取った内容を entryToMarkdown
メソッドを使ってMarkdown化し、その内容を書き込みます。
** この処理は新しい記事を作成する場合にも使うので BlogBase
クラスの中に定義します。**
// 記事をファイルとして保存する
saveEntries(entries) {
const me = this;
return new Promise((res, rej) => {
for (let i = 0; i < entries.length; i += 1) {
const entry = entries[i];
const data = this.entryToMarkdown(entry);
fs.writeFile(`${me.dir}/${entry.refid}.md`, data.join('\n'), err => (err ? rej(err) : ''));
}
res(true);
});
}
ブログ記事のMarkdown化
ブログ記事のMarkdown化は、分かりやすくするために幾つかに分割処理しています。Markdownは次のような構造になります。
---
メタ情報
---
記事概要
<!--more-->
記事詳細
メタ情報は記事タイトルやキーワード、記事公開日時などに加えて、カテゴリ、ACL(アクセス権限)になります。このメソッドも BlogBase
クラス内に定義します。
// 記事をMarkdown化
entryToMarkdown(entry) {
// メタ情報の作成(ここから)
let data = ['---'];
data.push(`refid: ${entry.refid}`);
const metas = [
'title',
'description',
'keywords',
'robots',
'link_status',
'owner',
'published',
];
data = data.concat(this.pushMeta(metas, entry));
data = data.concat(this.pushCategories(entry.get('categories')));
data = data.concat(this.pushAcl(entry.getACL()));
data.push('---');
// メタ情報の作成(ここまで)
// 本文の処理
data = data.concat(this.pushContent(entry.get('contents')));
return data;
}
メタ情報の設定(pushMeta)
記事タイトルやキーワード、公開日時と言ったメタ情報を取得します。このメソッドも BlogBase
クラス内に定義します。
// メタ情報の設定
pushMeta(metas, data) {
return metas.map(meta => `${meta}: ${data.get(meta) || ''}`);
}
カテゴリの設定(pushCategories)
カテゴリ情報をリスト構造にします。この時の構造は次のようになります。
categories:
- カテゴリID : カテゴリ名
- カテゴリID : カテゴリ名
- カテゴリID : カテゴリ名
処理 pushCategories
の内容は次のようになります。このメソッドも BlogBase
クラス内に定義します。
// カテゴリの設定
pushCategories(categories) {
const data = [];
if (!categories) return [];
data.push('categories: ');
for (let i = 0; i < categories.length; i += 1) {
const category = categories[i];
data.push(` - ${category.get('refid')} : ${category.get('name')}`);
}
return data;
}
アクセス権限(ACL)の設定(pushAcl)
ACLは以下の4つが指定できます。
- 全訪問者(未ログイン含む) : _all
- 全ログインユーザ : _allMembers
- 特定のグループ : _specificGroups
- 特定のユーザ : _specificMembers
それぞれの権限設定について、4つのアクションに対して権限が指定できます。
- 1件取得 : get
- リスト取得 : list
- 編集 : edit
- 削除 : delete
処理は次のようになります。このメソッドも BlogBase
クラス内に定義します。
// アクセス権限(ACL)の設定
pushAcl(acls) {
if (!acls) return [];
const data = [];
const metas = [
'_all',
'_allMembers',
'_specificGroups',
'_specificMembers',
];
data.push('acl:');
for (let i = 0; i < metas.length; i += 1) {
const meta = metas[i];
if (Object.keys(acls[meta]).length === 0) {
// 空だった場合
data.push(` ${meta}: `);
} else {
data.push(` ${meta}:`);
const acl = acls[meta];
const subMetas = ['get', 'list', 'edit', 'delete'];
for (let j = 0; j < subMetas.length; j += 1) {
const key = subMetas[j];
if (typeof acl[key] !== 'undefined') {
data.push(` ${key}: ${acl[key]}`);
}
}
}
}
return data;
}
本文部分の作成(pushContent)
本文を作成する pushContent
メソッドでは、内容と詳細を連結します。その際には HTML を Markdown に変換します。そのためのライブラリとして toMarkdown
をインストールします。コマンドプロンプトやターミナルにて npm
コマンドを実行します。
$ npm install to-markdown --save
そしてこのライブラリを libs/base.js
にて読み込みます。
const toMarkdown = require('to-markdown');
pushContent
ではHTMLをMarkdownに変換しつつ、画像のパスにドメインを追加します。例えば以下のような変換になります。
 -> 
そして、内容と詳細を <!--more-->
で繋ぎます。これはWordPress風にしているだけで、他の区切り記号でも構いません。これは BlogBase
クラスのコンストラクタで設定します。
constructor(options) {
// :(省略)
// 一番下に以下を追加
this.bodySplit = '<!--more-->';
}
pushContent
の内容は次のようになります。このメソッドも BlogBase
クラス内に定義します。
pushContent(contents) {
if (!contents) return [];
const data = [];
const contentType = ['main', 'detail'];
for (let i = 0; i < contentType.length; i += 1) {
const type = contentType[i];
const content = contents[type];
// HTMLをMarkdownに変換しつつ、画像のURLを変換します
data.push(toMarkdown(content)
.replace(/!\[(.*?)\]\((\/.*?)\)/g, ``));
// 内容の後に <!--more--> を追加します
if (type === 'main') { data.push(`\n${this.bodySplit}\n`); }
}
return data;
}
pullコマンドを実行する
ここまでで pull
コマンドが完成です。実際に実行してみましょう。
$ echopf-cli pull
記事を取得しました
そうして entries ディレクトリを見ると、Markdownファイルが作られているはずです。
$ ls entries/
20171027063553.md
そして内容は次のような形になります。
---
refid: 20171027063553
title: バージョン1.0をリリースしました
description:
keywords:
robots:
link_status: 1
owner:
published: Fri Oct 27 2017 06:35:00 GMT+0900 (JST)
categories:
- versionup : バージョンアップ
acl:
_all:
get: true
list: true
edit: false
delete: false
_allMembers:
_specificGroups:
_specificMembers:
---
ブログのCLIクライアントをバージョンアップしました。
### 1.0の主な特徴
* データ取得に対応しました
* 設定ファイル作成に対応しました
<!--more-->
### 設定ファイルの作成に対応しました
設定ファイルの作成は次のように入力してください。
> echopf init .
一番最後のドット「.」を忘れないようにしてください。これは設定ファイルを作成するディレクトリになります。
ここまでの処理で初期設定を行うところと、記事を取得するまでの流れができあがりました。次回は新しい記事を作成、アップロードする処理を作ります。
なお、ここまでのソースコードはechopfcom/Echopf_Blog_CLI at v1にアップロードされています。実装時の参考にしてください。