Windows Terminalを使って、CLIを出来るだけ一元化したい。

背景

これまでは、
sshクライアント…RLogin(少し前はputty)
windows用のCLI…Windows PowerShell(少し前はコマンドプロンプト)
を使っておりました。

まず色々開くのは好きじゃないのでRLoginのようなタブで複数操作できるのは結構魅力だったりします。
windows用のcli もそうなったらいいと思っていた中、たどり着いたのはPowerShell 7(Windows PowerShellではない)とWindows Terminalの組み合わせです。
どちらもMicrosoftが提供しているので、安心感もありますね。

Windows Terminalをインストール

まずはWindows Terminalです。
Windows11にしてから入っているのか元々入っていたのかは知らないですが、自分の環境では既に入っていました。
もしかしたら過去に同じ悩みを抱え、インストールだけしたのかもしれません。
全く記憶にないのですが…。

Microsoft StoreにありますのでMicrosoft Storeアプリで「Windows Terminal」と探せば見つかります。

Windows Terminalについて

PowerShell 7 をインストール

正直「Windows PowerShell」でもよかったのですが、PowerShell 7にした理由は、Windows PowerShellを開くと出てくる以下のメッセージが出なくなると嬉しいなぐらいの気持ちです。

「新機能と改善のために最新の PowerShell をインストールしてください! https://aka.ms/PSWindows」

https://aka.ms/PSWindows

上のリンク先から「PowerShell 7 のインストール」の
MSI パッケージを使用して PowerShell を展開する
からmsiをダウンロードしてインストールしました。

Windows Terminalを好きなように設定

最初は既定で設定されているCLIが立ちあがると思います。
こんな感じです。

タブが使えるのは嬉しいですね。

「Ctrl + 、」で設定画面を開きます。

ここからはお好みですが…

【スタートアップ】

  1. 既定のプロファイルでPowerShellを選択
  2. 既定のターミナルアプリケーションを Windows ターミナル に設定

【操作】

  1. URLを自動的に検出して、クリックできるようにするをオフ

cronとかがあった場合に誤ってクリックしないようにオフにしました。

【外観】

  1. テーマをダークに変更

やっぱりダークが好き

【 プロファイルのPowerShell 】

追加の設定にある外観をクリック後、 テキストの配色、フォント、背景を好きなものに設定して保存。

結果

良い感じになったのではないでしょうか。

Google Docsみたいな共同編集Webサイトを作るライブラリ Yjs 初めの一歩 まずは動作させてみる

Yjs と聞いて、何を思い浮かべるでしょうか?
検索すると、〇〇先輩とか出てきますが、先輩ではありません。

↓これです。

A CRDT framework with a powerful abstraction of shared data
https://github.com/yjs/yjs/tree/master

CRDTできるフレームワークと書いてありますが、CRDTとは何かと言い出すと、Conflict-free replicated data typeの略で結構難しい話になります。
そこで、CRDTをすごく平たく言うと、Google Docsとか、MicrosoftのOffice365のExcelとか、AさんとBさんが同じExcelのシートを開いていても、共同で編集できますよね。あれがCRDTです。

そんな機能を実現させるためのライブラリです。

ベルリン在住のケビン・ヤーン(?)さんが作っているようです。
ありがとう、ケビンさん!

さて、まずはめっちゃ簡単に動作させてみます。

しかし、「Yjs sample」 とかググってみても、Reactを使ったりマイクラを使ったり、TODOアプリとかちょっと複雑なアプリが多く出てきます。
そういうのじゃなくって、「Hello World」的なことがまずは最初にしたい!という人いませんか?→はい、私です。
他にもそういう方がいるのではないかと思いまして、この記事を書きました。

灯台元暗しでYjsの公式サイトに「Hello World」的なサンプルがありました!

https://docs.yjs.dev/

import * as Y from 'yjs'

// Yjs documents are collections of
// shared objects that sync automatically.
const ydoc = new Y.Doc()
// Define a shared Y.Map instance
const ymap = ydoc.getMap()
ymap.set('keyA', 'valueA')

// Create another Yjs document (simulating a remote user)
// and create some conflicting changes
const ydocRemote = new Y.Doc()
const ymapRemote = ydocRemote.getMap()
ymapRemote.set('keyB', 'valueB')

// Merge changes from remote
const update = Y.encodeStateAsUpdate(ydocRemote)
Y.applyUpdate(ydoc, update)

// Observe that the changes have merged
console.log(ymap.toJSON()) // => { keyA: 'valueA', keyB: 'valueB' }

これを、index.mjs という名前で保存します。

PCにNode.jsはインストールされていますか?されていなければ、まずはNode.jsをインストールしてください。

Node.jsがインストールされていれば、次はYjsをインストールします。
私の開発環境はWindowsです。なのでコマンドプロンプトを開いて次のコマンドを実行します。

npm i yjs y-websocket

厳密にいうと、後半のy-websocketは今は必要なさそうですが、いずれ必要になりそうなので入れておきます。

インストールできましたね!

そしたら、さっき保存したindex.mjs をnodeコマンドで動作させます。

node index.mjs

すると、上記のスクショのように、サンプルコードの下記の部分が表示されます。

console.log(ymap.toJSON()) 
('keyA', 'valueA')

だったymap が 

('keyB', 'valueB')

という ymapRemote と統合されて

 { keyA: 'valueA', keyB: 'valueB' }

になったんですね~。

すごい!
もちろん、これだけではすごさは全く伝わらないと思うので、Yjsについては今後記事を書いていきたいと思います。

単純CASE式と検索CASE式

CASE式みたいな処理って、自分の場合はこれまでアプリケーション内で行うことが多くてMySQLで行うことが少なかったです。
しかし、使う機会があって若干はまったので、備忘録として残します。

やりたかったことは、「NULLか空文字だったら値を0として取得する」ことでした。
CASE式には単純CASE式と検索CASE式があってそれが頭に入っておらず、はまっておりました。

サンプルテーブルを以下に用意します。

START TRANSACTION;
    CREATE TABLE member (
        id int(11) NOT NULL,
        name varchar(64) NOT NULL,
        gender enum('male','female') NOT NULL,
        age int(11) NOT NULL,
        memo mediumtext,
        created datetime NOT NULL,
        updated timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    ALTER TABLE member
        ADD PRIMARY KEY (id),
        ADD KEY gender (gender),
        ADD KEY age (age);

    ALTER TABLE member
        MODIFY id int(11) NOT NULL AUTO_INCREMENT;

    INSERT INTO member (name, gender, age, memo, created) VALUES
        ('suzuki', 'male', 25, 'イケメン', '2021-04-01 00:00:00'),
        ('ohtani', 'female', 30, NULL, '2021-05-01 00:00:00'),
        ('sato', 'female', 35, '', '2021-06-01 00:00:00'),
        ('nakamura', 'female', 40, 'おもしろい', '2021-07-01 00:00:00'),
        ('tanaka', 'female', 45, NULL, '2021-08-01 00:00:00'),
        ('inoue', 'male', 50, '博識', '2021-09-01 00:00:00'),
        ('ishida', 'female', 55, '優しい', '2021-10-01 00:00:00'),
        ('matsumoto',  'male', 60, '穏やか', '2021-11-01 00:00:00'),
        ('sasaki', 'male', 65, '', '2021-12-01 00:00:00'),
        ('kato', 'female', 70, '', '2022-01-01 00:00:00');
COMMIT;

テーブルの中身はざっとこんな感じ。

idnamegenderagememocreatedupdated
1suzukimale25イケメン2021-04-01 00:00:002022-05-13 14:18:42
2ohtanifemale30NULL2021-05-01 00:00:002022-05-13 14:18:42
3satofemale352021-06-01 00:00:002022-05-13 14:18:42
4nakamurafemale40おもしろい2021-07-01 00:00:00 2022-05-13 14:18:42
5tanakafemale45NULL2021-08-01 00:00:002022-05-13 14:18:42
6inouemale50博識2021-09-01 00:00:00 2022-05-13 14:18:42
7ishidafemale55優しい2021-10-01 00:00:002022-05-13 14:18:42
8matsumotomale60穏やか2021-11-01 00:00:002022-05-13 14:18:42
9sasakimale652021-12-01 00:00:002022-05-13 14:18:42
10katofemale702022-01-01 00:00:002022-05-13 14:18:42

性別が英語で入っているので日本語で取得してみたいと思います。
比較しやすいように名前と英語での性別も取得します。
以下のようにCASEのすぐ後ろにカラム名が来て、そのWHENの値と同値のCASE式を単純CASE式と呼ぶそうです。

# 単純CASE式
SELECT
    name,
    gender,
    CASE gender
        WHEN 'male' THEN '男性'
        WHEN 'female' THEN '女性'
    END AS gender_ja
FROM member;

検索結果

namegendergender_ja
suzukimale男性
ohtanifemale女性
satofemale女性
nakamurafemale女性
tanakafemale女性
inouemale男性
ishidafemale女性
matsumotomale男性
sasakimale男性
katofemale女性

maleの場合は「男性」、femaleの場合は「女性」と表示されます。

次は
60歳以上であれば「senior」
40歳以上60歳未満であれば「middle-age」
40歳未満を「young」
と出力するようにします。

検索CASE式ではWHENの後に検索条件が入ります。
CASEの後に列名は必要ありません。

# 検索CASE式
SELECT
    name,
    age,
    CASE 
        WHEN age >= 60 THEN 'senior'
        WHEN age >= 40 && age < 60 THEN 'middle-age'
        ELSE 'young'
    END AS generation
FROM member;

検索結果

nameagegeneration
suzuki25young
ohtani30young
sato35young
nakamura40middle-age
tanaka45middle-age
inoue50middle-age
ishida55middle-age
matsumoto60senior
sasaki65senior
kato70senior

応用が効くのは検索CASE式で単純CASE式は、検索CASE式でも行うことが出来ます。

最初に行った性別を日本語で表示する単純CASE式を検索CASE式で書いてみます。

# 検索CASE式
# 結果は二つ上の表と同じ
SELECT
    name,
    gender,
    CASE
        WHEN gender LIKE 'male' THEN '男性'
        WHEN gender LIKE 'female' THEN '女性'
    END AS gender_ja
FROM member;

今回やりたかったことは、「NULLか空文字だったら値を0として取得する」だったので、検索CASE式を使えば簡単に出来そうです。
CASEの後ろに memo と書いていたので若干はまっていました。

SELECT
    name,
    memo,
    CASE
        WHEN (memo IS NULL || memo = '') THEN 0
        ELSE 1
    END AS memo_exists
FROM member;

検索結果

namememomemo_exists
suzukiイケメン1
ohtaniNULL0
sato0
nakamuraおもしろい1
tanakaNULL0
inoue博識1
ishida優しい1
matsumoto穏やか1
sasaki0
kato0

めでたしです。
と、最初は上のSQLを書いたのですがCHAR_LENGTHを使えば空文字判定出来そうですね…。
TRIMも必要なら一緒にすれば良さそうです(全角スペースは除かれないので注意)。

SELECT
    name,
    memo,
    CASE
        WHEN CHAR_LENGTH(memo) > 0 THEN 1
        ELSE 0
    END AS memo_exists
FROM member;

上のIF文を使って書くことも可能です。

SELECT
    name,
    memo,
    IF(CHAR_LENGTH(memo) > 0, 1, 0) AS memo_exists
FROM member;

単純CASEで書けるところも検索CASE式で書けるので、検索CASE式 → 単純CASE式の順に学んだ方が覚えやすい気がします。

SourceTreeでAccessTokenを作ってGithubに接続できない

SourceTree Ver 3.3

実は半年ぐらい、下記のエラーが出て

git -c diff.mnemonicprefix=false -c core.quotepath=false --no-optional-locks fetch --no-tags origin
remote: Support for password authentication was removed on August 13, 2021. Please use a personal access token instead.
remote: Please see https://github.blog/2020-12-15-token-authentication-requirements-for-git-operations/ for more information.
fatal: Authentication failed for 'https://github.com/hogehoge'

SourceTreeでGithubに接続できなくて困っていました。

Githubがメールアカウントとパスワードではなく、Access Tokenというものを使ってしかGihutbクライアントからアクセスできなくなったのは有名な話ですが、下記などいろいろなサイトでやり方が紹介されているものの、

https://zenn.dev/koushikagawa/articles/3c35e503c8553a

https://syslog.life/2022/01/21/github-sourcetree-alignment-access-token/

上記のサイトのようにOauthにしてパスワードをトークンにしても、ベーシック認証にしてパスワードをアクセストークンにするなど、いろいろやってみてもダメでした。

SourceTreeをアンインストールしたりしてもダメだったので

「どうせ僕なんて…アクセストークンも使いこなせない人間なんだ…。」

とウジウジしながらコマンドプロンプトや他のGihutbクライアントを使う毎日でした。(>_<)

しかし、今日やっと解決しましたので、書いておきます!

こちら、SourceTree本家のサイトに書いてありましたね💦

Sourcetree ignores github token and throws 403 error

https://community.atlassian.com/t5/Sourcetree-questions/Sourcetree-ignores-github-token-and-throws-403-error/qaq-p/1778978#U1785059

ちゃんと英語のサイト読めよってことですね!

同じようなことに困っている方がいらっしゃると思うので、以下、やり方を書いておきます。

①右上の設定ボタンをクリックし、下記の「リモート」を表示させて、編集をクリックします。

②次の画面で、リモートの詳細設定という設定がありますが、ここのURL/パス欄に

次のように入れます。

https://<token>@<git_url>.git 

わかりづらいので解説すると、token が 123456 だとして、GithubのURLが https://github.com/hogehoge/hogeだとすると、

https://123456@github.com/hogehoge/hoge.git

と入れるということです。

すると、何と接続ができます。

ヤッター!!

新規のCloneするときも、

一番最初の 「元のパス/URL」 とある欄に、同じようにアクセストークンを含めたURLを入力します。

すると、接続できます。

ありがとう、世界!⊂(^-^)⊃ 吾妻山公園というところからの眺めです。

jest で vue のコンポーネントのメソッドをテストしたいが呼び出せない

jest 27.4.3
vue/test-utils 1.3.0
vue 2.5.2

すみませんが、jest 初心者です。

初心者らしく、初心者がハマりそうなポイントを書いておきます。

TimeTable.vue というコンポーネントがありまして、次のようにコンポーネントのテストを作ったりすると思いますが

/**
 * TimeTable.spec.js
 * @jest-environment jsdom
 */

import {createLocalVue, mount} from '@vue/test-utils'
import TimeTableVue from '../../../../../components/timeTable/templates/TimeTable'
import Vuex from 'vuex'  

 const localVue = createLocalVue()
  localVue.use(Vuex)

  let store

  const wrapper = mount(TimeTableVue, {
    stubs: ['font-awesome-icon'],
    propsData: {
      getServiceHandler: jest.fn(),
    },
    components: {},
    computed: {},
    store,
    localVue
  })

describe('text check', () => {
  it('should be some class', () => {
    expect(wrapper.find('.some_class').exists()).toBe(true)
  })
})

ここで、TimeTable.vue のメソッドに

// 略  
methods: {
    setDrivers (drivers) {
      this.drivers = drivers
    },
// 略

というメソッドがあった場合、テスト(TimeTable.spec.js)から

wrapper.setDrivers(drivers)

とやっても、

TypeError: setDrivers is not a function

みたいにメソッドが見つからないエラーになってしまいます。

これ、Vueのコンポーネントにアクセスする場合は、

wrapper.vm

を使わないとダメなんですね。

つまり、TimeTable.vue コンポーネントの中の setDrivers() にアクセスしたい場合は

wrapper.vm.setDrivers()

としないとダメです。

Vue Test Utilsの公式サイトのwrapperの説明に下記のようにありました。
https://v1.test-utils.vuejs.org/api/wrapper/#properties

———————————————————————-
# vm

Component (read-only): This is the Vue instance. You can access all the instance methods and properties of a vm

with wrapper.vm. This only exists on Vue component wrapper or HTMLElement binding Vue component wrapper.

———————————————————————-

findとか使う場合は wrapper 自体を使うので、間違えちゃいますよね!
(๑>◡<๑)