サポンテ 勉強ノート

サポンテの勉強ノート・読書メモなどを晒します。

e-Stat 時間軸コードを、人間が見て分かりやすい表現に変換する Excel 関数【Excel/ユーザー定義関数/E_STAT_TO_STRING()】

はじめに

 官公庁の統計で使われている「e-Stat 時間軸コード」を見やすく変換する Excel 関数(ユーザー定義関数)です。

 こんなもの、探せばありそうな気がしたのですが無かったので VBA で作りました。

関数の使い方イメージ

 セルに数式で =E_STAT_TO_STRING(A1) などとすると、以下のイメージのように文字列変換して表示します。

 範囲外のコード値を使用すると「#VALUE!」エラーに、数字以外の文字が含まれると「#NUM!」エラーを返します。

続きを読む

Redmineのチケットの内容をコミットメッセージにするブックマークレット【Bookmarklet】

はじめに

 VCS のコミットが、Redmine のチケット単位になることはしばしばあります。コミットメッセージに書く内容も、同じような感じになるかもしれません。

 だったら、Redmine の内容から自動でコミットメッセージに変換してくれればいい。

 ブックマークレットを作成します。

続きを読む

Launchy を利用して素早く新規ファイルを作成したい【Windows】【VBScript】

はじめに

 macOS には Spotlight がありますが、Windows には無いので代わりに Launchy を使っています。先日作ったものWindows 用にも作ってみました。

 現在、Windows が手元にないので動作未検証です。そのうち検証します。

【追記(2022/06/26):エクスプローラーの「最前面ウィンドウ」が取得できませんでした。ひとまず断念。折を見てまた挑戦します】

VBScript でできるか

仕様

  1. 新規ファイルを作成する場所は、エクスプローラーで今開いている場所。なければデスクトップ。
  2. ファイル名は、デフォルトで日付文字列 + 拡張子。
  3. ファイル作成後は、すぐにファイル名の変更ができるように、エクスプローラーで選択状態にする。

インストール

 以下のソースコードを拡張子 ".vbs" で保存し、Launchy で開けるようにしておきます。

 ファイル名は "nm新規マークダウン書類newmarkdown.vbs" のような名前で良いでしょう。

Option Explicit

Private Const EXTENSION = ".md"

' ---
' エクスプローラーで開いているフォルダのパスを取得する。
' 取得できなければ、デスクトップフォルダのパスを取得する。
' 参考:
'   https://r2z.hateblo.jp/entry/20100731/p1
'   https://www.ka-net.org/blog/?p=3782
' ---
Dim shell, fso
Set shell = CreateObject("Shell.Application")
Set fso = CreateObject("Scripting.FileSystemObject")

Dim path
path = ""
Do
    Dim wnd
    For Each wnd In shell.Windows
        If InStr(TypeName(wnd.document), "IShellFolderViewDual") >= 0 Then
            ' エスクプローラーウィンドウならパスを取得する
            path = wnd.Document.Folder.Self.Path
            Exit For
        End If
    Next
    Exit Do
Loop

If path = "" Then
    ' ウィンドウを開いていなければ、デスクトップのパスを取得する
    Private Const SSFC_DESKTOP_DIRECTORY = 16
    path = shell.Namespace(SSFC_DESKTOP_DIRECTORY).Self.Path
End If
Set shell = Nothing

' ---
' 日付文字列のファイル名を作成する
' ---
Dim fileName
fileName = Year(Now) & "-" & _
    Right("00" & Month(Now), 2) & "-" & _
    Right("00" & Day(Now), 2)

Dim fullName
fullName = path & "\" & fileName & EXTENSION

' ---
' フルパスを作成する
' ---
Dim seq
seq = 1
Do
    If fso.FileExists(fullName) Then
        fullName = path & "\" & fileName & "-" & seq & EXTENSION
        seq = seq + 1
    Else
        Exit Do
    End If
Loop

' ---
' ファイルを作成する
' ---
Dim fileH
Private Const FOR_WRITING = 2
Private Const CREATE_FILE = True
Set fileH = fso.OpenTextFile(fullName, FOR_WRITING, CREATE_FILE)
fileH.Close
Set fileH = Nothing
Set fso = Nothing

' ---
' エクスプローラーで選択状態にする
' ---
Dim wsh
Set wsh = CreateObject("WScript.Shell")
wsh.Run "explorer.exe /select, """ & fullName & """"
Set wsh = Nothing

さいごに

 Launchy には ".lnk" ファイルをカタログするようにしておいて、ショートカットのプロパティで拡張子を起動オプションにすれば、".txt" もほとんど同じコードでいけるかと思います。

AppleScript の do shell script で実行されるシェル環境を調べる

 昨日のスクリプトを作っているときに気になったので。

 スクリプトエディタ.app で以下の AppleScript を実行します。

-- 使われているシェルとそのバージョン取得
set shName to do shell script "echo $SHELL"
log (do shell script shName & " --version")

-- 環境変数の取得
log (do shell script "set")

-- AppleScript のバージョン取得
log ("AppleScript version: " & version of AppleScript)

-- macOS のバージョン取得
log (do shell script "sw_vers")

Spotlight を利用して素早く新規ファイル作成【macOS】【AppleScript】

はじめに

 「Finder で『この場所に新しいテキストファイル』を作成したい」際に、クリックひとつでテキストファイルが作成できるよう、かつて AppleScript で作成したアプリケーションを、Finder ウィンドウのツールバーに登録していました。メモファイルをたくさん作るためです。

 しかし最近は、同じように .md ファイル(Markdown ファイル)を素早く作りたいケースが増えてきました。でもツールバーのボタンを、むやみに増やしたくありません。

 ツールバーのアプリケーションアイコンをクリックした際に、新規作成したファイルの候補を選択するような UI を最初に考えたのですが、それよりは Spotlight で「new_text」とか「new_md」とか、いやいっそ「nt」とか「nm」とかタイプするだけでテキストファイルや .md ファイルが作成できるようにしたい。

続きを読む

Mac の日本語 IME のユーザ辞書内容を Google 日本語入力の辞書へ移行するスクリプト

はじめに

 Mac(Catalina)の日本語 IME があんまりにもひどいので1Google 日本語入力を試してみることにしました。

 しかしインストールしてもユーザ辞書を自動的にインポートしてくれたりはしませんでした。ChromeSafari のブックマークをインポートするかどうか選べるのに。


  1. 学習しない。正確に言うと、学習内容がしばらくするとなぜか消される。ことえりより明らかに悪化しています。
続きを読む

TSV(Excel または他の表計算ソフト)から xUnit のテスト引数(InlineData)に変換するコードジェネレータ【C#】

はじめに

 xUnit で [Theory] を指定するテストメソッドでは [InlineData()] を使用して、複数のテストケースを記述できます。

 ですが引数が多くなってくると面倒で、楽をしたくなります。

 いつものごとく、コードジェネレータを作成します。

コードジェネレータ

 左の入力欄に、サンプルのように TSV 文字列をコピペして、convert ボタンをクリックしてください。右の入力欄に、生成した C# のコードが出力されます。

 サンプル文字列が既に入力されているので、convert ボタンをクリックするだけで動作を確認できます。

生成されるコード例

 以下のようなコードが出力されます。

[Theory()]
[InlineData(1, "Apple", 200)]
[InlineData(2, "Banana", 150)]
[InlineData(3, "Citrus", 300)]
public void TestMethod(int Id, string Name, int Price)

オプション

 オプションが二つあり、チェックボックスで有効/無効を切り替えます。

Use CamelCase

 チェックすると、メソッドの引数部分の識別子をキャメルケースにします。

Use String

 チェックすると、全ての引数を文字列としてコードを生成します。

ソースコード

 念のため、ソースコードも載っけておきます。JSFiddle がサービス終了しちゃうかもしれませんからね。

 それに、オフラインで使いたいという要件もあるかもしれません(その場合、CDN のところは修正してください)。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TSV (from Excel or other) to xUnit InlineData Definition</title>
    <style>
        .outer {
            display: flex;
            justify-content: center;
            align-items: center;
        }
        #app {
            margin: 0 auto;
            display: inline-block;
            text-align: left;
            height: 95%;
        }
        textarea {
            font-family: monospace;
        }
        @media (prefers-color-scheme: dark) {
            body {
                background-color: #333;
                color: #fff;
            }
            textarea {
                background-color: #444;
                color: #fff;
            }
        }
    </style>
</head>
<body>
    <div class="outer">
        <div id="app">
            <table>
                <tr>
                    <td rowspan="3">
                        <textarea name="" id="src" cols="30" rows="10" v-model="src" placeholder="Excel などからコピペしてください"></textarea>
                    </td>
                    <td></td>
                    <td rowspan="3">
                        <textarea name="" id="result" cols="30" rows="10" v-model="result"></textarea>
                    </td>
                </tr>
                <tr>
                    <td>
                        <input type="checkbox" id="useCamelCase" v-model="useCamelCase" />
                        <label for="useCamenCase">Use CamelCase</label>
                        <br />
                        <input type="checkbox" id="useString" v-model="useString" />
                        <label for="useString">Use String</label>
                        <br />
                    </td>
                </tr>
                <tr>
                    <td>
                        <button v-on:click="convert">convert</button><br />
                        <button v-on:click="copyResult">copy</button>
                    </td>
                </tr>
            </table>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
    <script>
        var vm = new Vue({
            el: "#app",
            data: {
                src: "Id\tName\tPrice\n1\tApple\t200\n2\tBanana\t150\n3\tCitrus\t300",
                result: "",
                ln: "grid",
                useCamelCase: true,
                useString: false,
            },
            methods: {
                convert: function (e) {
                    let ret = '';
                    let lines = this.getLines();
                    const useCC = this.useCamelCase;
                    const useStr = this.useString;

                    // create header
                    ret = "[Theory()]\n";
                    lines.forEach((line, row) => {
                        if (row === 0) {return;}
                        var csv = [];
                        line.forEach((col, idx) => {
                            if (useStr || isNaN(col)) {
                                csv.push('"' + col + '"');
                            } else {
                                csv.push(col);
                            }
                            
                        });
                        ret += "[InlineData(" + csv.join(', ') + ")]\n";
                    });

                    // create types
                    let types = [];
                    const isInt = function (src) {
                        const n = Number.parseFloat(src);
                        return Number(n) === n && n % 1 === 0;
                    };
                    lines[1].forEach((col, idx) => {
                        if (!this.useString && !isNaN(col)) {
                            if (isInt(col)) {
                                types.push('int');
                            } else {
                                types.push('double');
                            }
                        } else {
                            types.push('string');
                        }
                    });

                    // create method 
                    ret += 'public void TestMethod(';
                    var params = [];
                    lines[0].forEach((col, idx) => {
                        if (useCC) {col = this.camelCase(col);}
                        params.push(types[idx] + ' ' + col);
                    });
                    ret += params.join(', ');
                    ret += ")\n";
                    this.result = ret;
                },
                getLines: function () {
                    var linesSrc = this.src.split("\n");
                    var lines = [];
                    linesSrc.forEach(line => {
                        if (line.trim() ==='') {return;}
                        lines.push(line.trim().split("\t"));
                    });
                    return lines;
                },
                camelCase: function (str){
                    // http://webdesign-dackel.com/2015/05/15/js-change-case/
                    str = String(str);
                    str = str.charAt(0).toLowerCase() + str.slice(1);
                    return str.replace(/[-_](.)/g, function(match, group1) {
                        return String(group1).toUpperCase();
                    });
                },
                copyResult: function () {
                    navigator.clipboard
                        .writeText(this.result)
                        .then(() => {
                            console.log('copied!')
                        })
                        .catch(e => {
                            console.error(e)
                        });
                }
            }
        });
    </script>
</body>
</html>

最後に

 2021-11-30 バグ修正。

 なんか Edge だとコピーができない。