GitHubのコードブロック右上コピーボタンをサンプル実行ボタンのように使う


# 概要
- [GitHubのMarkdownのコードブロック表示、右上にコピーボタンがつきました。クリックするだけでコードをクリップボードに入れてくれる便利なボタンです。](https://github.community/t/feature-request-code-copy-button-on-markdown-code-snippets/711)
- これで、GitHubのMarkdownのサンプルコードをコピーしてはVS Codeに貼り付けて保存、GitHubのMarkdownのサンプルコードをコピーしてはVS Codeに貼り付けて保存、とやるだけで、サンプルコードがどんどん実行できて便利ですね。
- …貼り付けて保存、手間ですね。なんとか（自動化）したいですよね。
- ということで自動化しました。
- 対象は、__1ファイルすべてがコードブロックに書かれているもの__です。例えば[crisp-game-lib](https://github.com/abagames/crisp-game-lib)のサンプルコードの一部が該当します。

# コード

- Windows + PowerShell 用です。ほかのOSや別の言語でも同様のことができると思います。

```ps1:chromeClipboardSaver.bat
@powershell -NoProfile -ExecutionPolicy Unrestricted "$s=[scriptblock]::create((gc \"%~f0\"|?{$_.readcount -gt 1})-join\"`n\");&$s" %*&goto:eof
#↑ps1として編集するときは、この行を#でコメントアウトし、拡張子をps1にする

# chromeクリップボードsaver
# クリップボードが変化し、かつ、アクティブウィンドウ.Name が $target（デフォルトはchrome） の場合、
#   指定したファイルにセーブします
# 用途の例
#   GitHubのMarkdownで書かれたサンプルコードのコードブロックが大量にあるとき、
#   それぞれの右上のコピーボタンをクリックしてゆくだけで
#   サンプルコードをどんどん実行していける。
#   そうするには、当ツールを起動して「npm run watch_games等で更新監視中のmain.js等」を指定すればよい。

# 設定
$target = "chrome"

# ファイル選択ダイアログを開く
Add-Type -AssemblyName PresentationFramework
function openFileDialog($title) {
    $dlg = New-Object Microsoft.Win32.OpenFileDialog -Property @{ 
        InitialDirectory = Get-Location
        #Filter = ''
        Title = $title
    }
    if ($dlg.ShowDialog()) {
        $dlg.FileName
    } else {
        exit
    }
}

# バックアップファイルを作成する
function backup($backupSource) {
    $todayTime = Get-Date -Format "yyyyMMdd_HHmmss"
    Copy-Item -Path $backupSource -Destination "$backupSource.$todayTime"
}

# アクティブプロセスを得る
function getActiveProcessInformation() {
$code = @'
    [DllImport("user32.dll")]
     public static extern IntPtr GetForegroundWindow();
    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out int ProcessId);
'@

    Add-Type $code -Name Utils -Namespace Win32
    $myPid = [IntPtr]::Zero;
    $hwnd = [Win32.Utils]::GetForegroundWindow()
    $null = [Win32.Utils]::GetWindowThreadProcessId($hwnd, [ref] $myPid)
    return (Get-Process | Where-Object ID -eq $myPid | Select-Object Name,processName,Id,Path,MainWindowTitle)
}

# クリップボードを監視する
function monitorClipboardText() {
    (Get-Host).UI.RawUI.WindowTitle = $MyInvocation.MyCommand
    Add-Type -AssemblyName System.Windows.Forms
    $clipText = [Windows.Forms.Clipboard]::GetText()
    while ($true) {
        $latestClipText = [Windows.Forms.Clipboard]::GetText()
        if ($latestClipText -ne $clipText) {
            $clipText = $latestClipText
            onChangeClipboardText
        }
        Start-Sleep -Milliseconds 16
    }
}

# クリップボードが変化した場合、 $filename にセーブする
function onChangeClipboardText() {
    $info = getActiveProcessInformation
    if ($info.Name -eq $target) {
        $clipText | Set-Content -Encoding UTF8 $filename # UTF8 with BOMになる。BOMなしを選ぶ場合は別の手を使うこと
        "---"
        "クリップボードをセーブしました：[" + $filename + "]"
        $clipText
    } else {
        "アクティブウィンドウのNameが [" + $target + "] の場合のみセーブします"
        "このアクティブウィンドウはクリップボードセーブの対象外です："
        $info
    }
}

#
$filename = openFileDialog "クリップボードセーブするファイルを選んでください"
"バックアップします：[" + $filename + "]"
backup $filename
"クリップボードを監視してセーブします：[" + $filename + "]"
monitorClipboardText
```

# 使い方
- `npm run watch_games`等を実行し、`main.js`等を更新したらブラウザ等に自動で反映される状態にします。
- 実行したら、`main.js`等を選びます。選ぶと一度バックアップされます。
- chromeでGitHubのREADME.md等のMarkdownで書かれたコードブロックのあるページを開きます。
- コードブロックの右上のコピーボタンを押します。
- コードブロックの内容が、`main.js`等に保存されます。
- 自動でブラウザ等に反映されることを確認します。
- コピーボタンをポチポチ押します。
- ポチポチするごとにどんどんブラウザ等に反映されることを確認します。

# パーツ

- パーツも残しておきます。今後ほかの用途でパーツを使う場合に再度検索と選定と動作確認とリファクタリングをしなくて済む用です。

## バックアップをとる
- 実行すると、file.txt のバックアップファイルを作成します。

```ps1:copyFileForBackup.ps1
function backup($backupSource) {
    $todayTime = Get-Date -Format "yyyyMMdd_HHmmss"
    Copy-Item -Path $backupSource -Destination "$backupSource.$todayTime"
}

backup "file.txt"
```

## アクティブプロセスの情報を得る

- 実行すると、16ミリ秒ごとにアクティブプロセス（アクティブウィンドウ）の情報を出力します。アクティブプロセスがchromeの場合は表示を変更します。

```ps1:getActiveProcessInformation.ps1
# https://qiita.com/Xiacchi/items/ad7d35eb476f2f2fb8ae
# を元に、パーツとして使いやすいように関数化した

# アクティブプロセスを得る
function getActiveProcessInformation() {
$code = @'
    [DllImport("user32.dll")]
     public static extern IntPtr GetForegroundWindow();
    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out int ProcessId);
'@

    Add-Type $code -Name Utils -Namespace Win32
    $myPid = [IntPtr]::Zero;
    $hwnd = [Win32.Utils]::GetForegroundWindow()
    $null = [Win32.Utils]::GetWindowThreadProcessId($hwnd, [ref] $myPid)
    return (Get-Process | Where-Object ID -eq $myPid | Select-Object Name,processName,Id,Path,MainWindowTitle)
}

# 一定時間ごとにアクティブウィンドウ情報を得、指定文字列かどうかで分岐する
function loop_getActiveProcessInformation($target) {
    while ($true) {
        $info = getActiveProcessInformation
        if ($info.Name -eq $target) {
            "hitしました"
        } else {
            $info
        }
        Start-Sleep -Milliseconds 16
    }
}

loop_getActiveProcessInformation "chrome"
```

## クリップボードを監視する

- 実行すると、16ミリ秒ごとにクリップボードを監視し、変化があった場合はそれを出力します。

```ps1:monitorClipboardText.ps1
# https://qiita.com/saggie/items/481461a436a1d6801bbd
# を元に、パーツとして使いやすいように関数化した

# クリップボードを監視する
function monitorClipboardText() {
    (Get-Host).UI.RawUI.WindowTitle = $MyInvocation.MyCommand
    Add-Type -AssemblyName System.Windows.Forms
    $clipText = [Windows.Forms.Clipboard]::GetText()
    while ($true) {
        $latestClipText = [Windows.Forms.Clipboard]::GetText()
        if ($latestClipText -ne $clipText) {
            $clipText = $latestClipText
            # クリップボードが変化した場合
            onChangeClipboardText
        }
        Start-Sleep -Milliseconds 16
    }
}

# クリップボードが変化した場合
function onChangeClipboardText() {
    $clipText
}

monitorClipboardText
```

## ファイル選択ダイアログを開く

- 実行すると、ファイル選択ダイアログを開きます。ファイルを選ぶかキャンセルしたあとは、選択結果を出力します。
    - ダイアログの初期ディレクトリはひとまずカレントディレクトリとしていますが、「最後にダイアログで使ったディレクトリ」にしたい場合は、「InitialDirectory = Get-Location」をコメントアウトします。

```ps1:openFileDialog.ps1
Add-Type -AssemblyName PresentationFramework
function openFileDialog() {
    $dlg = New-Object Microsoft.Win32.OpenFileDialog -Property @{ 
        InitialDirectory = Get-Location
        #Filter = ''
        Title = $title
    }
    if ($dlg.ShowDialog()) {
        return $dlg.FileName
    } else {
        return "選択キャンセル"
    }
}

openFileDialog
```

## クリップボードをUTF8BOMつきでファイルに保存する

- 実行すると、クリップボードをUTF8BOMつきでファイルに保存します。

```ps1:saveClipboard.ps1
get-clipboard | Set-Content -Encoding UTF8 "utf8bom.txt" # UTF8 with BOMになる。BOMなしを選ぶ場合は別の手を使うこと
```