OmniAICreator/Qiita-1.07M huggingface dataset:
TableViewCellタップでアコーディオンでCollectionViewを表示する。

tableViewのcellをタップした際にcellの下にアコーディオンでcollectionViewを表示させる方法をご紹介します。

### ■考え方
TableViewCellの下にCollectionViewを配置し、デフォルトではセルの高さだけを返す。
セルが開いた時にだけCell+CollectionViewの高さを返す。
![スクリーンショット 2023-06-27 1.14.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2370083/96fdd637-4a51-8557-2318-15350a715abf.png)


### ■実装方法
```swift
    //セル開閉のフラグを設定
    var isCellOpen = false
    var selectedIndexPath: Int?
```

```swift
    // セルタップ時の処理
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let cell = tableView.dequeueReusableCell(withIdentifier: "collectionView", for: indexPath) as! CollectionView  
        if selectedIndexPath == indexPath.row {
            //選択中のセルがタップしたセルと同様だったらfalseが返される
            isCellOpen = false
        } else {
            isCellOpen = true
        }
        selectedIndexPath = indexPath.row
            
        tableView.reloadData()
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if selectedIndexPath == indexPath.row {
            if isCellOpen == false {
            //セルを閉じた時の高さを指定(Cellの高さ)
                return 40
            }
            //セルを開いた時の高さを指定(Cell+CollectionViewの高さ)
            return 200
        } else {
            //初期状態の高さを指定(Cellの高さ)
            return 40
        }
    }
```
Azure Container Registryのリポジトリ世代管理方法を考える

ACRの世代管理について検討したのでまとめます。
まずレジストリやタグ、マニフェストといった各種用語を理解しておくことが重要です。
[レジストリ、リポジトリ、成果物について](https://learn.microsoft.com/ja-jp/azure/container-registry/container-registry-concepts)

# タグなしマニフェストって？
* ACRを使っていると、Azure Portalからリポジトリを見たときに、タグ数とマニフェスト数がずれていることが見られます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180335/2fd7f9ab-0b1a-87d0-339a-19a6c1b639d7.png)

* これはACRに同じタグでpushしたときに前回のタグがnoneとなることによって発生します。
* この例では52のマニフェストがタグなしであることが分かります。
* このタグなしマニフェストはAzure Portalから確認することができません。以下コマンドを実行することでタグなしのマニフェストを確認することができます。

```bash
$ az acr manifest list-metadata --registry [レジストリ名] --name [リポジトリ名]
```

# マニフェストのタグを削除する
* ACRタスクを使って定期的にコマンドを実行できるようにします。
* 今回のコマンドだと以下条件で動作します。
    * 90日以上更新がないマニフェストのタグを削除します
    * 90日以上更新がなくても10世代は残します
    * 毎日JST20時にこの処理は動作します

```bash
$ PURGE_CMD="acr purge --ago 90d --filter '[リポジトリ名]:.*' --keep 10"

$ az acr task create --name purgeTagTask \
  --cmd "$PURGE_CMD" \
  --schedule "0 11 * * *" \
  --registry [レジストリ名] \
  --context /dev/null
```

* タスクが作成されたか確認します。

```bash
$ az acr task list-runs -r [レジストリ名]
```

# タグが付いていないマニフェストを削除する
* こちらもACRタスクを使って定期実行します。
* タグが付いていないマニフェストが対象となるため、マニフェストのタグを削除した後に実行するようにしています。
* 今回のコマンドだと以下条件で動作します。
    * タグが付いていないマニフェストを全て削除します。
        * --ago 90dオプションは--untaggedオプションを付けているときは無効になるのがポイントです（--agoは必須オプションなので指定は必要だが無効になっている）
    * 毎日JST21時にこの処理は動作します

```bash
$ PURGE_CMD="acr purge --ago 90d --filter '[リポジトリ名]:.*' --keep 10 --untagged"

$ az acr task create --name purgeManifestTask \
  --cmd "$PURGE_CMD" \
  --schedule "0 12 * * *" \
  --registry [レジストリ名] \
  --context /dev/null
```

* タスクが作成されたか確認します。

```bash
$ az acr task list-runs -r [レジストリ名]
```

# ACRタスク実行ログ確認
* ACRタスクの実行をログを確認する方法です。指定したタスクの最新の実行結果ログが確認可能です。

```bash
$ az acr task logs --registry [レジストリ名] -n [タスク名]
```

# Azure Portalからの設定
* ACRの左メニューに[保持(プレビュー)]という項目があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180335/dd995713-e7e6-1b91-19cb-a1e8f5f5d3a5.png)

* ここで保持日数を設定することが可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180335/c03807e8-7023-383c-1af1-a4081a86ebcf.png)

* ただしあくまでもタグなしマニフェストの削除にしか使えません。タグありをタグなしに変更して世代管理するには現時点ではコマンドラインで組むしかなさそうです。

* あとPortalから設定した場合、設定したタイミング以降に作られたマニフェストのみが対象になります。設定以前に作られたマニフェストは本設定の対象とはならないため注意が必要です。


以上です。
2023年のこのご時世に「Visual Studio 2008 Express Edition」をインストールする

# はじめに
「なんでこの令和の時代にVS2008なんか？」と思ったあなたは幸せ者です。

ある日、私が "生まれる前" に作られた古のプログラムのアップデートを頼まれVS2008が必要になりました。その際にダウンロードし場所を見つけるのに苦労したのでここに書き残しておきます。

ググると出てくる、『以下のリンクにありますよー（投稿日2010年）』系は恐らくリンク先が消えてるためかMicrosoftのトップページに飛ばされ、古のPDFファイルのリンクをクリックする系はなぜかBingのトップページに飛ばされたため別の方法を使いました。

# インストール方法
以下のリンクをクリックするだけです。
怪しいサイトじゃないので安心してください。WaybackMachineというサービスで残されていたリンクです。

**⚠️クリックすると即ダウンロードが始まります。**

https://web.archive.org/web/20150622033331/http://download.microsoft.com/download/6/F/1/6F161D1A-E893-4BFA-A53A-AF6B0D56A55D/VS2008ExpressWithSP1JPNX1504866.iso

「VS2008ExpressWithSP1JPNX1504866.iso」がダウンロードされるはずなので、されていたらそのISOファイルをマウントして開く。
そしたら、中にある「Setup.hta」を実行して流れに乗ってインストールすればおｋです。


WaybackMachineについては以下。

https://warp.da.ndl.go.jp/contents/reccommend/world_wa/world_wa02.html

# 余談:とある質問箱への回答
「VS2008 Expressはどこで手に入りますか？」というとある質問箱への回答。
泣いた(´；ω；`)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1303641/cf2ae110-b6e9-80e1-63b6-55498f6adfdd.png)
※私の質問ではありません
[AtCoder] Beginners Selection - ABC086C - Traveling [Elixir Livebook]

## はじめに

AtCoder の Beginners Selection コンテスト ABC086C - Traveling 問題を Elixir Livebook で回答しました

実装したノートブックはこちら

https://github.com/RyoWakabayashi/elixir-learning/tree/main/livebooks/atcoder/abc086/abc086_c

## 問題

https://atcoder.jp/contests/abs/tasks/arc089_a

## 入出力例

以下のようになれば OK です

```elixir
"""
2
3 1 2
6 1 1
"""
|> Main.solve()
|> then(&(&1 == "Yes"))
```

```elixir
"""
1
2 100 100
"""
|> Main.solve()
|> then(&(&1 == "No"))
```

```elixir
"""
2
5 1 1
100 1 1
"""
|> Main.solve()
|> then(&(&1 == "No"))
```

## 回答１

```elixir
defmodule Main do
  def main do
    :stdio
    |> IO.read(:all)
    |> solve()
    |> IO.puts()
  end

  defp split_lines(lines) do
    lines
    |> String.trim()
    |> String.split("\n")
  end

  defp split_words(words) do
    String.split(words, " ")
  end

  def solve(input) do
    points =
      input
      |> split_lines()
      |> tl()
      |> Enum.map(fn line ->
        line
        |> split_words()
        |> Enum.map(&String.to_integer/1)
      end)

    [[0, 0, 0] | Enum.drop(points, -1)]
    |> Enum.zip(points)
    |> Enum.all?(fn {[pre_t, pre_x, pre_y], [post_t, post_x, post_y]} ->
      inc_t = post_t - pre_t
      inc_d = abs(post_x - pre_x) + abs(post_y - pre_y)
      margin = inc_t - inc_d

      margin >= 0 && rem(margin, 2) == 0
    end)
    |> if(do: "Yes", else: "No")
  end
end
```

初期座標 `[0, 0, 0]` を先頭に足し、最終座標を捨てた座標リストを作ります

それと元の座標リストを `Enum.zip` することで、以下のようなリストが作成されます

```elixir
[
  {座標0, 座標1},
  {座標1, 座標2},
  {座標2, 座標3},
  ...
  {座標N-3, 座標N-2},
  {座標N-2, 座標N-1},
  {座標N-1, 座標N}
]
```

それぞれの座標で時間の増分 `inc_t` と距離の増分 `inc_d` の差 = 移動に対する余裕 `margin` を算出します

`margin` が負になっていれば移動不可能です

また、同じ所に留まれないため、余裕時間中は同じ経路を反復しなければならず、余裕時間が奇数だと目的地に戻れません

実行時間は 744 ms でした

## まとめ

`Enum.zip` は便利ですね
PHPで多次元配列から複数列を取り出す方法（array_columnの複数列版）

# 結論
```php
  function array_multi_columns(array $array, array $index_keys){
    return array_map(fn(...$row) => array_combine($index_keys, $row), ...array_map(fn($key) => array_column($array, $key), $index_keys));
  }
```
1行で短く美しく書きたかったのでこうしました。

# 解説

## 取り出したい列名のデータを取り出し
```php
array_map(fn($key) => array_column($array, $key), $index_keys))
```
「列を取り出す」という操作は、php純正関数であるarray_columnを使います。
foreachなどを使うとarray_columnの内部の最適化の恩恵を受けられないためです。

## zip操作
```php
return array_map(fn(...$row) => array_combine($index_keys, $row), ...array_map(...));
```
array_mapに複数配列を渡すとzip操作になります。
普通にzipすると列名キーが消えてしまうので、array_combineでキーを振り直してやります。
(参考:https://www.php.net/manual/ja/function.array-map.php)

## パフォーマンス比較
同じ操作の比較として、array_filterを使うやり方も考えられます。
コードは示しませんが、in_arrayやarray_flip+issetを使ってキーを検索することになります。
しかしその場合、in_arrayやissetに、各列のキーを一つ一つ渡す必要があるため、元配列の行数×元配列の列数ぶん、（in_arrayないしissetを内部実装した）自作クロージャを実行することになります。

しかし今回の実装であれば、自作クロージャの呼び出し回数は、取り出す列数＋元の配列の行数となり、ループが必要な処理をphpの内部関数に委託することができます。php内部関数は高度に最適化されているため、列の絞り込みを自作クロージャで実装するよりも遥かに高速に動作します。
サイトとオリジンを超ざっくり理解する

SameSiteのサイトってCORSのオリジンと違うの。。？と思った方向け。

別で詳しく説明されている記事もあるので、
ここでは超ざっくりと理解することを目的にする。

# サイトとは

CookieのSameSite属性などで出てくる「サイト」。

次の二つが「別のサイト」であるというのは直感的にわかるでしょう。
```text
https://sample.com
https://sample1.com
```

問題なのが次のようにサブドメインを利用している場合。これは「同じサイト」となる。
```text
https://sample.com
https://sub.sample.com
```

つまり、CookieのSameSite属性において、サブドメインを使って別ページになっているサイトへのアクセスは「同じサイト」とみなされる。

正確にいうと、サイトの定義は`eTLD+1 (effective top-level domain + 1)`でありこれが異なると別のサイト、となるのだが、
詳しい説明は下記「より詳しく知りたい方向けの記事」で紹介した記事をご覧ください。

# オリジンとは

CORSの説明などで出てくる「オリジン」
`オリジン = スキーマ + ホスト + ポート番号`である。

サイトの例で挙げたサブドメインを利用している例をもう一度見てみる。(ポート番号はデフォルト443で省略されているものとする)
```text
https://sample.com
https://sub.sample.com
```

上のURLのホストは「sample.com」で、下のURLのホストは「sub.sample.com」。
これは「別のオリジン」とみなされる。

# より詳しく知りたい方向けの記事

https://zenn.dev/agektmr/articles/f8dcd345a88c97

https://dev.classmethod.jp/articles/same-site-same-origin/
Unicode: バイト列を U+4E03 〜 U+8E0F を使ってエンコードする

# 符号化形式

名前がないと不便なので Kanji14 形式とします。

## 情報文字： U+4E10 〜 U+8E0F

ネットワーク バイト順のバイト列データを 14ビット区切りにし、Unicode の範囲 <code>U+4E10 〜 U+8E0F</code> に割り当てた文字列に変換します。

```text:バイト列の14ビット区切り形式
  +--------+--------+--------+---------+--------+--------+--------+----
D |   +0   |   +1   |   +2   |    +3   |   +4   |   +5   |   +6   | ...
  +--------+----+-+-+--------+----+----+--------+-+-+----+--------+----
B |  0..7  |0..5|6|7|  0..7  |0..3|4..7|  0..7  |0|1|2..7|  0..7  | ...
  +--------+----+-+-+--------+----+----+--------+-+-+----+--------+----
E |     [0]     |       [1]       |       [2]       |     [3]     | ...
  +-------------+-----------------+-----------------+-------------+----

D: バイト列のデータ
B: バイト内のビット位置
E: 符号化に使用する 14 ビット区切り。これに 0x4E10 を加えた文字を使用する。
```

## 制御文字： U+4E03 〜 U+4E0F

符号化したデータは14ビット単位の情報なので、元のバイト列が7の倍数でないとき、最後の情報文字（<code>U+4E10〜U+8E0F</code>）では有効な情報が一部になります。

このとき、<code>0x4E10</code> から有効なビット数(<code>1〜13</code>)を引いた <code>U+4E03 〜 U+4E0F</code> を制御文字として追加します。

## 見た目は小さくなるが…

バイナリをテキストに変換する例として Base 64 と比較してみます。

| | Base 64 | Kanji14 |
|:-:|:-:|:-:|
| 21 バイト → 文字数 | 28 | 12 (半角24) |
| 21 バイト → UTF-16 | 56 バイト | 24 バイト |
| 21 バイト → UTF-8 | 28 バイト | 36 バイト |
| シフトJIS 変換 | 影響なし | 不可 |

Kanji14 は表示すると短くなったように見えますが Base 64 の方が実用的です。


# プログラム

https://github.com/ikiuo/kanji14
cut

-d delimiter is :
-f 1st and 7th string
```sample.sh
#!/bin/sh 
     grep linuxs /etc/passwd
     cut -d : -f 1,7 /etc/passwd | grep linuxs
```
```
linuxstudy:x:1000:1000:linuxstudy:/home/linuxstudy:/bin/bash
linuxstudy:/bin/bash
```
ウヰスキーをレビューするアプリを作る【個人開発】

## どんなことができるアプリ？

ウイスキーのレビューができるアプリで、主に下記のようなことができるようにしようと思っています。
- ウイスキーの検索
- ウイスキーの詳細やレビューの閲覧
- ウイスキーのレビュー
- レビューへのいいね
- 他ユーザーのプロフィールやレビュー履歴の閲覧
- 他ユーザーのフォロー

## 作ろうと思ったきっかけ

- なんとなくトレカやレゴ、ウイスキーなどオルタナティブ投資に関わる何かを作りたいなと思っていました。
- そんなとき、妻の実家に大量のウイスキーのミニボトル（容量が50mlのやつ）があるのを見つけました。
- 「ウイスキーは結構好きだし、コレクションも楽しそうだな」と思いました。
- 「じゃあウイスキーをレビューするよくあるアプリを作ろう」と思いました。

あとは、技術的に下記をキャッチアップ&深掘りしたいなという思いもありました。
- Next.jsのapp directory
- RailsのAPIモード（あんまりちゃんとやったことなかった）
- Auth0
- アーキテクチャ

## 進捗

6月から開発を始めていますが、今のところ下記はなんとなくできてきました。

- ログイン、ログアウトできる
- ウイスキーの名前で検索したら検索結果の一覧がページネーション付きで表示される。
- 検索結果のウイスキーをクリックすると詳細ページが見れる。
    - 画像、ウイスキーの情報、レビュー
- 詳細ページからそのウイスキーをレビュー（5段階評価&コメント）できる。

次（7月?）は下記あたりを進めたいです。

- プロフィールページを見れる、編集できる。
- 他のユーザーのプロフィールページを見れる
- 他のユーザーをフォローできる
- レビューにいいねできる


## 開発過程
あまり開発の時間が取れていないですが、毎日最低でも１コミットはして、少しずつでも前進するようにしています。
また、前回まで2回連続でFlutterでモバイルアプリを個人開発していたのですが、今回はWebアプリなのでなんだか画面が大きいしレスポンシブだしUIデザインが大変だなと少し感じています。
ただ、UIを考えるのは結構楽しくて好きで、アーキテクチャもいろいろ考えながら楽しく進められているので良い感じだと思います。

## 最後に
開発しているうちに、自分でもウイスキーを少しずつ集めて並べて晩酌したい気持ちになってきました。
とりあえず前好きだったI.W.Harperを買ってソーダ割りでいただきました。
おすすめのウイスキーを教えてください。

まだ未完成なので開発が進んだらor完成したらこの記事の続きを書こうと思います。

:::note info
このプロダクトは、株式会社mofmofの「水曜日の個人開発」にサポートされています。
:::

https://indie-dev.mof-mof.co.jp
[AtCoder] Beginners Selection - ABC049C - 白昼夢 [Elixir Livebook]

## はじめに

AtCoder の Beginners Selection コンテスト ABC049C - 白昼夢 問題を Elixir Livebook で回答しました

実装したノートブックはこちら

https://github.com/RyoWakabayashi/elixir-learning/tree/main/livebooks/atcoder/abc049/abc049_c

## 問題

https://atcoder.jp/contests/abs/tasks/arc065_a

## 入出力例

以下のようになれば OK です

```elixir
"""
erasedream
"""
|> Main.solve()
|> then(&(&1 == "YES"))
```

```elixir
"""
dreameraser
"""
|> Main.solve()
|> then(&(&1 == "YES"))
```

```elixir
"""
dreamerer
"""
|> Main.solve()
|> then(&(&1 == "NO"))
```

## 回答１

```elixir
defmodule Main do
  @words [
    "eraser",
    "erase",
    "dreamer",
    "dream"
  ]

  def main do
    :stdio
    |> IO.read(:all)
    |> solve()
    |> IO.puts()
  end

  def solve(input) do
    s = String.trim(input)

    Enum.reduce(@words, s, fn word, acc ->
      String.replace(acc, word, " ")
    end)
    |> String.trim()
    |> case do
      "" -> "YES"
      _ -> "NO"
    end
  end
end
```

順次消していって空文字になれば YES です

`dreameraser` (= `dream` + `eraser`) のとき、消す順番が悪いと `dreamer` + `aser` と捉えらて、判定を失敗します

以下の順序で消すことで、判定ミスが発生しません

- eraser
- erase
- dreamer
- dream

また、 各文字列を単純に空文字に置換すると `erdreamaser` のとき、 `erdreamaser` -- `dream` 置換 -> `eraser` -- `eraser` 置換 -> `` となってしまい、これも判定に失敗します

そこで、一旦全て半角スペースに置換しておき、最後にまとめてトリムします

これだと `erdreamaser` -- `dream` 削除 -> `er aser` となり、正しく判定できます

実行時間は 393 ms でした

## まとめ

この1つだけですが、他の人とは違うパターンができました

非常にシンプルで良いのではないでしょうか
AWSアカウントの作成とルートユーザーの初期設定

# はじめに
AWSを使い始めるにはAWSアカウントを作成する必要があります。
私が作成時に参考にした情報と、実施した方がよい初期設定をまとめました。
これからAWSアカウントを作成する方の参考になれば嬉しいです。

:::note warn
記事の中にAWSの画面が出てきますが、AWSは頻繁にUIを変更するため見た目が異なる場合があります。
:::

# AWSアカウントの作成
以下の公式サイトを参考にAWSアカウントを作成しました。

https://aws.amazon.com/jp/register-flow/

「AWSアカウント　作成」などでググって調べてみましたが、私には公式サイトが一番わかりやすかったです。

# AWSアカウントのルートユーザー
AWSアカウントを作成すると、作成時に使用したメールアドレスとパスワードでルートユーザーとしてサインインできます。
ですが、ルートユーザーの取り扱いには注意が必要です。

下記ページにも記載されていますが、AWSは「日常的なタスクにはルートユーザーを使用しないこと」をベストプラクティスとしています。

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_root-user.html

>強くお勧めしているのは、日常的なタスクには、それが管理者タスクであっても、ルートユーザーを使用しないことです。ベストプラクティスとして、ルートユーザーの認証情報を保護し、日常的なタスクには使用しないようにします。ルートユーザーの認証情報は、少数のアカウントおよびサービス管理タスクの実行にのみ使用します。

ベストプラクティスの詳細は下記ページに記載されています。

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/best-practices.html#lock-away-credentials

ポイントを以下にまとめました。
- ルートユーザーの認証情報にMFA（多要素認証）を設定する
- ルートユーザーのアクセスキー作成はお勧めではない。作成した場合、誰とも共有しない
- ルートユーザーを日常的なタスクに使用しない。日常的なタスクのために、IAMで管理ユーザーを作成する
- ルートユーザーはルートユーザーのみが実行できるタスクに使用する（「[ルートユーザー認証情報が必要なタスク](https://docs.aws.amazon.com/accounts/latest/reference/root-user-tasks.html)」を参照。英語です）

上記ポイントを踏まえ、次にルートユーザーの初期設定を行います。

# ルートユーザーの初期設定
### ルートユーザーのMFA（多要素認証）を設定する
右上のメニューから「セキュリティ認証情報」を選択します。

![スクリーンショット 2023-06-28 14.34.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3455793/4ba643ae-0a7a-b486-ce4b-0d16f0f3cb67.png)

セキュリティ認証情報画面の多要素認証（MFA）タブにある「MFAデバイスの割り当て」を押下し、画面の指示に従ってMFAデバイスを割り当てます。
![スクリーンショット 2023-06-28 14.10.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3455793/2732fe62-35db-523b-dced-4ffd4d03e044.png)

MFAデバイスの割り当て完了後の画面です。
![スクリーンショット 2023-06-28 15.00.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3455793/a442b149-a978-e61c-be6b-519b8107ca75.png)


### IAMユーザーによる請求情報へのアクセスを有効化する
日常的なタスクはIAMで作成する管理ユーザーで行うため、IAMユーザーによる請求情報（Billing）へのアクセスを有効化します。

右上のメニューから「アカウント」を選択します。
![スクリーンショット 2023-06-28 14.16.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3455793/8ef7e53e-1b84-8c8d-b426-79ad0a594319.png)

アカウント画面のIAMユーザーおよびロールによる請求情報へのアクセスタブが「有効化済み」になっていない場合、有効化します。
![スクリーンショット 2023-06-28 14.17.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3455793/1715ee5d-00c0-441f-f12d-6c0cca76354e.png)

# まとめ
AWSアカウントの作成とルートユーザーの初期設定は以上となります。
ルートユーザーは何でもできてしまう（AWSの解約も！）ユーザーなので取り扱いには十分注意しましょう。

次に、IAMで管理ユーザーを作成していきます。

https://qiita.com/Taka898/items/91e82a7bb5a9a49d35b2
Storybookの閲覧画面で controls (アドオンパネル) が表示されない時の対処方法

Storybookを使っていると、何かの拍子に画面下部の controls (アドオンパネル)が消えてしまうことがある。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134740/399f4667-680f-cc8c-d999-795aa86d8b6f.png)

そんな時は、 キーボードで `D + A` を押して再表示できる。

公式のわかりやすい所に書いておいて欲しい。

https://stackoverflow.com/questions/49468813/panels-not-visible-in-react-storybook
VS Code 拡張機能の「Less TOML」をリリースしました


TOML の VS Code拡張機能といえば、172万ダウンロードの [Better TOML](https://marketplace.visualstudio.com/items?itemName=bungcip.better-toml) を使っている人が多いと思いますが、
Better TOML では、TOML ファイルのシンタックスハイライトの他に、
Markdown ファイルの TOML 形式のフロントマターのシンタックスハイライトはできるのですが、
Markdown ファイルの TOML 形式の [コードブロック](https://docs.github.com/ja/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks) のシンタックスハイライトはできません。

機能拡張の PR を送ろうと思ったのですが、[Better TOML](https://github.com/bungcip/better-toml) はすでに、2022年4月に開発を終了し、Public Archive 状態になっているため、Better TOML を Fork して、Language Server で実装された Code Validation 機能を削除して、Markdown ファイルの TOML 形式の [コードブロック](https://docs.github.com/ja/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks) のシンタックスハイライトを追加した、[Less TOML](https://marketplace.visualstudio.com/items?itemName=juggernautjp.less-toml) をリリースしました。

- [Less TOML - Microsoft Market Place](https://marketplace.visualstudio.com/items?itemName=juggernautjp.less-toml)
- [Less TOML - GitHUb](https://github.com/juggernautjp/less-toml)


[Better TOML](https://marketplace.visualstudio.com/items?itemName=bungcip.better-toml) をインストールしている人は、VS Code から Better TOML をアンインストールして、[Less TOML](https://marketplace.visualstudio.com/items?itemName=juggernautjp.less-toml) をインストールしてください。
Tarraformを使ってAWSにWebサーバを構築 #1 ~TerraformからAWSリソースを操作できるように設定する~

## 目的
EC2にNginxがインストールされた状態を作り出し、ブラウザからデフォルトページが確認できることをゴールとする。
インフラ構築はTerraformでコード化することで再現性、再利用性、速度などが手作業に比べると高い。
[GitHubリンク](https://github.com/keitasato/terraform-nginx)

### 手順
1. [TerraformからAWSリソースを操作できるように設定する](https://qiita.com/kekkepy/items/4185a621e5d3cc75016b)
1. [Terraformをインストールする](https://qiita.com/kekkepy/items/1b3f4f14bbc2d2dc2c9b)
1. [TerraformでEC2サーバを立ち上げてみる](https://qiita.com/kekkepy/items/8b84fc0c39313267a078)
1. [ネットワークを整える(VPC, サブネット, ルーティングテーブル,インターネットゲートウェイ)](https://qiita.com/kekkepy/items/c941c00536af57bae163)
1. [セキュリティグループ(ファイアウォール)を設定する](https://qiita.com/kekkepy/items/47dcda4d9a7a2064382f)
1. [Webサーバ用EC2を立ち上げる](https://qiita.com/kekkepy/items/81bfa5d542c9160d430b)
1. [Webサーバが立ち上がる際にWebサーバソフトをインストールするように設定する](https://qiita.com/kekkepy/items/209f7ddb29fb5af65a6e)

#### 今回の到達点
TerraformからAWSリソースを操作できるように設定する

##### Step0. AWS CLIのインストール
[AWS CLI インストール](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/getting-started-install.html)
TerraformではコマンドからAWSリソースに対して処理を行うが、AWS CLIを介する必要がある。
バージョンは2で特に問題ない。
インストールできたかの確認はコマンドプロンプトやターミナルで以下を実行。結果としてバージョンが表示されれば無事インストール完了
```cmd
$ aws --version
aws-cli/2.11.18 Python/3.11.3 Windows/10 exe/AMD64 prompt/off
```


##### Step1. AWSにてデフォルトのIAMユーザのアクセスキーを作成
ログインなどに使用しているデフォルトユーザのアクセスキーを作成する。

AWSコンソールにアクセス
IAM > ユーザー > デフォルトユーザ > アクセスキーを作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1221536/3fdea4ea-7e59-ae0b-ba71-85857daa32d8.png)

作成したアクセスキーIDとシークレットアクセスキーを控えておく(csvもダウンロードしておくと忘れたときに便利)。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1221536/376e27c6-38fc-42ac-f8a9-9b30d7c546b3.png)

##### Step2. AWSにてTerraform用IAMユーザの作成
TerraformからAWSに操作ができるようなIAMユーザを作成する。
AWSを操作することが可能なのは、AWSリソースかIAMユーザ(ルートユーザも可能だが非推奨)。
そのためTerraform用のIAMユーザを作成する。

AWSコンソールにアクセス
IAM > ユーザー > ユーザーを追加
ユーザ名 : terraform (これ以外でも自分が識別できれば任意のユーザ名で問題ない)
ポリシー : AdministratorAccess
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1221536/c0bb06dc-e62e-37b0-6c9a-2e409a393908.png)


##### Step3. Terraform用IAMユーザのアクセスキーを作成(Step1を参照)
Terraform用IAMユーザのアクセスキーを作成する。
このアクセスキーをTerraform側で設定することで、TerraformでのAWS操作を可能とする。

AWSコンソールにアクセス
IAM > ユーザー > 2で作成したユーザ > アクセスキーを作成

作成したアクセスキーIDとシークレットアクセスキーを控えておく(csvもダウンロードしておくと忘れたときに便利)。

##### Step4. デフォルトユーザのアクセスキーをAWS CLIに設定
```cmd
$ aws configure
AWS Access Key ID [None]: [ここにデフォルトユーザのアクセスキーを入力]
AWS Secret Access Key [None]: [ここにデフォルトユーザのシークレットキーを入力]
Default region name[None]: ap-northeast-1
Default output format [None]: json
```

##### Step.5 Terraform用IAMユーザのアクセスキーをAWS CLIに設定
```cmd
$ aws configure --profile terraform
AWS Access Key ID [None]: [ここにTerraform用IAMユーザのアクセスキーを入力]
AWS Secret Access Key [None]: [ここにTerraform用IAMユーザのシークレットキーを入力]
Default region name[None]: ap-northeast-1
Default output format [None]: json
```

##### Step.6
以下のファイルにアクセスキーの設定が反映されているかを確認
~/.aws/credentials
```
[default]
aws_access_key_id = [デフォルトユーザのアクセスキー]
aws_secret_access_key = [デフォルトユーザのシークレットキー]
[terraform]
aws_access_key_id = [Terraform用IAMユーザのアクセスキー]
aws_secret_access_key = [Terraform用IAMユーザのシークレットキー]
```
IBM Cloud上でのVMware vSphereオファリングの導入 - vCenterインストール

# はじめに
前回は、IBM Cloud上でのVMware vSphereオファリングのオーダーおよびvCenterインストールのためのジャンプ・サーバーの作成について記述しました。今回はVMware vSphereオファリングのオーダーにより作成されたベアメタル・サーバーへvCenterをインストールしていきます。

https://qiita.com/cxl2022my/items/5f4821c561adf6b1957b

# vCenterのインストール
1. ESXiの構成確認
IBM Cloudに作成したベアメタル・サーバー上のESXi Host Clientにブラウザーでアクセスし、ログインします。
左側のナビゲータよりネットワークを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3063877/f3479703-d882-53d5-00f0-0953cdca479e.png)
ポートグループとして、ESXi自身用のポートグループ(Management Network)とESXi上に作成されるVM用のポートグループ(VM Network)があります。ただし、IBM CloudのVMware vSphereオファリングで作成されるESXiには、Management Networkしか存在しないケースがありますので、その場合は手動でVM Networkを作成します。これは作成されたすべてのESXiで確認し、VM Networkがなければ作成します。

2. ジャンプ・サーバーでVMware vCenter Server Applianceインストーラー実行
Remote Desktop アプリにて、ジャンプ・サーバーとして作成したWindows Serverにアクセスします。
File ExplorerからVMware vCenter Server Applianceのインストーラーを実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3063877/304e2fa9-c290-2e47-5954-6ec23475aba2.png)
インストーラーが起動しますので、`Install`をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3063877/fe48abc6-e61d-5e4d-f0b8-7a32d61ca6d8.png)
`Next`ボタンをクリックし、`vCenter Server deployment target`の画面まで遷移させます。
vCenterを導入するESXiのホスト情報を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3063877/d7d0553f-a363-c633-eb44-f02458cc79a6.png)
指定したESXi上にvCenter用のVMが作成されます。
画面の指示に従って、必要な情報を入力していきます。
`Configure network settings`の画面で、最初に確認したESXi上のVM用のポートグループを指定します。Management Networkポートグループのみの場合は、選択肢が空ですのでご注意ください。この画面では、vCenter用のVMのネットワーク設定を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3063877/834ce3d1-9a2b-4a83-1368-8fdd2e9fcce6.png)
次の画面で最終確認を行い、`Next`ボタンをクリックして、vCenterのインストールが開始されます。

3. vCenterへ管理対象のESXiの登録
作成されたvCenterのVMにブラウザーでアクセスし、ログインします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3063877/00dd655a-e800-d39b-1ec1-cc7560ed211d.png)
まず、Datacenterを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3063877/4f724d68-a3aa-0204-4537-5e659e79c995.png)
次に、作成したDatacenterにESXiホストを追加します。ESXiを追加する前にClusterを作成し、そのClusterにESXiホストを追加していくこともできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3063877/39040ace-c3e1-4817-6302-0744d9164d33.png)

クラスタについては以下のドキュメントをご覧ください。

https://docs.vmware.com/jp/VMware-vSphere/7.0/com.vmware.vsphere.vcenterhost.doc/GUID-F7818000-26E3-4E2A-93D2-FCDCE7114508.html

以下は、Clusterを作成し、その中に3台のESXiホストを追加した画面になります。`VMware vCenter Server`は、vCenter用のVMになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3063877/f0400afa-a4be-249e-1732-4a7e56b659bf.png)


# 終わりに
今回は、ジャンプ・サーバーかvCenter Server Applianceをインストールし、インストールしたvCenterにアクセスし、ESXiホストを追加するところを説明しました。IBM Cloud上に作成されたESXi上にVM Networkポートグループが存在しない場合、手戻りが発生するので、事前にESXi上で確認する必要があるところが当記事の一番のポイントとなります。
RustでChatGPTとチャットできるCLIツールを作ってみている

[株式会社mofmof](https://www.mof-mof.co.jp/)のkoba_hiro_です。
Rustの勉強も兼ねて、CLIでGPTとチャットできるツールを作っているので、途中経過を書いてみようかと思います！

# 経緯
mofmofでは自由参加の社内技術部活動のようなものがあり、現在はRust入門をテーマに活動しています。
入門したついでに何かつくってみたいなーということで、個人開発でCLIツールを作り始めました！

なぜGPTとチャットするツールなのかというと、
OpenAIのAPIを気軽に試せる方法が欲しいな〜curlは面倒だしな〜
APIkeyさえ用意すれば気軽にAPI試せるツールを・・・という感じです。

# 過程
とりあえずは、[Rust公式](https://www.rust-lang.org/ja/learn)が用意してくれているTHE BOOKというチュートリアルを読んでみました。
簡単なCLIプログラムを書いたり、webサーバを構築してみたり、かなり充実していて勉強になりました。

その後、THE BOOKの内容を参考にしながら実際に作り始め、わからない部分はGPT先生に相談し・・・
SystemRoleを設定した状態でチャットができるところまでは完成しました！


# 使ったもの
- [Rust](https://www.rust-lang.org/ja/learn)
    - [rustup](https://www.rust-lang.org/ja/tools/install)を使ってインストールしました(Rustのインストーラ・管理ツール
- [Cargo](https://doc.rust-lang.org/cargo/)
    - Rustのビルドツール兼パッケージマネージャー
    - rustup使うと自動的にインストールされる
- [clap](https://docs.rs/clap/latest/clap/)
    - コマンドライン引数をパースするためのクレート
    - 引数を構造体で定義するとよしなにやってくれる
    - コマンドのヘルプ表示も自動で用意してくれたりする
- [Serde/serde_json](https://serde.rs/)
    - データをシリアライズ/デシリアライズしてくれるフレームワーク
    - 今回は構造体↔jsonの変換のために使った
- [async-openai](https://github.com/64bit/async-openai)
    - さくっとOpenAIのAPIを叩いてくれるライブラリ
    - 使用例も豊富で親切

# できたもの
![スクリーンショット 2023-06-28 12.40.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3081679/735f3695-d6db-16e2-dc8e-314cc92305f5.png)
(最近sf6にはまってます)

# 今後やりたいこと
とりあえず動く所まで作ってみたレベルなので、もっとしっかりしたツールにして公開したいです。
OpenAIには画像生成だったり、音声認識のAPIもあるっぽいのでそこらへん組み込んで何か面白いもの作れないかなーとも考えています。
あとは、[Tauri](https://tauri.app/)を使ってReact✕Rustでデスクトップアプリ化してみるとかもやりたいですね！

# 感想
Rust触ってみるまではとっつきづらそうだし難しそうだなーと思っていました。
実際、所有権やライフタイムの概念、独特な記法等、戸惑うことも多いのですが、
コンパイラが優秀でエラーが起きた際に何が悪いのかわかりやすく、トライ&エラーしながら学ぶことができました。

まだまだ触り始めたばかりですが触っていて楽しい言語だと感じているので、
今後も使い続けてRustaceanを名乗れるようになりたいです。

:::note info
このプロダクトは株式会社mofmofの「水曜日の個人開発」にサポートされています。
:::

https://indie-dev.mof-mof.co.jp/
Azure SQL Database認証にAzure Active Directoryグループを設定する

# 概要
Azure SQL Databaseの認証にAzure Active Directorグループを設定、ロール付与、DML権限付与・剥奪の手順を紹介します。



# Azure Active DirectoryのAADグループでAzure SQL Databaseのユーザを作成
以下のコマンドを実行すると、Azure Active DirectoryのAADグループでAzure SQL Databaseのユーザを作成する事が出来ます。ポイントは[FROM EXTERNAL PROVIDER]です。

```sql
/** 各データベースで実行 **/
CREATE USER '<AADグループ名>' FROM EXTERNAL PROVIDER;
```

# ロールの付与
作成したユーザーにAzure SQL Databaseのロールを割り当てます。権限は目的に合わせて付与して下さい。

```sql
/** 各データベースで実行 **/
ALTER ROLE db_datareader ADD MEMBER '<AADグループ名>';
ALTER ROLE db_datawriter ADD MEMBER '<AADグループ名>';
```

:::note info
上記の例では対象のAADグループに「db_datareader」、「db_datawriter」の権限を付与しています。
:::

# テーブル毎にDML権限付与

```sql
/* SELECT権限付与 */
GRANT SELECT ON OBJECT::<スキーマ名>.<テーブル名> TO '<AADグループ名>';

/* INSERT権限付与 */
GRANT INSERT ON OBJECT::<スキーマ名>.<テーブル名> TO '<AADグループ名>';

/* UPDATE権限付与 */
GRANT UPDATE ON OBJECT::<スキーマ名>.<テーブル名> TO '<AADグループ名>';

/* DELETE権限付与 */
GRANT DELETE ON OBJECT::<スキーマ名>.<テーブル名> TO '<AADグループ名>';
```

:::note info
上記の例では対象のテーブルのAADグループに「SELECT」、「UPDATE」、「INSERT」、「DELETE」の実行権限を付与しています。
:::

# テーブル毎にDML権限剥奪

```sql
/* SELECT権限剥奪 */
REVOKE SELECT ON OBJECT::<スキーマ名>.<テーブル名> FROM '<AADグループ名>';

/* INSERT権限剥奪 */
REVOKE INSERT ON OBJECT::<スキーマ名>.<テーブル名> FROM '<AADグループ名>';

/* UPDATE権限剥奪 */
REVOKE UPDATE ON OBJECT::<スキーマ名>.<テーブル名> FROM '<AADグループ名>';

/* DELETE権限剥奪 */
REVOKE DELETE ON OBJECT::<スキーマ名>.<テーブル名> FROM '<AADグループ名>';
```

# 付与した権限の確認方法

```sql
/* 確認 */
SELECT dp.major_id, dp.permission_name, dp.state_desc, dp.grantee_principal_id, pr.name _
FROM sys.database_permissions AS dp CROSS JOIN sys.database_principals AS pr _
WHERE pr.principal_id = dp.grantee_principal_id;
	  
SELECT rm.role_principal_id, dp.name AS role_name, dp.type_desc, rm.member_principal_id, _
(SELECT dp.name from sys.database_principals dp WHERE dp.principal_id = rm.member_principal_id) AS member_name _
FROM sys.database_principals dp CROSS JOIN sys.database_role_members rm _
WHERE dp.principal_id = rm.role_principal_id;
```

# 参考
* [Azure SQL での Azure AD 認証を構成して管理する](https://learn.microsoft.com/ja-jp/azure/azure-sql/database/authentication-aad-configure?view=azuresql&tabs=azure-powershell) 
* [GRANT (オブジェクトの権限の許可) (Transact-SQL)](https://learn.microsoft.com/ja-jp/previous-versions/sql/sql-server-2008/ms188371%28v=sql.100%29?redirectedfrom=MSDN)
Python3 エンジニア認定基礎試験 受験記

# はじめに
Python3エンジニア認定基礎試験を受験したので、その受験記をまとめました。

# Python3エンジニア認定基礎試験とは
Pythonの基礎的な文法などの知識が問われる試験です。
Pythonの試験には、「Python3 エンジニア認定基礎試験」や「Python 3 エンジニア認定データ分析試験」などいくつかありますが、その中でも易しめの難易度の試験です。

## 試験概要
* 受験日：通年
* 試験センター：全国のオデッセイコミュニケーションズCBTテストセンター
* 申込URL：http://cbt.odyssey-com.co.jp/pythonic-exam.html
* 受験料金：1万円（税別）
* 試験名：Python3エンジニア認定基礎試験（英名：Python 3 Certified Engineer Basic Examination）
* 概要：文法基礎を問う試験
* 問題数：40問（すべて選択問題）
* 試験時間：60分
* 合格ライン：正答率70％
* 出題範囲：主教材であるオライリー・ジャパン「Pythonチュートリアル（第四版）」の掲載内容を出題範囲として、次の比率で出題（一般的な知識からも出題される）

### 出題範囲
| 章 | 出題数 | 出題率 |
|:--|--:|--:|
| 1章　食欲をそそってみようか | 1 | 2.5% |
| 2章　Pythonインタープリタの使い方 | 1 | 2.5% |
| 3章　気楽な入門編 | 6 | 15.0% |
| 4章　制御構造ツール | 9 | 22.5% |
| 5章　データ構造 | 7 | 17.5% |
| 6章　モジュール | 2 | 5.0% |
| 7章　入出力 | 1 | 2.5% |
| 8章　エラーと例外 | 4 | 10.0% |
| 9章　クラス | 2 | 5.0% |
| 10章　標準ライブラリめぐり | 4 | 10.0% |
| 11章　標準ライブラリめぐり─PartII | 1 | 2.5% |
| 12章　仮想環境とパッケージ | 1 | 2.5% |
| 13章　次はなに？ | 0 | 0.0% |
| 14章　対話環境での入力行編集とヒストリ置換 | 1 | 2.5% |
| 合計 | 40 | 100.0% |

出典：[基礎試験 | Python試験・資格、データ分析試験・資格を運営する一般社団法人Pythonエンジニア育成推進協会のページです。](https://www.pythonic-exam.com/exam/basic)


# 学習方法について
自分が実際に行った学習方法について記載します。

参考までに、受験前の自分のスペックはざっくり次のような状態でした。

* 数年前にJavaの本をひととおり読んで、プログラミングの基本構文やオブジェクト指向の概念などはなんとなく理解している
* 実務ではSQLを書くことが多く、プログラミング言語をゴリゴリコーディングすることはあまりない
* Pythonは実務で使用したことはないが、趣味では触ったことがある

## 勉強時間
* 10日間、毎日1～3時間程度

## 使用教材
* 教材
	* [Python チュートリアル — Python 3.11.4 ドキュメント](https://docs.python.org/ja/3/tutorial/index.html)
		* 試験の出題範囲はPythonチュートリアルの目次に沿っているため、これを読めば試験範囲は網羅できます
		* が、機械翻訳ぽい文章でかなり分かりづらいです
		* プログラミングをある程度知っていいる前提で書かれているため、プログラミング初心者の方は少しつらいかも（自分はちょっとつらかったです）

* 模擬試験
	* [ディープロ | プログラミングとIT資格試験の勉強ができる ディープロ](https://diver.diveintocode.jp/dive_into_exam)
		* 問題は毎回ランダムでかわり、何回でも受験できます
		* 難易度は易しめです

	* [PRIME STUDY（プライム・スタディ） – Python試験とPHP試験の無料模擬試験サイト](https://study.prime-strategy.co.jp/)
		* 問題は固定ですが、3回分用意されています
		* 難易度はディープロと比べると高めです

## 勉強の進め方
ざっくりとつぎのような順番で勉強を進めました。
1. 模擬試験を受験して雰囲気をつかむ
1. Pythonチュートリアルを読む
1. 模擬試験を受験し、間違えたところを復習する
1. 2～3を繰り返す

### 1. 模擬試験を受験して雰囲気をつかむ
まずは試験の問題文などの雰囲気をつかみたかったので、ほぼ勉強していない状態で一度模擬試験を受験しました。
模擬試験は簡単な方のディープロを受験しました。このときの正解率は37.5%でした。

### 2. Pythonチュートリアルを読む
Pythonチュートリアルを理解するのが一番早そうだったので、模擬試験のあとはチュートリアルを読みました。
文章がかなりわかりにくいので、文章はあまり深く考えずさっくり読んで、コードを実際にためして動きを理解していきました。

### 3. 模擬試験を受験し、間違えたところを復習する
Pytyonチュートリアルを3分の1くらいすすめた時点で再度ディープロの模擬試験を受験しました。
正解率は60%で、チュートリアルの範囲がそのまま素直に出題されるような感触でした。
間違えた問題を、チュートリアルを読み返したりネットで検索して理解しました。

### 4. 2～3を繰り返す
あとはチュートリアルを進め、飽きたら模擬試験を受ける、という流れで進めていきました。
この時点で試験日まであまり時間がなかったため、「データ構造」や「エラーと例外」などの出題率が高いところを中心にチュートリアルを読むようにしました。

ディープロの模擬試験である程度点数がとれるようになってから、PRIME STUDYの模擬試験も受験しました。

最終的にはディープロの模擬試験の正解率は90%、PRIME STUDYの模擬試験の正解率は70%という状態で試験に挑み、80%で合格できました。

# 試験時に気を付けること
Python試験はCBT方式というコンピューターを利用して実施される試験です。
最近はCBT方式の試験が増えてきましたが、慣れていない人は注意が必要です。
Pythonの試験では、メモ用意は配布されませんでした。

また、受験にはオデッセイ コミュニケーションズのアカウントが必要です。
試験会場の端末でアカウントにログインする必要があるため、登録したOdyssey IDとパスワードは忘れないようにしましょう。
jqで対応するキーがない場合にnullではなく空文字を返す

## Alternative operator `//` を使う
Perlで言うところの`Defined-or`演算子
とっても検索しづらい

これと何も返さない`empty`を組み合わせる
もちろん適当なデフォルト値を設定することもできる

## サンプル
```json
{"hoge": 1}
```

```bash
$ jq .foo // empty
# 何も返ってこない
```

## 参考
* [jq Manual - Alternative operator](https://jqlang.github.io/jq/manual/#:~:text=Alternative%20operator%3A%20//)
* [jq Manual - empty](https://jqlang.github.io/jq/manual/#:~:text=Example-,empty,-empty%20returns%20no)
WSL2からWindows（ホスト）のIPアドレスを取る方法3選

X環境を構築しようとしていてWindows側のIPアドレスを取ろうと参考サイトを見ていました。
少しうまくいかなかったこともあったのでメモを残しておきます。
ちなみに、もともと [Puppeteer](https://pptr.dev/) のデバッグ環境をつくりたくてIPアドレスを設定しようとしていました。

## /etc/resolv.conf から取得する方法

```bash
cat /etc/resolv.conf | grep nameserver | awk '{print $2}'
```

こちらのサイトを参考にさせてもらいました。

https://gunmagisgeek.com/blog/puppeteer/6924

こちらの方法は、 `/etc/resolv.conf` が自動作成される場合に使えます。（デフォルト設定だと自動作成されるはず。）
**`/etc/resolv.conf` を手動設定していて、nameserver 1.1.1.1 などに設定している場合には使えません。**（私はWSL2の通信が遅いので、この対策をやっていました。参考にさせてもらったサイトの記事がなくなっていました…。ググれば似たような記事がでます。）

## ipconfig.exe から取得する方法

```bash
/mnt/c/Windows/system32/ipconfig.exe | iconv -f CP932 -t UTF-8 | sed -e 's/\r//' | grep 'IPv4' | tail -n 1 | cut -d ':' -f 2 | awk '{print $1}'
```
`/etc/resolv.conf` を手動設定している場合の対応方法を調べていたところ下記で ipconfig から取得する方法が書かれていました。

https://zenn.dev/d_hori/articles/128f8976a48b52

参考にさせてもらったコマンドのままだと、自分の環境だと2点注意することがありました。

1. ipconfig.exe コマンドがWSL2から実行できない
  これは私のWSL2の設定でWindows側のPATHを引き継がれないようにする設定をしていたためです。なので `/mnt/c/` から始まるフルパスを指定して ipconfig.exe を実行するようにしました。
1. ipconfig.exe の結果をパイプに渡すと文字化けして、うまく grep できない
  参考にしたサイトにも同様の内容がありましたが、結局どうすればいいのか分からなかったので ChatGPT に確認したところあっけなく解決できました。ありがとう。ChatGPT。もっとうまいやり方あるかもしれませんが、とりあえずの動作確認ならこれで十分でした。
![WSL2 Sed 文字化け解消 - Google Chrome 2023-06-26 09.05.5 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960076/a476b19d-4fd2-effa-2110-5336c56f14da.png)

## ip route から取得する方法

```bash
ip route | grep 'default via' | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'
```

これはこちら↓の内容のままでいけました。

https://qiita.com/samunohito/items/019c1432161a950892be


ということで自分の環境でやる場合だと、ipconfig.exe から取得する方法か、ip route から取得する方法の2択になりました。私は ip route から取得するようにしました。
python maya filePathEditor get all reference file

```python
def _get_scene_references():
    """
    List all the references in the current scene
    Return the reference node/attribute and the file path
    """
    cmds.filePathEditor(rf=True)
    referenced_files: List[Tuple[str, pathlib.Path]] = []

    # Get the referenced files from the file path editor
    for attribute in cmds.filePathEditor(q=True, lf="", ao=True) or []:
        try:
            # Skip the nodes that are from an other referenced scene
            if cmds.referenceQuery(attribute, isNodeReferenced=True):
                continue
        except RuntimeError:
            continue

        # If the attribute is a maya/alembic/... reference
        if cmds.nodeType(attribute) == "reference":
            file_path = cmds.referenceQuery(
                attribute, filename=True, withoutCopyNumber=True
            )
            referenced_files.append((attribute, pathlib.Path(file_path)))
            continue

        # Otherwise, just get the attribute for simple stuff like file nodes
        file_path = cmds.getAttr(attribute)

        # Skip references that have an empty file_path
        if len(file_path) == 0:
            continue

        referenced_files.append((attribute, file_path))

    # Make sure to not have duplicates in the references
    return list(set(referenced_files))
        
referenceFiles = _get_scene_references()
print(referenceFiles)
```
TSUBAMEに投げたジョブを監視してSlack通知

# ジョブの開始と終了をちまちま確認しにいくの嫌ですよね

ほとんど[この記事](https://qiita.com/toshid1997/items/7c109baace41e82e30c7)通りです．

## やりたいこと
`hoge.sh`をTSUBAMEに投げる時，ジョブ待ちから実行になった時＆ジョブ終了時にSlack通知が来て欲しい


## 実現方法
`hoge.sh`内の実行コマンド部分に`slacknotice`という自作コマンドを噛ませる
```hoge.sh
slacknotice python3 main.py
```

## 注意点
- pythonを利用するので**pythonのインストールされている環境でのみ動きます**．

- 「ジョブ待ちから実行になった時＆ジョブ終了時」とはいったものの**実際に通知が来るのがコマンド実行時**なので，それより前の部分でエラーが発生してジョブが終了してたり，TSUBAMEの実行時間制限に引っかかって強制終了した場合，**通知は来ません**．

## 実現手順

### Slackに通知用チャンネルをつくり， Incoming Webhookを連携
通知を送りたいチャンネルを決めておく．
[Incoming Webhook](https://slack.com/apps/A0F7XDUAZ-incoming-webhooks)を連携させる．この時生成される`https://hooks.slack.com/services/`から始まるWebhookURLをコピーしておく．

### slacknoticeコマンドを作成
以下のような`slacknotice`ファイルを作り，好きな位置に保存しておく．元記事とほぼ一緒です．
（元記事にならって自身のディレクトリ配下にscriptsディレクトリを作ってそこに作成しておけばいいと思います．以下では`~/scripts/slacknotice`というふうに作成したとします）

```py
#!/usr/bin/env python　　　　　<-これは必須のやつ

# ここを取得したWebhook URLに変更
webhook_url = "https://hooks.slack.com/services/hoge/fuga"

import json, os, requests, sys
from subprocess import run

# 通知を送る関数を定義
def notice(msg):
    post_data = json.dumps({"text": msg})
    requests.post(webhook_url, data=post_data, headers={"Content-Type": "application/json"})


# 引数から実行するコマンドを取得
commands = sys.argv[1:]

# 実行前通知
notice(f"実行開始！:  {commands}")

# コマンド実行、ユーザーのカレントディレクトリ（os.getcwd()）で実行するようにしている。
run(commands, cwd="{}/".format(os.getcwd()))

# 実行後通知
notice(f"実行終了！: {commands}")
~                                    
```

もし実行するpyhton環境にrequestsモジュールをインストールしていなかった場合`import json, os, requests, sys`の部分で詰まるので，利用している環境に合わせてpipかcondaでインストールしておこう．（食べごろうは以下のスクリプトでもわかるようにconda環境を利用しているので事前に以下の操作をしておいた）
```shell
conda activate my_env
conda install requests
```

仕上げに一応権限を与えておく
```
chmod u+x ~/scripts/slacknotice
```

### 実際にジョブを投げる時
今まで投げていたジョブに対し`export PATH="/hoge/fuga/scripts/:$PATH"`の１行を足し，コマンド実行部の頭にも`slacknotice`を足す（`slacknotice`コマンドに今までのコマンドを引数として渡して，中であらためて実行するイメージ）

これを踏まえたスクリプトはこんな感じ

```hoge.sh
#!/bin/bash
#$ -cwd
#$ -N Test_job
#$ -l q_node=1
#$ -l h_rt=0:10:0
#$ -V


. /etc/profile.d/modules.sh
module load cuda/10.2.89 cudnn/8.1

source  ~/.bashrc
conda activate my_env
export PYTHONPATH="/hoge/fuga/:$PYTHONPATH"
export PATH="~/scripts/:$PATH"  ##この１行を追加

slacknotice python3 -u main.py
## ↑ここを追加
```

ちなみに本題とは関係ないですが，pythonコマンド実行時に`-u`オプションをつけると，時間制限で実行が正常に終了しなかったときも終了直前までのログを出してくれるようになって便利だということを今知りました．
その代わり，普段は高速化するためにバッファに貯めて出力してくれてたのを強制的に吐き出させるようにしてるので，やや遅くなるらしい．
invalid command 'egg_info' on AWS Amplify Console CI/CD

私と同じように、Amplify Consoleの継続デプロイで突然発生したPythonエラーに困っているエンジニアがいると思います。そのため、その解決方法についてメモします。

結論から言いますと、もし2023/06/25日まで以下のビルド設定でうまくデプロイしていたのに、6月26日から「invalid command 'egg_info'」のエラーが出た場合、その原因はpipenvのバージョンにあります。

```
version: 1
backend:
  phases:
    preBuild:
        commands:
            - export BASE_PATH=$(pwd)
            - yum install -y gcc openssl-devel bzip2-devel libffi-devel python3.8-pip
            - cd /opt && wget https://www.python.org/ftp/python/3.8.2/Python-3.8.2.tgz
            - cd /opt && tar xzf Python-3.8.2.tgz 
            - cd /opt/Python-3.8.2 && ./configure --enable-optimizations
            - cd /opt/Python-3.8.2 && make altinstall
            - pip3.8 install --user pipenv
            - ln -fs /usr/local/bin/python3.8 /usr/bin/python3
            - ln -fs /usr/local/bin/pip3.8 /usr/bin/pip3
            - cd $BASE_PATH

```
解決方法：pipenvをインストールする際は、バージョンを指定します。
```
- pip3.8 install --user pipenv==2023.2.18
```

問題を調べるため、最初に疑問に思ったのは以下のログでした：
```
added seed packages: pip==23.1.2, setuptools==67.8.0, wheel==0.40.0
```
ローカル環境と一致しているため、pipとsetuptoolsのバージョンが問題である可能性は低そうです。
次に考慮に入れたのはpipenvのバージョンです。
preBuild phaseに`pipenv --version`を追加してみると、以下のログが出力されました：
```
 # Executing command: pipenv --version
pipenv, version 2023.6.26
```
ローカルのpipenvのバージョンは「2023.2.18」です。
これが原因かもしれないと考え、pipenvのバージョンを「2023.2.18」に指定してみたところ、deployがついに成功しました！
python でクラス名を文字列で指定してインスタンスを作成する

## やりたいこと
* クラス名を文字列で指定して、指定したクラスのインスタンスを取得する

## やったこと
get_attr を使います。
```Python3:Python3
# Python 3.9.13
# getattr_sample.py
import sys

class Hoge():
 
    def __init__(self, output: str):
        print(f"{output}")


cls = getattr(sys.modules[__name__], 'Hoge')
cls_a = cls('This is Hoge(a)')


```
実行結果
```
$ python getattr_sample.py 
This is Hoge(a)
```
## 参考
- Python ドキュメント
    - https://docs.python.org/3.9/library/functions.html#getattr
-  https://stackoverflow.com/questions/4821104/dynamic-instantiation-from-string-name-of-a-class-in-dynamically-imported-module
【小ネタ】PowerAppsでChatGPTがオススメするアプリを作ってみた

PowerAppsでアプリが簡単に作れるよと説明されても具体的にどんなもの作ればいいんだろうとよく考えていたので、
今回はChatGPTに実用的な具体案を出してもらって、なるほど確かによさそうだと思ったタスク管理アプリを作ってみます。
![001.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2480832/3698ad1f-bbac-1c88-f798-e9ac8c4f0773.jpeg)

## データの作成
まずデータを格納する場所としてSharePointのリストを作ります。
![002.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2480832/80c4d6f0-b09f-4d8d-319a-cd6569688621.jpeg)

## アプリの作成
以下画像の赤枠部分　統合>Power Apps>アプリの作成　からアプリを作成します。　
![003.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2480832/f1dbaf32-5a99-b1b8-f3fd-b39563240868.jpeg)
![005.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2480832/3d5fa673-9562-b8de-d333-3c5c06a95a64.jpeg)

## アプリの編集
### ギャラリーの表示内容を変更
メールアドレス表示になっているので名前表示にします。
Subtitle1のTextを以下の通り変更することで名前表示になります。
``ThisItem.'名前 (OData__x540d__x524d_)'.Claim``→
``ThisItem.'名前 (OData__x540d__x524d_)'.DisplayName``
![006.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2480832/0ad05420-e6fb-ae8e-2c14-f1101faa4215.jpeg)

Body1のTextを以下の通り変更することで内容ではなく期限の表示になります。
``ThisItem.内容``→``ThisItem.期限``
![007.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2480832/532091fb-58d0-53f2-31e7-0c55add27950.jpeg)

### ギャラリーのセパレート線を見やすくする
Separator1のHeightを``1``にして、罫線の太さも``1``にしてセパレート線を見やすくします。
![008.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2480832/56e0b2fb-b5cb-e4e9-fa6b-980a1a4083b3.jpeg)

### 初期の灰色字を変更する
続いて編集画面の編集を行います。
まずは不要な添付ファイル項目を削除して、内容項目を大きくします。
![009.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2480832/6e04c02f-0007-8f63-6e58-e1a5dca880e1.jpeg)

灰色字はInputTextPlaceholderを変更します。
``If(IsBlank(Self.SelectedDate), Text(Date(2001,12,31), Self.Format, Self.Language))``→
``If(IsBlank(Self.SelectedDate), Text(Today(), Self.Format, Self.Language))``
![010.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2480832/39001e36-4940-1477-3d65-c74c60c6ef6e.jpeg)

### ソート機能と検索機能を追加する
以前書いた記事で紹介しているので、そちらを参考にしてください。

https://qiita.com/ssc-ksakamaki/items/3c74bc12d5a45c544bcf

### 追加機能
追加機能について、進捗状況や関連タスク機能などありますが自分で思いつく機能には限界があるため、
追加機能についてもChatGPTに聞くことでよさそうなアイディアが色々出てきます。
![011.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2480832/43d6cbcc-0563-5f52-01fd-6eb7a5e4c76e.jpeg)

## まとめ
Power Appsで例えばどんなものが作れるんだろう？
となっていた部分をAIが補ってくれるのでPower Appsの可能性が広がったように感じました。
ぜひPower AppsとChatGPTを組み合わせて使ってみてください。
python maya xgen collect

```python
import maya.cmds as cmds
import xgenm as xg

dstFolder = 'D:/'

palettes = xg.palettes()
for pal in palettes:
    xgDataPath = xg.getAttr("xgDataPath", pal)
    targetXgDataPath = publish_loc + '/xgen/collections/' + pal

    descs = xg.descriptions(pal)
    geom_nodes = []
    for desc in descs:
        geoms = xg.boundGeometry(pal, desc)
        print(geoms)
        for geom in geoms:
            shapeChildren = cmds.listRelatives(geom, shapes=True)
            geom_nodes.append(shapeChildren[0])
    geom_nodes = list(dict.fromkeys(geom_nodes))
    print(pal, geom_nodes)
    for node in geom_nodes:
        attrs = cmds.listAttr(node, st=pal + '*')
        print(attrs)
        for attr in attrs:
            conns = cmds.listConnections(node + '.' + attr, d=False, s=True, t='file')
            if conns:
                texMapPath = cmds.getAttr(conns[0] + '.fileTextureName')
                print(texMapPath)
                targetTexMapFolder = dstFolder + '/xgen/maps/'
```
フローを使用したカスタム通知の作成

これまでカスタム通知はプロセスビルダーを使用してきたが、
フローで完結できることに気付いたため備忘録。

## 簡単な要件
* 一定条件の取引先が作成されたときがトリガー 
* グループ（のメンバー）に通知を飛ばす
* フローだけで完結させる

## 事前準備
1.カスタム通知種別の作成

[通知種別の作成](https://help.salesforce.com/s/articleView?id=sf.notif_builder_custom_type.htm&language=ja&r=https%3A%2F%2Fwww.google.com%2F&type=5)

2.公開グループの作成

https://savepo.com/how_to_use_public_groups#:~:text=%E5%85%AC%E9%96%8B%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AF%5B%E8%A8%AD%E5%AE%9A%5D%E2%86%92,%E5%90%8D%E3%82%92%E5%85%A5%E5%8A%9B%E3%81%97%E3%81%BE%E3%81%99%E3%80%82

## フロー作成

今回は取引先作成がトリガとなるので、「レコードトリガフロー」を選択。
![スクリーンショット 2022-06-29 151430.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2612959/255ac901-debb-31a6-f5b4-e1258f4946d7.png)

フローの開始条件は以下のように設定。
アクション要素を使用するには「アクションと関連レコード」を選択する必要があります。
![スクリーンショット 2022-06-29 151625.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2612959/263792db-0be1-e5d0-72ba-ef9c54fbc366.png)

フローのアクション要素「カスタム通知を送信」では、以下4つが必須入力の値となります。
①カスタム通知種別のID
②受信者ID
③通知タイトル
④通知本文

③④に関しては変数で用意するでもよし、直打ちするでもよし。割愛します。

まずは①カスタム通知種別IDです。
一般的なレコードのようにURLからIDを見ることができないため、
API参照名を条件に「レコードを取得」してくる必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2612959/ce094c12-69bf-dd0f-7918-dfd70d750bf8.png)

これでOK！

次に②受信者IDです。
公式サイト([フローコアアクション: カスタム通知を送信](https://help.salesforce.com/s/articleView?id=sf.flow_ref_elements_actions_sendcustomnotification.htm&type=5))をみると、
通知受信者として、ユーザ・取引先・商談・グループ・キューが指定可能です。
ただし、ここで注意したいこととしては、
**グループとは公開グループのことを指します。**

※chatterで使用するグループは指定することができません。
 　どうにかこうにかする方法はありました。

今回は公開グループを通知対象とし「レコードを取得」していきます。
公開グループはフローではオブジェクト名「グループ(API:Group)」と表示されます。

:::note warn
※「グループ(API:CollaborationGroup)」と間違えないよう注意
:::
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2612959/2b8036ac-b445-d29b-7523-0aa16575eb37.png)

受信者IDはテキスト型のコレクション変数を設定する必要があるため、
適宜ループしてIDを追加してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2612959/c914f502-ddef-b6bc-713a-47401e48d624.png)


最後にアクションの設定をします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2612959/25d72435-5931-7938-97d4-b520a4033b62.png)
カスタム通知種別 ID：①で取得したカスタム通知種別のID
受信者 ID：②で作成したテキスト型の受信者IDコレクション
通知タイトル：任意の値
通知本文：任意の値

その他の値も必要があれば設定。
以上で設定完了です。

## 動作確認
今回は取引先作成時に通知がされるようなフローを組みました。
無事に通知が表示されてます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2612959/3e697108-465c-d7ae-e2ff-a174f2fd9661.png)

## あとがき
記事を書き始めてから完成させるまでかなり時間が空いたので、
途中で面倒だと思い始めて適当になってます。
chatterグループのメンバーに送信したい場合は、CollaborationGroupでグループを取得し、
CollaborationGroupMemberからUser情報へ、、とたどっていけば何とかなりました。
実際に使用するシーンがあるかは不明ですが備忘録。
ASP.NETのPageMethodsをasync/awaitで書く

ASP.NETで開発をしている人は、.aspx.vbのWebMethodをjs側のPageMethodsで呼び出して使用している人もいるかと思います。しかし、コールバック処理中にさらにPageMethodsでコールバックを行うと、皆さんご存知のコールバック地獄に陥ってしまいます……。令和ですし、もうIE向けに開発する心配もありませんので、async/awaitを使用してPageMethodsを呼んじゃいましょう。
## PageMethodsAsync
```js
function PageMethodsAsync(functionName, ...args) {
    return new Promise((resolve, reject) => {
        PageMethods[functionName](...args,
            result => {resolve(result)},
            error => {reject(error)}
        );
    });
}
```
このコードを使用すれば、コールバック地獄から抜け出すことができます！元々あったPageMethodsをPromiseでラップしています。第一引数にWebMethodの関数名、第二引数以降に渡したい引数を書きます。


## 使用例
```js
// 旧
function InputCheck() {
    // ボタンを押したときの入力チェック
    const val1 = $get('val1');
    const val2 = $get('val2');
    PageMethods.InputCheck(val1,
        function(result) {
        // 成功時処理
            PageMethods.InputCheck2(val2,
                function(result) {
                    // 成功時処理
                }
            )
        }
    )
}

// 新
async function InputCheck() {
    // ボタンを押したときの入力チェック
    const val1 = $get('val1');
    const val2 = $get('val2');
    const result = await PageMethodsAsync("InputCheck", val1);
    const result2 = await PageMethodsAsync("InputCheck2", val2);
    // この後に成功時処理
}
```
これでサーバーからの処理を待てるし、ネストも減りました！やったー！
JavaScript の 配列操作メソッド（shift, unshift, pop, push）はメビウスの輪で覚える

# はじめに
筆者が初学者のとき、JavaScript の配列の中身を取り出したり、追加したりするメソッドが頭の中でこんがらがって「あー！（語彙力）」となることが多々ありました。
筆者が混乱した各メソッドは下記のものです。どれも基礎的なものですね。

```
- shift： 配列の`先頭`から1つ取り出す
- unshift： 配列の`先頭`に1つ追加する
- pop： 配列の`末尾`から1つ取り出す
- push： 配列の`末尾`に1つ追加する
```

![スクリーンショット 2023-06-28 13.44.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3368730/ffb4923b-4cb4-ac1b-4736-faa034ae5027.png)
参考までに各種の挙動を確認したスクリーンショットを貼っておきます。

# 各メソッドの挙動を指でなぞってみると「メビウスの輪」になった
筆者は手を動かしながら学んでいくほうが効率的なタイプの人間なので、頭の中で各メソッドの挙動をそらんじながら指でなぞってみました。左上（shift部分）が始点です。
![0000.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3368730/a254ac7a-1484-b7a0-c2e9-b7ee61c9f3ee.jpeg)
※筆者の頭の中の図

ちなみに筆者的にはこれを閃いたとき、
「まさにこれだ！メビウスの輪だ！！」と一人で盛り上がっていました。

なお、配列操作に関するメソッドは他にもたくさんあります。
@rokumura7(haxpig) 様のこちらの記事が大変分かりやすかったので、他のメソッドも気になる方はアクセスしてみてください。
[【TypeScript/JavaScript】配列操作、利用数順に紹介](https://qiita.com/rokumura7/items/e1e7f4006897e9247572)

# イメージすることで理解力が深まる？
筆者の配列操作メソッドに関する脳内図が、もしどなたかのお役に立てればすごく嬉しいです（本当に）。

文章を読んだり、少し触ってみたりするだけでマスターできる凄まじい理解力をお持ちの方もいらっしゃるでしょう。
しかし筆者のように「読んだり、触ったりしても漠然とした内容しか分からん！」という方も少なくないと思います。

そんな時は自分だけが理解できる形でも良いので何かイメージ（具現化）してみると良いかもしれません。

以上、まさしく「この記事誰得？」な記事を読んでいただき、ありがとうございました！