どのように私のマークダウンバッジサービスをインジェクタを使用して構築


このポストでは、私がどのように小さなサービスを構築したかについての私の洞察を共有したいですLangauge (それはタイプミスではなく、グラマーリーに感謝します)Injex Framework and Injex Express Plugin .
Langaugeを使用すると、特定のGithubリポジトリで使用されるプログラミング言語を表示するゲージのカラフルなバッジを追加します.あなたのMarkdownファイルまたはあなたのウェブサイトに追加することができますソーシャルメディア上のイメージとしてそれを共有し、それを印刷し、壁に置く!😄

動機づけ


すべてのGiTubリポジトリは、そのホームページの右側のパネルにカラフルな言語バーを持っています.たとえば、ここの言語バーですQuickey , 私のもう一つのオープンソースプロジェクト.

だからなぜ気を付けろとLangaugeを作成すると、あなたが尋ねる?さて、もちろん他の場所で自分のスキルを見せびらかしたいからです.
別の例にすばやく行きましょう.Quickey NPM経由でインストールできるモジュールです.ください、そして、2番目のためにそれをチェックしてください..
ようこそ!何かがそこに行方不明ですか?右!それは我々が以前見たカラフルな言語バーです!それはGithub機能ですので、我々はプロジェクトのNPMページやリポジトリのホームページの外の他の場所でそれを見ることができません.
だからここに女性と紳士、モチベーション!

ロードマップ


だから、その動機を念頭に置いて、このカラフルなバレエを作成するために使用するツールを満たしましょう.

データ


これらのゲージを表示するには、与えられたGithubリポジトリのプログラミング言語で区切られたバイト数を取得する方法が必要です.ここではGitHubリポジトリを扱っているので簡単です.Github APIを見て、何を推測する場所ですか?Githubは既に私たちについて考え、そのためだけに大きな資源を持っています.リポジトリバイトをフェッチして、そのプログラミング言語によって分割されたhttps://api.github.com/repos/quickey/quickey/languages .
レスポンスは以下の通りです.
{
  "TypeScript": 57718,
  "JavaScript": 11731,
  "CSS": 2708,
  "HTML": 899
}


Colors are the smiles of nature — Leigh Hunt


GitHubのリポジトリ言語バーの各言語にはユニークな色があります.例えば、JavaScriptの色は淡い黄色(CHERHINE ECE 066)、CSSはダークパープル(経度503 F 7 A)です.ここで問題が見えますか.どのように多くのプログラミング言語や技術を知っていますか?また、何人?私の推測は、それがあまりに多いです.Github、迅速な研究の助けを借りて、私はこのリポジトリLinguist .
言語学者のソースコードには、すべての言語や技術、これまでユニークな色を含むそれらのそれぞれにいくつかのメタデータをgithubに知られているYAMLファイルが含まれています!私は簡単なスクリプトを作成したので、YAMLをフェッチしてJSONに変換して、ソースコード内のモジュールとして保存するのは簡単です.
#!/usr/bin/env node

const yamljs = require("yamljs")
    , axios = require("axios")
    , path = require("path")
    , fs = require("fs")
    , LANGUAGES_YML_URL = "https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml"
    , DESTINATION_PATH = path.resolve(__dirname, "../data/languages.js");

(async () => {
    try {
        const response = await axios.get(LANGUAGES_YML_URL)
            , json = yamljs.parse(response.data)
            , raw = JSON.stringify(json, null, 4);

        fs.writeFileSync(DESTINATION_PATH, `module.exports = ${raw};`);
    } catch (err) {
        console.error("- failed to fetch and parse languages yml", err);
    }
})();

ゲージ


今、我々はデータと色を持って、我々は行くことができますし、ゲージを作成!
数週間前、私はスケッチアプリで遊んで始めた.私がスケッチについて好きである1つのものは、ベクトル形をつくって、SVGとして彼らをエクスポートする能力です.
オープンスケッチ、いくつかのベクトルを作成し、いくつかのテキストを追加し、10分後、私はこの素敵なゲージを持っていた!

SVGにこのゲージをエクスポートして、少しそれを掃除した後に、私は以下のコードで終わりました:
<svg width="100px" height="120px" viewBox="0 0 100 120" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g transform="translate(0.000000, -10.000000)">
            <circle fill="#FFDA76" cx="51" cy="51" r="32"></circle>
            <g transform="translate(45.000000, 27.000000)" fill="#000000">
                <path d="M6,0 L6,0 C7.65685425,-3.04359188e-16 9,1.34314575 9,3 L9,21 C9,22.6568542 7.65685425,24 6,24 L6,24 C4.34314575,24 3,22.6568542 3,21 L3,3 C3,1.34314575 4.34314575,3.04359188e-16 6,0 Z"></path>
                <circle cx="6" cy="24" r="6"></circle>
            </g>
            <path d="M51,87 C70.882251,87 87,70.882251 87,51 C87,31.117749 70.882251,15 51,15 C31.117749,15 15,31.117749 15,51" stroke="#000000" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" transform="translate(51.000000, 51.000000) rotate(-45.000000) translate(-51.000000, -51.000000) "></path>
        </g>
        <text font-family="OpenSans-Regular, Open Sans" font-size="12" font-weight="normal" fill="#000000">
            <tspan x="22.8066406" y="92">JavaScript</tspan>
        </text>
        <text font-family="OpenSans-Bold, Open Sans" font-size="12" font-weight="bold" fill="#000000">
            <tspan x="37.7451172" y="110">50%</tspan>
        </text>
    </g>
</svg>

静的SVGからダイナミックPNGまで


私はSVGがスケーラブルなベクトルグラフィックスのため、私はゲージSVGを取ることができる、巨大な次元にそれを変更することを意味し、品質は元のサイズと同じままです.もう一つは、SVGは純粋で読みやすいXMLでできているということです.HTMLのように、各図形またはラベルはマークアップ要素で作成されます.
アイデアは、このSVGのマークアップを取るし、それを変更するには、言語、パーセンテージ、色、およびゲージの回転を動的に設定することです.その後、このSVGを取り、PNGのようなイメージ形式に変換する必要があります.
私は、JavaScriptとNODEJSを使用しているので、NPMレジストリのクイック検索と私は見つかりましたSharp , SVGを入力として様々な画像フォーマットとサイズに変換するライブラリ.

ドットの接続


我々はLangaugeサービスを開発するために使用するツールに会った後、エッジを探索し、どのように我々はこれらの頂点を一緒に置くことができるかを見てみましょう.

正しいWebフレームワークの選択


私がこのポストで以前に言ったように、私はNodejsをLangaugeサービスのためのバックエンドとして使っています.私は、ウェブフレームワークとしてExpressと共に働いていました.それでも、何かが欠けていると感じました.これが私が作った理由ですInjex , タイプスクリプトアプリケーションのための依存性注入フレームワーク
Injexはプラグインシステムによって供給された依存性注入IOCコンテナを含んでいます.
The Injex Express Plugin エクスプレスアプリケーションの開発を見て、よりエレガントな感じ.
私たちのサービスは1つのエンドポイント、リポジトリの所有者と名前を取得し、リポジトリで使用されるプログラミング言語のカラフルなゲージのイメージで応答する1つだけです.私はこのポストの全体のソースコードを越えません、そして、あなたは行くことができます、そしてread it あなた自身で.代わりに、サービスドメインの部分をカバーします.

リクエストモデル


Langaugeに各要求を無効に色のようなオプションでカスタマイズすることができます、列の数を設定し、より.私は、リクエストモデルを記述するためにTypeScriptインターフェイスを使用するつもりです.
export interface ILangaugeOptions {
    type: GaugeType;
    output: OutputFormat;
    threshold: number;
    colors: boolean;
    columns: number;
    scale: number;
}

export interface IRequestModel extends ILangaugeOptions {
    owner: string;
    repo: string;
    maxAge: number;
}

コントローラ


コントローラは、各着信リクエストを処理する/:owner/:repo . RenderメソッドはExpress要求と応答の引数を受け取り、モデルをManagerに渡してイメージをレンダリングします.
@define()
@singleton()
@controller()
export class LangaugeController {

    @inject() private langaugeManager: LangaugeManager;

    @get("/:owner/:repo")
    @middleware(RequestValidationMiddleware)
    public async render(req: ILangaugeRequest, res: Response<Buffer>) {

        res.setHeader("Content-Type", OutputFormatContentType[req.model.output]);
        res.setHeader("Content-Encoding", "gzip");
        res.setHeader("Cache-Control", `max-age=${req.model.maxAge}`);

        const bitmapBuffer = await this.langaugeManager.generate(req.model.owner, req.model.repo, req.model);
        const buffer = await gzip(bitmapBuffer);

        res.send(buffer);
    }
}
私たちはLangaugeController クラスを使用しているIncexコントローラとしてのクラス@controller() デコレータ.レンダリングメソッドは@get() ハンドラ/:owner/:repo 路線図RequestValidationMiddleware リクエストバリデータミドルウェアとして.バリデータが失敗した場合、エラーがクライアントに戻ります.
次に、generate リクエストモデルを持つ言語マネージャのメソッド、結果をクライアントとしてイメージとして送信します.
public async generate(owner: string, repo: string, options: ILangaugeOptions): Promise<Buffer> {
    try {
        const createRenderer = this.rendererCreators[options.type];

        let languages = await this.githubService.getRepositoryLanguages(owner, repo);

        if (options.threshold) {
            languages = this.thresholdLanguagesFilter(languages, options.threshold);
        }

        const totalBytes = _(languages).values().sum();

        const renderer = createRenderer(options, totalBytes, languages);

        return await renderer.render();

    } catch (err) {

        this.$injex.logger.error(`failed to render langauge for type ${options.type}`, err);

        return Buffer.from("");
    }
}
Generatorメソッドは、残りのモデルオプションを引数としてGitオーナーとREPOを受け取ります.第3行では、クリエイターの辞書からRenderer CreatorrendererCreators 辞書はタイプオプションによって索引付けされます.行5は、ロードマップのセクションで以前に見たように、Github APIからリポジトリ言語を取得します.次に、この値の下に任意の言語使用率をフィルタリングするしきい値オプションを使用します.行15で、Renderメソッドを呼び出し、ビットマップバッファーを返します.この場合、生成されたメソッドから返されます.

SVGテンプレート


レンダリングする前に、SVG用のテンプレートを動的に変更し、異なるデータでコンパイルする必要があります.私はこの仕事のためにハンドルバーを使用しています.ハンドルは、あなたがフラストレーションなしで効果的にテンプレートを作るようにするために必要な力を提供します.それで、私はロードマップから見たスケッチから生成されたSVGを取り、このハンドルバーテンプレートに変換しました.
<svg viewBox="0 0 {{width}} {{height}}" version="1.1" xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">

    {{#each languages}}
    <g transform="translate({{this.translateX}}, {{this.translateY}})" stroke="none" stroke-width="1" fill="none"
        fill-rule="evenodd">
        <g transform="translate(0, -10.000000)">

            <circle fill="{{this.color}}" cx="51" cy="51" r="32"></circle>

            <g transform="translate(45.000000, 27.000000) rotate({{this.rotation}}, 6, 24)" fill="#000000">

                <path
                    d="M6,0 L6,0 C7.65685425,-3.04359188e-16 9,1.34314575 9,3 L9,21 C9,22.6568542 7.65685425,24 6,24 L6,24 C4.34314575,24 3,22.6568542 3,21 L3,3 C3,1.34314575 4.34314575,3.04359188e-16 6,0 Z">
                </path>

                <circle cx="6" cy="24" r="6"></circle>
            </g>

            <path
                d="M51,87 C70.882251,87 87,70.882251 87,51 C87,31.117749 70.882251,15 51,15 C31.117749,15 15,31.117749 15,51"
                stroke="#000000" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"
                transform="translate(51.000000, 51.000000) rotate(-45.000000) translate(-51.000000, -51.000000) ">
            </path>

        </g>

        <text text-anchor="middle" font-family="'OpenSans-Regular, Open Sans'" font-size="12" font-weight="normal"
            fill="#000000">
            <tspan x="50" y="92">{{this.language}}</tspan>
        </text>
        <text text-anchor="middle" font-family="'OpenSans-Bold, Open Sans'" font-size="12" font-weight="bold"
            fill="#000000">
            <tspan x="50" y="110">{{this.percent}}%</tspan>
        </text>
    </g>
    {{/each}}
</svg>
このテンプレートファイルから学ぶことができるので、このスキーマでコンパイルします.
{
    // SVG box width
    width: number;

    // SVG box height    
    height: number;

    // Array of languages
    languages: [{
        // X position translation
        translateX: number;

        // Y position translation       
        translateY: number;

        // Gauge color       
        color: string;

        // Gauge needle rotation        
        rotation: number;

        // Language name      
        langauge: string;

        // Usage in percents        
        percentage: number;
    }]
}

すべてを包む


さて、レンダラのコードを見て、どのようにハンドルバーのテンプレートを取り、カラフルなゲージのイメージに変換するかを見ましょう.
protected async _render(): Promise<sharp.Sharp> {
    const languages = this.hydrateRendererLanguages()
        , totalLanguages = languages.length
        , [width, height] = this.calculateCanvasSize(totalLanguages)
        , destWidth = width * this.options.scale
        , destHeight = height * this.options.scale
        , dpi = DEFAULT_DPI * destWidth / width
        , svg = Buffer.from(handlebars.compile(SOLID_TEMPLATE)({ languages, width, height }));

    return sharp(svg, { density: dpi })
        .resize(destWidth, destHeight);
}
作成時に、レンダラはリクエストモデルから元のオプションを受け取り、各言語の合計バイト、オブジェクトキーは言語名、値はバイトからのバイト数です.
まず最初に、このオブジェクトをとって、それを色のようなより多くの特性と総バイトのパーセンテージで言語の配列に変える必要があります.コードはまっすぐ進む.losonを使用してJSONオブジェクトを配列に変換します.
return _.reduce(languagesBytes, (result: IRendererLanguage[], bytes: number, language: string) => {

    const percent = bytes / this.totalBytes * 100;

    result.push({
        language,
        bytes,
        percent: parseFloat(percent.toFixed(1)),
        color: this.getLanguageColor(language)
    });

    return result;
}, []);
色とパーセンテージで言語の配列を持っているので、ハンドルバーのテンプレートをコンパイルできます.
Translatex、Translatey、および回転のプロパティで言語のそれぞれを水和させる必要があります.The hydrateRendererLanguages メソッドは、SVG内の各ゲージの位置と回転を計算します.
private hydrateRendererLanguages(): Array<IRendererLanguage & { rotation: number; translateX: number; translateY: number; }> {
    const results = [];
    const languagesRows = _.chunk(this.languages, this.options.columns);

    let currentLanguage: IRendererLanguage;

    for (let i = 0, rows = languagesRows.length; i < rows; i++) {

        for (let j = 0, columns = languagesRows[i].length; j < columns; j++) {

            currentLanguage = languagesRows[i][j];

            results.push({
                ...currentLanguage,
                rotation: currentLanguage.percent / 100 * (ROTATION_EDGE_DEGREE * 2) - ROTATION_EDGE_DEGREE,
                translateX: j * GAUGE_WIDTH,
                translateY: i * GAUGE_HEIGHT
            });
        }
    }

    return results;
}
ご覧のように、LodDash Chunk関数を使用して、列の行と行の行列を作成します.デフォルト値は言語の数ですので、列に値がない場合は1行だけを取得します.
7行目と9行目では、行列を繰り返して計算を計算します.私はスケッチで作成ゲージを覚えてる?針は0°で北に向かっている.0 %−−135°、100 %=135°とする必要があるので、ライン15で回転値を算出する.xとyの並びはかなり単純であり、両方とも線16と17で計算される.
レンダラに戻りましょう.Hydred Language Arrayを持っているので、テンプレートをコンパイルしてシャープに送って残りを行う必要があります.

結果


最終的な結果を見るにはhttps://badge.langauge.io/:OWNER/:REPO プロジェクトLangaugeバッジを見るには!
例えば、Invex用のラングージバッジです.
https://badge.langauge.io/uditalias/injex

概要


それで、それを合計するために、我々はちょうどGithub APIからのデータを結合する方法を見ました.次に、アプリケーションモジュールと依存関係を管理するために、Incexフレームワークを使用してサーバーを構築する方法を見ました.
私はあなたがこのサービスを構築した方法を楽しんでほしい.私はあなたが行くとチェックアウトsource code repository . 私は、サービスについてのフィードバックを感謝し、任意の貢献に感謝する.
利便性のため、このサービスモードでは、このサービスの有効なバージョンをテストできます
ハッピーコーディング!
daily.dev すべての新しいタブを最高のプログラミングニュースを提供します.私たちはあなたのための資格のソースの数百人は、将来をハックできるようにランク付けされます.