OmniAICreator/Qiita-1.07M huggingface dataset:
忘備録：モーダルウィンドウ作成

## 久しぶりのJavaScript、モーダルウィンドウ作成をしました。
2年以上放置しておりました。
現実はコーディングではなくデザイン業務ばっかりになってしまったので、また1からJavaScriptを学習することにしました。（ありがとう、ドットインストールさん・・・）

勉強しなおしたことで、以前よりも理解が深まった感じがあるので、
メジャーなものから作成して体に覚えさせていこうと思います〜。
とりあえず今回はモーダルウィンドウから始めていきますよ〜！

## モーダルウィンドウ作成
実際に制作を行う前に、どのように条件を設定して発火させるかメモを取りました。
①：openはクラス名「modal-open」のボタンをクリックして、htmlに「active」クラスをつける
②：closeはクラス名「modal-close」のボタンだけでなく、モーダル背景の「modal-overlay」をクリックしても閉じるようにする

### HTMLの記述
以下、HTMLの記述になります。
わかりやすいようにザッと組んでます。
```HTML
<body class="l-top">
  <div class="l-wrapper">    
    <main class="l-main">
      <p>モーダルウィンドウ練習だよ</p>
      <button class="modal-open">内容を見る</button>
    </main>

    <div id="modal" class="modal-box">
      <div class="modal-item">
        <p>モーダルの中身だよ！！</p>
        <button class="modal-close">閉じる</button>
      </div>
      <div class="modal-overlay"></div>
    </div>
  </div>
  <script src="main.js"></script>
</body>
```
### 始めのJavaScriptの記述
始めは大まかにやりたいこと、条件をザッと記述。
これを基に、動作確認を行ないました。
クリックしたときにhtmlへのクラスの付与、削除を行うので、
一旦このような記述にしています。

```JavaScript
  // クラスの取得、判定に使う
  const htmlElement = document.querySelector('html');

  // 開閉周りの定数
  const modalOpen = document.querySelector('.modal-open');
  const modalClose = document.querySelector('.modal-close');


  // modal_open
  modalOpen.addEventListener('click',()=>{
    htmlElement.classList.add('active');
  });

  // modal_close
  modalClose.addEventListener('click',()=>{
    htmlElement.classList.add('active');
  });
```

### 動作確認後のJavaScriptの記述
クラスの付与、削除がうまくできたのが確認できたので
ここからボタンとモーダル背景をクリックしたときにクラス削除して閉じるように整えていきます。
モーダル背景の部分で手間取ってしまったので、以下のサイトを参考にforEach文で処理を指定して
モーダルが閉じるようにしました。

https://ics.media/entry/220620/

```JavaScript
  // クラスの取得、判定に使う
  const htmlElement = document.querySelector('html');

  // 開閉周りの定数
  const modalOpen = document.querySelector('.modal-open');
  const modalClose = document.querySelector('.modal-close');
  const modalOverlay = document.querySelector('.modal-overlay');


  // modal_open
  modalOpen.addEventListener('click',()=>{
    htmlElement.classList.add('active');
  });


  // close イベント：ボタンと背景クリックで閉じるようにする
  // forEach文で特定の配列に特化した処理を指定
  const modalCloseEvent = [modalClose,modalOverlay];
  modalCloseEvent.forEach((e)=>{
    e.addEventListener('click',()=>{
      htmlElement.classList.remove('active');
    });
  });
```

### 最後に...
forEachの存在を忘れていたので、「なんでここでforEach？」となりましたが、
こちらを確認してみて、なるほどな〜となりました。

https://and-engineer.com/articles/YhM_2BMAAKtubeB-#heading2-1

どういった処理でこれができる、とかのストック、どんどん増やしていかないとな〜と思いました。
GNU SCREENを使って自動で複数の(screenの)windowを作る

# 概要

普段からGNU SCREENを利用しており、いつも最初にscreenを立ち上げた後に定型的な処理を行う人にとって良い情報かもしれません
ナウなヤングはtmuxを使ってると思うのでおじさん向けの情報となります

僕の場合はいつも適当に5〜6個くらいwindowを作り、0番に `glances` 1番にルーターへのssh接続 2番に `nethogs` を起動しています

## スクリプト

screen初期起動用のスクリプトを用意します

```bash:init-screen
#!/bin/bash

# screenの初期起動用スクリプト
session_name=init-session

# screenが既に立ち上がっていればアタッチする
screen -ls $session_name
if [ $? -eq 0 ]; then
  screen -r $session_name
  exit 0
fi

# ホームディレクトリをカレントディレクトリとしたい
cd ~
screen -dmS $session_name
cd - > /dev/null

# 追加で適当にwindowを6個作る
screen -S $session_name -X screen
screen -S $session_name -X screen
screen -S $session_name -X screen
screen -S $session_name -X screen
screen -S $session_name -X screen
screen -S $session_name -X screen

sleep 1

screen -S $session_name -p 0 -X stuff "glances$(printf \\r)"
screen -S $session_name -p 1 -X stuff "ssh router$(printf \\r)"
screen -S $session_name -p 2 -X stuff "sudo nethogs$(printf \\r)"

screen -r $session_name
```

### ポイント

- セッション名を指定しておくことで `screen -ls セッション名` でそのセッション名のscreenが立ち上がってるかを確認できる
- セッション名を指定しておくことで `screen -r セッション名` でそのセッション名のscreenをアタッチできる
- `-X screen` オプションで新規windowを作成できる
- `-X stuff "文字列"` オプションでwindowに対して文字列を流し込める
- `-p windowの番号` オプションで特定のWindowに対して操作ができる

ざっくりこんな感じのようです

### 上記を踏まえて

先述のスクリプトをざっくり解説してみます

まずはセッション名を `init-session` としておきます
スクリプト起動時にセッションが存在しているか確認し、存在していればアタッチするようにしています
ここはお好みで何も起きないようにするなども考えられそうです

```bash
#!/bin/bash

# screenの初期起動用スクリプト
session_name=init-session

# screenが既に立ち上がっていればアタッチする
screen -ls $session_name
if [ $? -eq 0 ]; then
  screen -r $session_name
  exit 0
fi
```

次にscreenを立ち上げる前にホームディレクトリに移動します
ここもお好みで特定のディレクトリに移動するケースがあっても良さそうです
`/path/to/任意` でscreenを立ち上げると新規にwindowを作った際のカレントディレクトリが `/path/to/任意` になります

ここでセッション名付きでscreenを立ち上げます
`-d` `-m` オプションを利用してデタッチ状態でscreenを立ち上げます
`-S` オプションを利用してセッション名を指定します

その後、元のディレクトリに戻っておきます
(これ要らん気もする)

```bash
# ホームディレクトリをカレントディレクトリとしたい
cd ~
screen -dmS $session_name
cd - > /dev/null
```

続いて追加でwindowを作ります
今回のケースでは上記で0番目が出来ているので、最低でも追加で2つ用意しておきます
僕はいつも適当にいくつかwindowを作るのでそれにならって6つ追加するようにしておきました

```bash
# 追加で適当にwindowを6個作る
screen -S $session_name -X screen
screen -S $session_name -X screen
screen -S $session_name -X screen
screen -S $session_name -X screen
screen -S $session_name -X screen
screen -S $session_name -X screen
```

この後に各windowに対してコマンドを実行するのですが、続いて実行させたところzshの立ち上がりが遅いせいかnethogsだけ起動してるみたいなケースになってしまったのでsleepを挟みました
僕の環境ではこれで上手く行きましたが、もっとsleepした方が良いケースやそもそもsleepが要らないケースもあるかもしれません

`-X stuff "$command$(printf \\r)"` という形で任意のコマンドを流し込めますので色々やってみてください

```bash
sleep 1

screen -S $session_name -p 0 -X stuff "glances$(printf \\r)"
screen -S $session_name -p 1 -X stuff "ssh router$(printf \\r)"
screen -S $session_name -p 2 -X stuff "sudo nethogs$(printf \\r)"
```

もろもろ準備が出来たので最後にアタッチしてやります

```bash
screen -r $session_name
```

---

それでは良いscreenライフを
【Spanner】クエリパラメータを使用しないとパフォーマンスが著しく落ちる話

# はじめに
ベストプラクティスにさらっと書いてあるんですが、Spannerではクエリパラメータを使用することが推奨されています。
他のDBだとプリペアードステイトメントとか言われてるやつです。（違うかも）
SQLiの視点から言っても必ず使ったほうがいいです。

https://cloud.google.com/spanner/docs/sql-best-practices?hl=ja#query-parameters

# 解説
以下のような適当なテーブルに適当な初期データを入れて試します。

```sql
CREATE TABLE SampleTable (
    id INT64 NOT NULL,
    name STRING(100),
    age INT64,
    email STRING(255),
) PRIMARY KEY (id);

INSERT INTO SampleTable (id, name, age, email) VALUES
(1, 'John Doe', 30, 'john@example.com')
```
Query Insightsでパラメータ化を使用する前後の結果を見ていきます。

## クエリパラメータを使用しない場合
```sql
UPDATE
  `SampleTable`
SET
  `name` = 'test3',
  `age` = 30,
  `email` = 'test1'
WHERE
  `id` = 1
```

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/855584/fe7edb90-c52e-aaeb-20e9-e9bb26422809.png">

<br>

### 結果
| CPU(ms) | レイテンシ(ms) |
|:-:|:-:|
| 2.9  | 9.02 |

## クエリパラメータを使用した場合
以下のようにパラメータ化して入れてあげます。
Spanner Studioでは試せないのでコードベースで入れてあげます。（省略）
使わなかった場合と同じ条件で試しています。

```sql
UPDATE
  `SampleTable`
SET
  `name` = @SampleTable_name_1,
  `age` = @SampleTable_age_2,
  `email` = @SampleTable_email_3
WHERE
  `id` = @SampleTable_id_4
```

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/855584/2f38d535-2e20-11bc-fc75-447a2a3fadf5.png">

### 結果
| CPU(ms) | レイテンシ(ms) |
|:-:|:-:|
| 0.6  | 6.28 |

# まとめ

| パラメータ化 | CPU(ms) | レイテンシ(ms) |
|:-:|:-:|:-:|
| 未 | 2.9  | 9.02  |
| 有 | 0.6  | 6.28  |

レイテンシは3/4ほど、CPUに至っては半分以下になっています。
たった1テーブル1レコードでこの結果なので、ユーザーデータに使われがちなSpannerならば必ず対応すべきモノだと思われます。

# 考察
### 構文解析が余計に走っている？
自分の先生曰く、Spannerは最終的にパラメータ化されたクエリで解釈しているのではないかと予想していました。
パラメータ化しないで使用すると内部でパラメータ化するための構文解析が走るということですね。

### うまい感じに再利用されていない？
Query Insightsを見ているとパラメータ化したパターンとしないパターンではそれぞれ別にキャッシュされているっぽいと感じました。

例えば以下のクエリは再利用不可であり、前述の構文解析の話と合わせると毎回余計な解析が走っている（そもそもキャッシュしていないのか）
```sql
UPDATE
  `SampleTable`
SET
  `name` = 'test3',
  `age` = 30,
  `email` = 'test1'
WHERE
  `id` = 1
```

パラメータ化するとおそらく以下のクエリがキャッシュされていて再利用されているっぽい？
```sql
UPDATE
  `SampleTable`
SET
  `name` = @SampleTable_name_1,
  `age` = @SampleTable_age_2,
  `email` = @SampleTable_email_3
WHERE
  `id` = @SampleTable_id_4
```

ユーザーデータが毎回同じなわけないので、下の解析後のクエリをいい感じに再利用しているのかなと予想。
あくまで予想。

# 要望
さらっと試すためにSpanner Studioからパラメータを入力できるようにして欲しい。
スマホでlocalhostをテストする方法3選

web開発をする際、スマホで正常に動作するかの確認をしたい場面があると思います。
その際にさまざまな方法を試したので備忘録としてまとめました。

## 方法1 ： IPアドレスを使用する

1. PC, スマホが同じwifiに接続していることを確認
2. スマホで`http://[IPアドレス]:[ポート番号]`にアクセス(例：`111.111.1.1:8080`)

### 注意点

- 自分のアパートの備え付けであるwifiだと何故かできませんでした泣
- 同じwifiに接続しているデバイスでないとアクセスできません

## 方法2 ： ngrokを使用する

1. ngrokをインストール(macの場合、`brew install ngrok`)
2. `ngrok http [ポート番号]`を実行
3. 発行されたURLにアクセス

### 注意点

無料枠があり、アクセスしすぎると課金しないといけません。

## 方法3 : localtunnelを使用する

1. `npx localtunnel --port [ポート番号]`を実行
2. ローカルホストを立てているPCが接続しているネットワーク上のデバイスでhttps://loca.lt/mytunnelpassword にアクセスしてパスワードを取得
3. 発行されたURLにアクセスし、取得したURLを入力して送信

### 注意点

心なしかngrokに比べて遅い気がします。

## どれがいいのか？

基本的にはIPアドレスを使った方法で事足りると思います。自分はwifiの設定?の関係で使えなかったので、代わりにlocaltunnelを使用しています。

ローカル環境を他の人に共有したい場合、ngrokやlocaltunnelを使用すると良いと思います。


## 参考
https://qiita.com/nanarya/items/8d01dfb4de434e7b8acc

https://qiita.com/onosan729/items/44214f9f81d3ebda58bd

https://www.hanachiru-blog.com/entry/2023/07/06/120000
例外クラス よく使うメソッド

###
■例外クラスの主要なメソッド
#
※超自分用備忘録です。

・getMessage: 例外の説明を取得。
・getStackTrace: 例外がスローされたときの呼び出し ※1スタック情報を取得。
・printStackTrace: 例外がスローされたときの呼び出し　※1スタック情報を「出力」
・getCause: 例外の原因となった別の例外を取得。
・toString: 例外の説明と呼び出しスタック情報を文字列として取得。

```java
public class Lesson4a {
    public static void main(String[] args) {
        try {
            int a = 10;
            int b = 0;
            int result = a / b;

            System.out.println("tryブロックの中で例外は発生しませんでした。");
    
        } catch (Exception ex) {
                                //getMessageはJavaが用意してるExceptionクラスの戻り値
            System.out.println("例外が発生しました!" + ex.getMessage());
            System.out.println(ex.getStackTrace()); //１スタック情報呼び出し
            ex.printStackTrace(); //１スタック情報呼び出し・出力
            System.out.println(ex.getCause()); //例外の原因となった別の例外を取得
            System.out.println(ex.toString()); //例外の説明と呼び出しスタック情報を文字列として取得

        } finally {
            //必ず実行する処理（省略可能）
            System.out.println("finallyブロック実行");
        }
    }
}
```
PythonでPDFファイルのリンクを検出する方法

### PythonでPDFファイルのリンクを検出する方法

Webアプリケーションやデータ収集の過程で、リンク先のファイルがPDFかどうかを事前に知る必要がある場合があります。ここでは、Pythonの`requests`ライブラリを使用して、URLがPDFファイルを指しているかどうかを効率的に確認する方法について解説します。

#### 必要なライブラリ
このスクリプトでは`requests`ライブラリを使用します。まだインストールされていない場合は、以下のコマンドでインストールできます。
```
pip install requests
```

#### スクリプトの解説
まず、チェックしたいPDFファイルのURLリストを生成します。以下のコードでは、特定の形式に基づいてURLを生成しています。

```python
urls = [f'https://xxx.com/pdf/202404{x:04}01.pdf' for x in range(1000, 10000)]
```

次に、各URLに対してHTTP HEADリクエストを送信し、レスポンスヘッダーから`Content-Type`を確認します。`Content-Type`が`application/pdf`を含む場合、そのURLはPDFファイルであると判断します。

```python
import requests

for url in urls:
    try:
        response = requests.head(url, allow_redirects=True)
        content_type = response.headers.get('Content-Type', '')

        if 'application/pdf' in content_type:
            print(f'PDFファイルが見つかりました: {url}')
        else:
            print('NG', url)  # PDFファイルでない
    except Exception as e:
        print(f'エラーが発生しました: {e}')
```

#### 注意点
- `requests.head()` メソッドは、GETリクエストと同様にサーバーにリクエストを送るものの、ボディはダウンロードせずにヘッダー情報のみを取得します。これにより、リソースの消費を抑えることができます。
- `allow_redirects=True` はリダイレクトを追跡するためのものです。一部のサーバーでは、リクエストがリダイレクトされる場合があり、その追跡を許可することで正確な情報を得られます。

### まとめ
このスクリプトは、URLがPDFファイルかどうかを迅速に確認する簡単な方法を提供します。データ収集や自動化スクリプトで役立てることが期待されます。
ページを更新しないとform_withのボタンが押せない

rails6.1の環境でform_withを使った情報入力フォームを作成したときのことです。

フォームに付けた <%= f.submit '送信', class: "btn btn-info" %>のボタンをクリックしてもページ遷移せず、ページを更新したらなぜか押せるようになるという現象が起こっていました。

どうしてだろうと思い、form_withについて調べたり、
urlが違うのか？とか、単語を打ちミスしてるのか？とか
思いながら原因を探っているとすごく単純なミスを見つけました。

こちらが不具合が出ていた時の記述です。

```html
    <h1>注文情報入力</h1>
    
<!--ここのdivの位置が…-->
<div>
<%= form_with model: @order, url: '/orders/confirm' do |f| %>
  
    <h2>支払方法</h2>
    <div class="row d-flex flex-column">
      <div class="col">
        <%= f.radio_button :payment_method, "credit_card" %>
        <%= f.label :payment_method, "クレジットカード" %>
      </div>
      <div class="col">
        <%= f.radio_button :payment_method, "transfer" %>
        <%= f.label :payment_method, "銀行振込" %>
      </div>
    </div>
  </div>
：
：
<% end %>
```

↓修正後はこっち。修正したのは1箇所のみです。

```html
<h1>注文情報入力</h1>
<%= form_with model: @order, url: '/orders/confirm' do |f| %>
<!--正しくはここだった-->
  <div>
    <h2>支払方法</h2>
    <div class="row d-flex flex-column">
      <div class="col">
        <%= f.radio_button :payment_method, "credit_card" %>
        <%= f.label :payment_method, "クレジットカード" %>
      </div>
      <div class="col">
        <%= f.radio_button :payment_method, "transfer" %>
        <%= f.label :payment_method, "銀行振込" %>
      </div>
    </div>
  </div>
：
：
<% end %>
```

単純にform_withの内側で要素を囲っていてほしかった<&#8203;div><&#8203;/div>の片方が外に出てしまっていたようです。
分かりやすく書くと、こういう形になっていました。
```html
  <div>
<%= form_with model: @order, url: '/orders/confirm' do |f| %>
  </div>
<% end %>
```

原因をあれこれ探した末に頭を抱えていたところ、ふいに見つけたのですが、発見した瞬間は思わずクスッと笑ってしまいました。

不具合の原因を行き当たりばったりで探そうとするのはよくないですね。
余計な作業に時間を取られてしまったので、メソッドやオプションの意味も理解して自分で原因の仮説をしっかりと頭で考えられるようにしなければと感じました。

ただ、まだなぜページを更新したらボタンが押せるようになったのかが謎です。
また余裕のある時に検証してみたいです。
consoleからfetchを叩くとcorsエラーになる


# はじめに

cors 難い。今回は 通常のアプリケーション上の挙動では問題なかったが、dev tools 上で fetch を叩いた時、限定で cors エラーが発生したのでその解決方法を記載します。

# 要約

network タグから fetch をコピーすると `cache-control: no-cache` と `pragma: no-cache` header がつきます。これらの header がついた上で [preflight](https://developer.mozilla.org/ja/docs/Glossary/Preflight_request) リクエストが飛ぶような状況では ブラウザが自動で ['Access-Control-Request-Headers'](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Request-Headers) に　 cache-control と pragma を追加します。backend の cors 許可ではこれらの header が許可されている必要があるのですが、ここの設定ができていなかったため cors エラーが発生していました。

# 前提

- クロスオリジンへのリクエスト
- preflight が飛ぶ状況

# 詳細

## 1. 背景

ある日、特定の fetch のデバッグのために、dev tools の network タブから fetch をコピーして console 上で実行しました。
通常のアプリケーションの動作時は問題ないのですが、この時のみ cors エラーとなってしまったのです。

## 2. 原因

実は、fetch をコピーするときに、`cache-control: no-cache` と `pragma: no-cache` がついていました。
これらの header がついた上で preflight が飛ぶような状況では、ブラウザが自動で `Access-Control-Request-Headers` に　 cache-control と pragma を追加します。
バックエンドの cors 設定ではこれらの header が許可されている必要があるのですが、ここの設定ができていなかったため cors エラーが発生していました。

## 3. 解決法

下記のいずれかで解決できました。

- バックエンドの cors 設定で [`Access-Control-Allow-Headers`](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Allow-Headers) に `cache-control` と `pragma` を追加する

- console で実行する際に `cache-control` と `pragma` を削除する

# まとめ

fetch を コピーした際に credential や cache-control など実際の挙動と異なる header がついていることがあるので注意が必要。
viコマンドについて

今日はLinux勉強始めた人がつまずきやすいVIコマンドについて書こうと思います


Viとは
-----
Viは、テキストエディタであり、UNIX系システムやLinuxなどのオペレーティングシステムで広く使用されています。
リポジトリが確認するファイルを編集するときはVIを使って編集することが多い気がします。

キーボードを使用してテキストを編集するための多くのコマンドがあります。以下に、基本的なViのコマンドを解説します。

ノーマルモード（Normal Mode）
----

Viを起動すると最初にこのモードになります。このモードでは、テキストを編集するためのコマンドを入力します。
カーソル移動:
h: 左に1文字移動
j: 下に1行移動
k: 上に1行移動
l: 右に1文字移動
^: 行の先頭に移動
$: 行の末尾に移動
gg: ファイルの先頭に移動
G: ファイルの末尾に移動
w: 次の単語の先頭に移動
b: 前の単語の先頭に移動

挿入モード（Insert Mode）
----
テキストを挿入するためのモードです。
挿入モードに入る方法：i（カーソル位置で挿入）、a（カーソル位置の次の文字で挿入）

i: カーソルの位置に挿入
I: 行の先頭に挿入
a: カーソルの次の位置に挿入
A: 行の末尾に挿入
o: カーソルの下に新しい行を挿入
O: カーソルの上に新しい行を挿入
x: カーソル位置の文字を削除
dd: カーソル位置の行を削除
D: カーソル位置から行末までを削除
yy: カーソル位置の行をコピー
p: カーソルの次の位置に貼り付け


ビジュアルモード（Visual Mode）
----
テキストを選択するためのモードです。
ビジュアルモードに入る方法：v（文字単位で選択）、V（行単位で選択）

v: 文字単位の選択モード
V: 行単位の選択モード
Ctrl+v: 矩形選択モード

コマンドラインモード（Command-line Mode）
---

ファイルの保存や終了など、Vi自体のコマンドを入力するためのモードです。
コマンドラインモードに入る方法：コロン（:）を押す
ファイルの保存：w（ファイルを保存）、wq（保存して終了）、q!（変更を無視して終了）

:w: ファイルを保存
:q: エディタを終了
:wq: 保存して終了
:q!: 変更を無視して終了


これらは、Viの基本的なコマンドの一部です。Viは初めて使う人にとっては少し学習が必要かもしれませんが、慣れると非常に効率的なテキストエディタとして活用できます。




なれたらすごく便利になるのでぜひ学習してみてください！！
LPIC、LinuCレベル１でも絶対出てきます！
[ひとくちThymeleaf Tips] th:disabled disabledプロパティの適用

# ひとくちThymeleaf Tips disabledプロパティの適用
「Thymeleafってこんな機能があるんだ」という気分になってくれたらいいなという、Thymeleafの機能について一口サイズで紹介させていただく記事です。

# 非活性のステータスにするth:disabled
htmlの標準機能にある「disabled」を条件付きで指定できるthタグです。
通常のhtmlだと「disabled」の付与自体は可能なのですが、動的に「disabled」する/しないを切り替えるためにはjavaScriptなど外部からの制御が必要。
基本的に「活性/非活性」はセットで動的に切り替えられるように作るはずなので、そんな時に便利なthタグです。

# 記法
以下の{条件式}がtrueの場合のみ、「disabled」が適用されます。
```
th:disabled="{条件式}"
```

# 使用例
htmlに紐づけてあるFormオブジェクト「doClick（boolean型）」のステータスを見て、disabledを切り替える例です。
htmlヘッダなど細かい部分は割愛します。
「doClick」がfalseの場合のみボタンにdiabledが適用されます。「クリックし手非活性に」ボタンを押下すると「doClick」にfalseが設定されるように記載。


```Hello.html
<body>
	<h1>Hello World</h1>
		
	<form method="post" action="/hello/post" th:object="${helloForm}">
		<span th:text="*{demoText}"></span>
		<input type="submit" value="クリックして非活性に" th:disabled="*{doClick == false}">
	</form>
</body>
```

```DemoController.java
@Controller
@RequestMapping("/hello")
public class DemoController {

	
	@GetMapping
	public String getHello(Model model,@ModelAttribute HelloForm form) {
		String url="demo/hello.html";
		
		form.setDemoText("ステータス切り替え前");
		form.setDoClick(true);
		
		return url;
	}
	
	@PostMapping("post")
	public String getPost(Model model,@ModelAttribute HelloForm form) {
		String url="demo/hello.html";

		form.setDemoText("ステータス切り替え後");
		form.setDoClick(false);
		return url;		
	}
}
```

```HelloForm.java
@Data
public class HelloForm {
	
	private String demoText;
	
	private boolean doClick;

}

```

## 画面起動して確認
ボタン押下前
![スクリーンショット 2024-04-21 180532.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3355023/d5fe5d83-a487-14bc-cd2b-083338447c3d.png)


ボタン押下後
![スクリーンショット 2024-04-21 184700.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3355023/d168670c-a6f7-c444-2744-ea7d3d3e15a5.png)
RailsのActive Recordでの安全な日付比較方法

## はじめに
Ruby on RailsのActive Recordを使用してデータベースから情報を取得する際に、特定の日付以降のレコードをフィルタリングする方法にはいくつかの書き方があります。ここでは、その中でもよく使われる方法と、その際に考慮すべきセキュリティリスクについて解説します。

## 1. プレースホルダーを使用する方法（推奨）
```ruby
Reservation.where('date >= ?', Date.today)
```
この方法は、SQLインジェクション攻撃を防ぐために推奨されます。プレースホルダー（`?`）を使用することで、Active Recordが自動的に引数の値を適切にエスケープし、安全にクエリを実行します。

**利点:**
- SQLインジェクションのリスクを最小限に抑えることができます。
- Active Recordが自動的にデータのサニタイズを行います。

## 2. 直接変数を埋め込む方法（非推奨）
```ruby
Reservation.where("date >= '#{Date.today}'")
```
この書き方では、Rubyの文字列展開を利用しています。この方法は非推奨であり、外部からの不正な入力がクエリに直接組み込まれる場合、SQLインジェクションの危険性があります。

**リスク:**
- SQLインジェクションの可能性があり、セキュリティ上危険です。
- 外部からの入力をそのままクエリに使用する場合は特に避けるべきです。

## 3. 誤った構文の例
以下の構文はRubyまたはRailsのActive Recordでは無効です。
```ruby
Reservation.where(date >= , Date.today)
Reservation.where(date: >= , Date.today)
```
これらの書き方はRubyの文法エラーを引き起こし、正しく機能しません。シンタックスエラーが発生し、アプリケーションがクラッシュする原因となります。

## まとめ
安全なWebアプリケーションを開発するには、データベースへのクエリは慎重に扱う必要があります。プレースホルダーを使用した方法が最も安全で、Active Recordが提供する保護機能を活用できます。SQLインジェクションを避け、アプリケーションを安全に保つためには、常にこの方法を選択することをお勧めします。
Elixirで言語処理100本ノックやってみた！vol.6②積集合、③差集合、④含まれてるかチェック

こんにちは！
3ヶ月で、Elixir言語で開発できるようになるため、奮闘中のこーへーです！

vol.6①和集合の続きで、『②積集合、③差集合、④含まれてるかチェック』の問題を解いていきたいと思います。

[①和集合の記事はこちら](https://qiita.com/kohei-s/items/b0569090fe91efe70ed4)
[100本ノックの記事はこちら](https://nlp100.github.io/ja/ch01.html)

### 06の問題
“paraparaparadise”と”paragraph”に含まれる文字bi-gramの集合を，それぞれ, XとYとして求め，XとYの和集合，積集合，差集合を求めよ．さらに，’se’というbi-gramがXおよびYに含まれるかどうかを調べよ．

### ②積集合
積集合とは、２つの集合共通要素のことです。

つまり、「paraparaparadise」と「paragraph」を文字のbigramにしたのち、共通要素を探し出すという内容になります。
 #### Comprehensive.be_commonの作成、実行

 ```
 defmodule Comprehensive do
  def be_common(x, y) do
    String.codepoints(x <> y)
    |> Enum.chunk_every(2, 1, :discard)
    |> Enum.map(fn x -> Enum.join(x) end)
    |> Enum.frequencies
    |> Enum.filter(fn {_key, value} -> value > 1  end)
    |> Enum.map(fn {key, _value} -> key end)
  end
end
  ```

```
iex> Comprehensive.be_common("paraparaparadise", "paragraph")

["ap", "ar", "pa", "ra"]
```

### ③差集合
積集合とは、２つの集合の差要素のことです。

つまり、「paraparaparadise」と「paragraph」を文字のbigramにしたのち、差を探し出すという内容になります。

#### Comprehensive.relative_complementの作成、実行
 ```
 defmodule Comprehensive do
  def relative_complement(x, y) do
    String.codepoints(x <> y)
    |> Enum.chunk_every(2, 1, :discard)
    |> Enum.map(fn x -> Enum.join(x) end)
    |> Enum.frequencies
    |> Enum.filter(fn {_key, value} -> value < 2 end)
    |> Enum.map(fn {key, _value} -> key end)
    |> List.delete_at(1)
    |> List.delete_at(2)
    |> List.delete_at(2)
    |> List.delete_at(3)
  end
end
```
```
iex> Comprehensive.relative_complement("paraparaparadise", "paragraph")

["ad", "di", "is", "se"]
```

### ④要素が含まれてるかチェック
最後は、"se"要素が、「paraparaparadise」と「paragraph」に含まれているかをチェックするという内容になります。


#### Comprehensive.includeの作成、実行
```
 defmodule Comprehensive do
  def include(x, y) do
    String.codepoints(x <> y)
    |> Enum.chunk_every(2, 1, :discard)
    |> Enum.map(fn x -> Enum.join(x) end)
    |> Enum.member?("se")
  end
end
```

```
iex> Comprehensive.include("paraparaparadise", "paragraph")

true
```

### まとめ
８割ぐらいまでは、サクッと答えに近づけるようになってきましたが、残り２割の詰めがひつ筋縄ではいかず、色々試行錯誤しました。
特に、③の問題はもっとスマートに書けると思うんですが、それは今後の課題として、まずはクリアすることを目標にやりました。

これで、vol6はクリアしたので、vol7にチャレンジしたいと思います。
Git-2 基本操作とコマンド

### 概要
Gitの基本操作はInit、 Change、 Staging、 Commitの4つである。
※筆者が勝手に考えた操作名称で、公式ではない。

### 前提
gitコマンド実行時は、バージョン管理対象のプロジェクトをカレントディレクトリに設定する。

### [Init] 空のgitリポジトリを新規作成する（.gitディレクトリを新規作成する）
`git init`

このコマンドを実行しないとバージョン管理が開始できない。
尚、既にgitリポジトリがある場合はこのコマンドを実行しても空のgitリポジトリは作成されない。

### [Change] ワーキングディレクトリでファイルの変更作業を行う
この工程ではgitコマンドは関係ない。ワーキングディレクトリ内でファイルの追加・編集・削除を行う。

### [Staging] コミットしたいファイルをステージングする
`git add [ファイル1] [ファイル2] ...`
ワーキングディレクトリ内のファイルから、次のコミット対象に含めるファイル（複数可）を指定する。次にコミットを実行したとき、gitリポジトリに記録されるのはここで指定したファイルだけである。尚、このコマンドは次にコミットするまでに何度でも実行することができる。

内部的には、.gitの中のステージング・エリア（インデックスとも言う）という領域に次のコミット対象の情報が記録される。そのため、このコマンド操作をステージングする等と言ったりする。gitリポジトリにコミットするために必要不可欠な操作である。

※既存のプロジェックトにgitリポジトリを作成した時、プロジェクト内の全てファイルは最初に一度ステージングしておかないと、gitによるファイル管理の対象にならない。

### [Commit] コミットを実行する
`git commit`
ステージングされたファイルをgitリポジトリに保存する。
また、コミット時のメッセージやその時点でのプロジェクト全体のファイル・ディレクトリの状態（スナップショット）など、バージョン管理に必要な情報も紐づけてgitリポジトリに記録する。このように、コミット操作時に記録される情報のことをそのまま"コミット（情報）"と言ったりする。
Kotlin Fesst2024のプロポーザルを出した時のメモ

TLDR
- Kotlin Fest 2024にプロポーザルを出した所感
- プロポーザルを出す注意点
- テーマについて

## Kotlin Fest 2024にプロポーザルを出した所感

[Kotlin Fest　２０２４](https://www.kotlinfest.dev/)が開催されます。今年はオフライン開催だそうで、前回は一参加者として聴講していました。今年は登壇を目指してプロポーザルを出しました。

https://fortee.jp/kotlin-fest-2024/proposal/73b12854-cd0a-4132-8a0c-b55b72c0925a

レベルの高いプロポーザルも多いため採用までいけるか微妙なところですがとりあえずカンファレンスのプロポーザルを出すという今年の目標を達成しました。

やってみた感想としては、出してみて良かったということです。プロポーザルを何にするか考えたりまとめたりするのはそれだけで非常に勉強になります。最近業務以外であまりアウトプットができていないのでこれを気に増やしていけたらと。

## プロポーザルを出す注意点

プロポーザルを出す際の注意点として文字数制限があったことです。自分が見落としていたのかプロポーザル本文に400文字の制限がありました。下書きしているときは700文字くらいになっていたので削る作業が大変でした。

## テーマについて
「Kotlinでテスト駆動開発を始めよう」というタイトルで、詳しくはプロポーザルのページに譲りますが、テスト駆動開発（TDD)を実施しながらKotlinのテスト方法について触れていこうという内容です。

TDDは知名度こそあれどあまり実践している人は少ないように思います。使い所はありますが実用的ですし、もし実用しなくても思想的にも面白いです。こちらが参考文献です。

https://www.amazon.co.jp/%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA-Kent-Beck/dp/4274217884

こちら今回のために再読したのですが、最後の付録のところをしっかり読みいろいろ感じることがありました。TDDは万能と誤解されたり、論争を生むことがあるようなので、もし発表するとしたらそのあたりも気をつけないといけないなと思いました。

最近テストを考えることが多くあり、どれだけDRYとDUMPのバランスとか、テスト用のオブジェクトをどう管理するかとか、いろいろ考えていました。やっぱり良いテストを書くと安心感は高められるので、良いテストを書くために勉強中です。そんななかで普段から実践しているTDDをテーマに選びました。

キーワードとして挙げた内容のうち、テストダブルについて最近話題に上がっていたのも見ました。FlowのテストではTurbineがいい感じなのでテーマになるかと思いました。ワンポイントの切り口で深いところまで切り込んだ内容ではなく、テスト全般を広く浅く扱う内容です。レベルとして中級者レベルにしましたが、初級者レベルにするか迷いました。

採用されなかったとしてもなにか記事にできたらと思っています。
Python strに含む内容の判断

s 为字符串

s.isalnum()  所有字符都是数字或者字母，为真返回 True，否则返回 False。

s.isalpha()   所有字符都是字母，为真返回 True，否则返回 False。

s.isdigit()     所有字符都是数字，为真返回 True，否则返回 False。

s.islower()    所有字符都是小写，为真返回 True，否则返回 False。

s.isupper()   所有字符都是大写，为真返回 True，否则返回 False。

s.istitle()      所有单词都是首字母大写，为真返回 True，否则返回 False。

s.isspace()   所有字符都是空白字符，为真返回 True，否则返回 False。
promiseとthenの使い方

# はじめに
非同期通信でよく使うpromise、thenについて解説する。

# 説明
下記が参考コードになります。

```JavaScript
function processWait() {
    let promise = new Promise(function(resolve) {
        setTimeout(function() {
            resolve();
        }, 1000);
    });
    return promise;
}

let promise = processWait();

promise
.then(function() {
    console.log('朝だよ、起きて(1回目)');
    let promise = processWait();
    return promise;
})
.then(function() {
    console.log('朝だよ、起きて(2回目)');
    let promise = processWait();
    return promise;
})

```

## processWait関数について
関数内では、new Promise(コールバック関数)で新しいPromiseオブジェクトを作っています。
コールバック関数の引数`resolve`は関数で、この関数がコールバック関数内で実行されると、Promiseは「解決済み」となります。今回でいうと、setTimeout関数の設定により、1000ミリ秒経過するとresolve()が実行され解決済みになります。「解決済み」となることで、1つ目のpromise.then(function() { 〜 })が実行されます。

```JavaScript
function processWait() {
    // promiseオブジェクトを作成
    let promise = new Promise(function(resolve) {
        // 1000ミリ秒後に処理をする
        setTimeout(function() {
            resolve(); // promiseを解決するresolve関数
        }, 1000);
    });
    return promise;  // Promiseオブジェクトを返す
}

let promise = processWait();
```

## Promiseの処理
processWait関数から返されたPromiseオブジェクトをpromise変数に格納します。
```JavaScript
let promise = processWait();
```

then()はPromiseオブジェクトが持つメソッドで、Promiseオブジェクトのresolve()が実行されると、then()のコールバック関数が実行される。ゆえにまず1つ目のthenの処理が実行される。
またthen()実行後にpromiseオブジェクトをreturnすることで、`.then().then()`のように数珠つなぎで処理を1つずつ行うことができる。なので1つ目のthen()が実行されたあと、2つ目のthen()が実行される。

```JavaScript
promise
.then(function() {
    console.log('朝だよ、起きて(1回目)');
    let promise = processWait();
    return promise; // Promiseオブジェクトを返す
})
.then(function() {
    console.log('朝だよ、起きて(2回目)');
    let promise = processWait();
    return promise;
})
```

# 終わり
Promiseとthenについて説明しました。
結論として、Promiseのコールバック関数内で、resolve()が実行されると、then()が実行されます。
fly.ioにデプロイすると「instance refused connection. is your app listening on・・・」、「Out of memory: Killed process 334」というエラーが出た件

fly.ioにデプロイしたときに以下のようなエラーに遭遇しました。このエラーの解決法を紹介させていただきます。
```
・・・nrt [error] [PC01] instance refused connection. is your app listening on 0.0.0.0:8080? make sure it is not only listening on 127.0.0.1 (hint: look at your startup logs, servers often print the address they are listening on)

・・・ nrt [info] [ 2.598432] Out of memory: Killed process 334 (compile) total-vm:1379336kB, anon-rss:142832kB, file-rss:0kB, shmem-rss:0kB, UID:0 pgtables:412kB oom_score_adj:0
```

# 解決法
修正後は以下のとおりです。
fly.tomlのmemoryを512mbに増やしました。当初はmemoryを256mbとしていました。上記のエラーに「Out of memory」と記載されているようにメモリー不足が原因のようでした。
```toml:fly.toml
app = APP_NAME
primary_region = 'nrt'

[build]

[http_service]
  internal_port = 8080
  force_https = true
  auto_stop_machines = false
  auto_start_machines = true
  min_machines_running = 0
  processes = ['app']

[[vm]]
  memory = '512mb'
  cpu_kind = 'shared'
  cpus = 1

```
1個目のエラーの「instance refused connection」に固執してportを変更するなど色々試すもうまくいかず解決に時間がかかってしまいました...。

なお、修正前のfly.tomlは以下の内容です。

```toml:fly.toml
app = APP_NAME
primary_region = 'nrt'

[build]

[http_service]
  internal_port = 8080
  force_https = true
  auto_stop_machines = false
  auto_start_machines = true
  min_machines_running = 0
  processes = ['app']

[[vm]]
  memory = '256mb'
  cpu_kind = 'shared'
  cpus = 1

```
【備忘録】nullポチェックのお話

前回投稿した記事で、NullPointerExceptionについて整理しました。
（https://qiita.com/saionji-kimochi/private/bdc1671568139352ff84）
↑の記事のまとめで、仕様上nullになる可能性がある場合はnullポチェックを行ってエラー落ちを回避する必要があると書きました。

今回はそのお話です。

<h2>やり方</h2>
一般的なやり方は、「if文で参照元がnullでなければ参照を行わない」という条件を加えることだと思います。

というか、それ以外知りません。あれば教えてください。

以下具体例です。

```java
int sample = null;

if(sample != null){
    System.out.printLn(sample * 3);
}
```
このように、中身がnullではない時にのみそれを参照するようにすればnullポで落ちることを回避できます。
また、String型は上記の方法に加えて、isEmpty()メソッドも使うことが出来ます。
```java
String sample = null;
if(sample.isEmpty()){
// 以下略
```
isEmpty()は、対象の文字列がnullもしくは空文字の場合にtrueを返します。
なので、空文字は許容したいなどの場合にはこれは使用できません。

続いて、オブジェクトをチェックする場合です。
```Java
Object obj = null;

if(!Objects.isNull(obj)){
// 以下略
```
もしくは
```java
Object obj = null;

if(Objects.nonNull(obj)){
// 以下略
```
このように、ObjectsクラスをのisNullメソッドやnonNullメソッドを使用することで実装できます。
ただし、バージョンの注意は必要みたいです。

<h2>まとめ</h2>
今回はnullポ落ちを回避するための、チェックのやり方を纏めました。

現在の案件では、Objects.nonNull()を使用することが多いです。

様々な状況に応じて使い分けられるようになればいいですね。
Git-1 基本概念

# 概要
**git**は任意時点のファイルの状態を保存して時系列順に記録することで、過去の状態に遡ったり変更履歴を差分で確認できたりと色々便利なツールである。このようにファイルを管理することを**バージョン管理**という。

最大の特徴・利点は同じプロジェクトに対して複数人の人が別々にファイルを編集していって、互いに邪魔することなく変更内容を確認・管理しながら最終的に統合できることである。

このような特徴・利点があり、ソフトウェア開発プロジェクトではファイルの変更管理のためにgitを使っておけば間違いない。

# 基本概念

**[1]** gitでバージョン管理する対象のプロジェクトのディレクトリを作成する
```
project
`-- [project files]
```
**[2]** .gitディレクトリを作る。通称**gitリポジトリ**である。
```
project
├── .git
`-- [project files]
```
**[3]** **gitリポジトリ**とは、任意の時点でのプロジェクト配下のファイル・ディレクトリの状態を保存し、そのバージョン管理に必要な情報も含めて全て記録していく領域である。従って、gitリポジトリをコピーすれば、どこでもプロジェクトの各バージョンの状態を復元することができる。

**[4]** 以降、プロジェクトは**ワーキングディレクトリ**という領域になる。

```
project(working directory)
├── .git
|   ↑ 変更後のファイルの状態を保存
`-- [project files] ※ファイル変更作業を行う
```

上図のように、ワーキングディレクトリ内で管理対象のファイル・ディレクトリの変更作業を行い、変更後の状態を（ゲームのセーブデータのように）gitリポジトリに記録する、というのがgitの基本的な作業の流れとなる。

**[5]** gitでは、ファイルを変更した後の状態をgitリポジトリに記録することを**commit（コミット）** と言う。コミットでは、ファイルの差分ではなく**その時のプロジェクトのファイル・ディレクトリ全体の状態**が保存される（**スナップショット**という）。そのため、gitリポジトリにコミット情報があればどこでもその時のプロジェクトの状態を復元することができる。

![a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985939/57ed7329-026d-8a2a-5041-b6ff216b899f.png)
Spring BootでWebアプリケーションを作ろう！

Spring Boot（Spring Data JPA）、PostgreSQLを使ってWebアプリケーションの作り方を解説しています

～ 新着情報一覧機能編～　
<iframe width="560" height="315" src="https://www.youtube.com/embed/b1U3wjquU98?si=NMg9QQptZc6sOu1R" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3680113/45c9454c-9cf0-cfaf-4ae7-ecf07c06bab4.png)


〜新着情報登録機能編〜
<iframe width="560" height="315" src="https://www.youtube.com/embed/L5-6Gyz_7rk?si=NMg9QQptZc6sOu1R" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3680113/21dd810c-462c-f13c-b425-68e8923cc4a1.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3680113/1a18c10b-1af4-9ca7-d3e6-9425c593e27f.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3680113/49e7b728-4175-1f64-2157-ec3dca87bfee.png)





テキストを使用して学びたい方は↓
https://zenn.dev/codek2/books/d95901da7e8cd1

X
[https://twitter.com/kunchan2_](https://twitter.com/kunchan2_)

Zenn 技術本
https://zenn.dev/codek2?tab=books

Udemyなど現場で即戦力になるための講座一覧
https://zenn.dev/codek2/articles/e9e44f3e0023fb

note
https://note.com/codek2
Jetpack composeを使って画面内を画像として保存する

# はじめに
今回はJetpack Composeを使って画面内を画像として保存する方法を紹介していきます。
少しいじるだけで任意の箇所を画像として保存することもできるようになるのでぜひお試しください
### 本文
早速ですがコードを書いていきます
```kotlin:Screen
suspend fun toBitmap(activity: Activity): suspend coroutine<Bitmap> {
    activity.window?.let {
        val view = it.decorView
        // 今回はRGBの各8ビット
        val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)

        // ソース,画面内で使用するRect,書き込み先のBitmap,完了時コールバック,コールバックを呼ぶスレッド用のHandler
        PixelCopy.request(
            window,
            Rect(0, 0, window.width, window.heght),
            bitmap,
            { result ->
                if (result == PixelCopy.SUCCESS) {
                    it.resume(bitmap)
                } else {
                    it.resumeWithException("ErrorMessage")
                }
            },
            Handler(Looper.getMainLooper()
            )
        )
    }
}
```
上記のメソッドをボタン押下時や画面の長押しなど任意のタイミングに呼び出すことで画像として保存することができるようになります
### 最後に
以前、自分で実装しようとしていて現状では難しいという結果になってしまったものを今ではどうかなと思い調べたのがきっかけで今回記事にしてみました。
どなたかのお役に立てれば幸いです
AndroidStudioローカルの変更履歴を表示する方法

AndroidStudioでローカルの変更履歴を表示する方法です。



ローカルの変更を見たいクラス名で右クリックをします。

![スクリーンショット 2024-04-21 17.31.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274615/333a9d13-14e9-d8ff-4c7a-41ae8a042bca.png)

画像のように選択します。

ローカルの変更を確認できるウィンドウが表示されます。
PythonのPrint() とend

printのendの初期値は\nですが、endに値を与えると与えた値で終わりになり、改行しなくなる
逆にprint()なら改行のみになる
temp = input('请输入一个整数:')
number = int(temp)
while number:
    i = number - 1
    while i:
        print(' ', end='')
        i = i - 1
    j = number
    while j:
        print('*', end = '')
        j = j - 1
    print()
    number = number - 1
自作したVScode の拡張機能の README にGIF動画を載せる方法

初めて VSCode の拡張機能を自作しましたが、README に GIF をのせるところでつまづいたので、シェアしておきます。<br>
これでできるはず。
## 手順

1. Windows キー+G などで録画する
2. 画面サイズや動画の長さをトリミングする（スマホでも OK）
3. ffmpeg で.mov から.gif に変換する

   - インストールがまだの場合は<a href="https://hontabisatori.com/memo-ffmpeg-windows11-install/">こちら</a>を参考にしてください
   - コマンドプロンプトで下のコマンドを実行する

   ```
   ffmpeg -i [インプットファイル名].mov -r 10 [アウトプットファイル名].gif
   ```

   - -i：インプットファイル
   - -r [数字]：フレームレート数

4. プロジェクトの任意の場所（assets フォルダの中など）に gif ファイルを配置する
5. VSCodeでGit に Commit、Push する
6. <a href="https://qiita.com/athlaliel/items/91adb2ac4bc229a2069a">こちら</a>の「画像の変換に issue を使う」を参考にして gif の URL を取得する
7. README.md の img タグのソースに URL をペーストする

```md
<img src="https://github.com/kazuki235/space-to-br/assets/74903972/b0d4224b-ad7d-42ea-b062-fa4ff4d5e183">
```

8. コマンドプロンプトで以下のコマンドを実行して.vsix ファイルを生成する

```
vsce package
```

9. これで.vsix ファイルができるので<a href="https://marketplace.visualstudio.com/manage/publishers">Visual Studio Marketplace</a>へアップロードできます

## つまづいたところ

### vsix ファイルを生成するところでのエラー

README.md の img タグに gif ファイルを相対パスで参照しようとしたら以下のエラーが出ました。

```md
<img src="assets/output.gif">
```

```
ERROR Couldn't detect the repository where this extension is published. The image will be broken in README.md. GitHub/GitLab repositories will be automatically detected. Otherwise, please provide the repository URL in package.json or use the --baseContentUrl and --baseImagesUrl options.
```

相対パスでの参照はダメらしく、GitHub の URL を参照する必要があるらしいです。<br>
オプションの使用はしなくてもよかったです。<br>

### Marketplace での表示不具合

GitHub では表示されるが拡張機能にアップロードすると表示に失敗しました。<br>
![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601978/e9b78dfc-8654-4711-a368-cd05d1e9f3d5.png)

### 解決方法

gif の URL は GitHub を参照しているので、リポジトリを public にする必要がありました。<a href="https://qiita.com/HyunwookPark/items/1d24972dd71612eb81c9"><br>
public への変更方法</a>

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601978/7723377c-5926-2a95-84b3-e86bfb58b3bb.png)

## 備考

どちらの書き方でもよい

```md
![gif](https://github.com/kazuki235/space-to-br/assets/74903972/b0d4224b-ad7d-42ea-b062-fa4ff4d5e183)
```

```md
<img src="https://github.com/kazuki235/space-to-br/assets/74903972/b0d4224b-ad7d-42ea-b062-fa4ff4d5e183">
```

## 自作した拡張機能

<a href="https://marketplace.visualstudio.com/items?itemName=kazuki235.space-to-br">https://marketplace.visualstudio.com/items?itemName=kazuki235.space-to-br</a>

![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601978/04f776ad-f7f7-dbce-2eaf-1010e0d2efad.gif)

markdown で半角スペースを 2 つ入力すると、```<br>```に変換されます。
Jupyter Notebookでpip installしたときの出力を消す方法

## 環境
- Google Colaboratory
- pip 23.1.2

## Jupyter Notebookでpip installしたときの出力を消す方法
Jupyter Notebookで`pip install`したときの出力を消す方法はいくつかあります。

### 方法1: `--quiet`オプション

```sh
!pip install --quiet (パッケージ名)
```

成功
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/d696de09-d3ab-1b19-586e-9d4e406fe3d1.png)

失敗
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/33c0c528-9f03-b4f7-7aa9-0ce7c9df7137.png)

pipの`--quiet`オプションを使うのが普通だと思います。

### 方法2: `> /dev/null`

```sh
!pip install (パッケージ名) > /dev/null
```

成功（なにも出力されない）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/af40f53f-2970-ec6d-8821-1651619fd27c.png)

失敗
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/89697cc1-20bb-8765-1c30-1cb4b045324a.png)

`/dev/null`に捨てると、成功時は何も出力されなくなります。

### 方法3: `IPython.display.clear_output`

```sh
!pip install (パッケージ名)

from IPython.display import clear_output
clear_output()
```

成功（なにも出力されない）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/23d5c0c9-65b5-74d7-ce9b-eeb78646971f.png)

失敗（なにも出力されない）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/42f740cc-9e4b-5c04-9efd-279a0dbef639.png)

エラーがあっても何も出力が残りません。
また、インストールの実行中はすべてのログが出力され、`clear_output()`が実行されたタイミングですべてが消えるという特徴があります。実行中は目視でログを見たいという場合には有効だと思います。
【デモあり】Cookie の SameSite 属性について

# SameSite 属性とは
- Cookie に指定可能な比較的新しい属性
- SameSite 属性を付与することで、CSRF 脆弱性に対していくらかの防御ができる

# SameSite 属性に指定できる 3 つの値
## None
- これまでの Cookie の挙動通り、全ての cross-site なリクエスト対して Cookie が付与される

## Strict
- same-site に対するリクエストにのみ Cookie が付与される
- Cookie を使いログイン認証をしているサイトに対して（セッション ID を Cookie として保持し、その有無によってセッションを維持する） cross-site なサイトに設置されたリンクから遷移した場合、リクエストに Cookie が付与されないため未ログイン状態となる
- CSRF攻撃 に対して有効だが、ユーザーは cross-site なサイトからログインした状態で遷移できないため、ユーザは再度ログイン処理や再読み込み処理が必要となる

## Lax
- `None` と `Strict` の間に位置するような指定
- cross-site を含むページ遷移のようなトップレベルのナビゲーションと、same-site のスクリプトや画像などのサブリソースに対するアクセスに対してのみ Cookie が付与される
つまり、ログインした状態で遷移できる
- ただし、POST メソッドのような unsafe な HTTP メソッドによる cross-site なトップレベルのナビゲーションに対して Cookie が付与されない
- POST メソッドなどを使ったフォームのサブミットに対する CSRF 攻撃に対して有効。

# デモ
ケースごとにサイト間のリクエストを試してみて、実行結果を見てみた方が早いと思うので、検証用アプリケーションを用意しました。

[サイトA](https://aquatic-frost-tumble.glitch.me/) ヘアクセスし、リンクから Cookie を取得し、特定のケースごとの実行結果をご自身の目で確認してみてください。

検証ツール > アプリケーションから正常に Cookie ブラウザにセットされている確認できます。

※起動に少々時間がかかるかもしれないです。

## a タグによる遷移

```
{
    cookie_1: "none",
    cookie_2: "lax"
}
```

## GET メソッドフォームによる遷移
```
{
    cookie_1: "none",
    cookie_2: "lax"
}
```

## Post メソッドフォームによる遷移
```
{
    cookie_1: "none"
}
```

## 検証してみて
Strict → クロスサイトなリクエストでは送信されない
Lax → GET によるクロスサイトなリクエストのみ送信される
None → 全てのクロスサイトなリクエストで送信される

ということが実際に検証できたのでよかったです。
Pythonでフォルダ内のmp4動画を全画面で連続再生

タイトルのままですね。
ドライブレコーダーの動画が1分単位のこま切れのmp4なので
見返す際に便利かなと思い作成しました。
あとスクリーンセーバーを想定している仕様です。
その理由は………大人の事情があったりなかったりします。

:::note warn
追記：OpenCVを使っているので動画は正常な再生速度では再生できません。
:::

# まず必要なライブラリをimport
```Python:
import cv2
import glob
import random
import tkinter as tk
```
シャッフル再生する必要がなければrandomは要りません。

# MP4ファイルが入っているフォルダを指定
```Python:
# MP4ファイルが格納されたディレクトリを指定
mp4dir = r"I:\24年4月"
```

# mp4ファイルの一覧を取得してリスト化
```Python:
# mp4ファイルの一覧を取得してリスト化
mp4list = []
files = glob.glob(mp4dir + "\*.mp4")
for file in files:
    mp4list.append(file)
mp4list_count = len(mp4list)

# mp4ファイルのリストをシャッフルして再生したい場合は下記1行のコメントアウトを解除
# random.shuffle(mp4list)
```

# リストの先頭のファイルを指定して開く
```Python:
# 最初のmp4ファイル番号を指定
i = 0
cap = cv2.VideoCapture(mp4list[i])

# mp4ファイルの読み込みに失敗した際の処理
if (cap.isOpened()== False):  
    print("ファイルオープンに失敗しました") 
```

# リサイズのためディスプレイサイズの取得
```Python:
# ディスプレイサイズの取得
TkRoot = tk.Tk()
display_width = TkRoot.winfo_screenwidth()
display_height = TkRoot.winfo_screenheight()
```

# 全画面再生なのでマウス操作で終了したい
本当はスクリーンセーバーらしくマウスを動かしたら終了したいのですが、
うまくいかなかったのでクリック等で終了する様にしています。
```Python:
def mouseEvents(event, x, y, flags, param):
    try:
        if event == cv2.EVENT_LBUTTONDOWN:
            # 左クリック時
            exit()
        elif event == cv2.EVENT_RBUTTONDOWN:
            # 右クリック時
            exit()
        elif event == cv2.EVENT_MBUTTONDOWN:
            # 中ボタンクリック時
            exit()
        elif event == cv2.EVENT_MOUSEWHEEL:
            # マウスホイール操作時
            exit()
    except Exception as e:
        print(e)
```

# メインの処理
```Python:
while(cap.isOpened()):
    ret, frame = cap.read()
    if ret == True:
        cv2.namedWindow('Video', cv2.WINDOW_NORMAL)
        cv2.setWindowProperty('Video', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
        frame = cv2.resize(frame, (display_width, display_height), interpolation=cv2.INTER_LINEAR)
        cv2.imshow('Video', frame)
        cv2.setMouseCallback('Video', mouseEvents)

        # ESCキーでも終了する
        if cv2.waitKey(25) & 0xFF == 27:
            break
    
    else:
        # ファイルの再生が終わったら次のファイルを再生する処理
        i += 1
        if i <= mp4list_count - 1:
            # 次のファイルを再生
            cap = cv2.VideoCapture(mp4list[i])
            cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
            continue
        else:
            # リストの末尾まで来ていたら、最初のMP4ファイルに戻って再生
            i = 0
            cap = cv2.VideoCapture(mp4list[i])
            cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
            continue
cap.release()
cv2.destroyAllWindows()
```

# 作成にあたり参考にさせて頂いた記事

https://laboratory.kazuuu.net/play-video-in-python-using-opencv/

https://weblabo.oscasierra.net/python/python3-beginning-file-list.html

https://shuzo-kino.hateblo.jp/entry/2023/01/23/235508

https://qiita.com/chez_sugi/items/76a839244449b716c587

http://raspuser.blog.fc2.com/blog-entry-163.html
[解決・win] npm install永遠に終わらない件

# はじめに
普段持ち歩きmacで開発している時には起こらなかったのですが、自宅win環境でプロジェクトをクローンして作業を始めたところ、npm install全然終わらないし、待たされた挙句エラーを吐かれるという事象に遭遇したので、少しでも被害者減ればと備忘録的に残しておきます。
プロジェクトの中身👇
- Astrojsを使用したプロジェクト
- 中にはReact形式のコンポーネント

# サクッと解決策
以下を実行したら直りました。このコマンドの詳しい話は[先人のありがたい記事](https://qiita.com/ASLA/items/527f5c47ae60bc51cc9a)があるのでそちらを参照頂ければ。
```
npm config set strict-ssl false
```

# 紆余曲折あった話
最初は`package-lock.json`が存在する状態で`npm install`してこけたので、もちろん消して再チャレンジしてみるわけですよ。そうすると、
```
sill idealTree buildDeps
```
の状態で停止しました。まあ3分くらいは待ったんですが、待ちきれなくなって、検索していろんな対処法を試してみることにしました。(ここで大人しく待っていれば解決も早かったのに！)
nodeアンインストールしたり、最新バージョンにしたり、逆にバージョン下げるといいかもよ！(どっちやねん)みたいな記事を漂流しながら、とりまnodeを最新バージョンに上げて再起動しました。何も変わりません。
他にもnpmのキャッシュ削除するといいよ！みたいな記事をみて、`npm cache clean --force`するけどなんも変わらんです。
とまあいろいろあり、解決策万事尽きたところで、`npm install`を行い、前述の停止状態で永遠と待ちました。switchで遊べる時間がありました。
最終的に、
```
code UNABLE_TO_VERIFY_LEAF_SIGNATURE
npm ERR! errno UNABLE_TO_VERIFY_LEAF_SIGNATURE
```
というエラーが表示されたので、検索で一発K.O.
みなさんも慌てず、エラー文出るまで待ちましょう...
(持ち歩きのmacではこんなエラー出なかったのになぁ あさを)
【Spring Boot】REST APIでpatchForObject（PATCH）使用時の注意点【RestTemplate】

## はじめに
APIについて学習中です。
前回REST API作成についてと、その利用について記事を書きました。
[【Spring Boot + MyBatis】はじめてREST APIを作成してみる](https://qiita.com/1noseA/items/333bd451d928d5bed911)
[【Spring Boot】作成したREST APIを使いたい【RestTemplate】](https://qiita.com/1noseA/items/0f01100134510ae42194)

更新処理をPATCHで実装した際にうまくいかないことがあったので、今回記事にまとめます。

## PUTとPATCHの違い
設計は参考そのままPUTにしていたのですが、実装時に設計を確認するのを失念しており、以下の記事を見てPATCHが適切なのではないかと考えました。

[POST, PUT, PATCHの違い](https://zenn.dev/sgtkuc1118/articles/c73587c674a4a2)

> ### PUT
> - 既存リソースの新しいものに置き換える
> - 新しいリソースの中に、既存リソースにある要素がない場合、その要素は削除される
> 
> ### PATCH
> - 既存リソースに新しいものを付け足す
> - 新しいリソースの中に、既存リソースにある要素がない場合でもその要素は削除されない

## RestTemplateのpatchForObjectメソッド
取得処理（GET）はgetForObject、登録処理（POST）はpostForObjectを使ったので、更新処理はpatchForObjectを使用することにしました。

### patchForObjectメソッド
- 第一引数：送信先のURL
- 第二引数：リクエスト情報（ヘッダーやボディ等）
- 第三引数：レスポンスボディの型
- 第四引数：URLのパラメータ

以下記事を見て、そのままでは使えないことを確認しました。
- [RestTemplateに追加されたpatchForObjectはデフォ実装(HttpURLConnection使うやつ)だと実質使えません](https://qiita.com/kazuki43zoo/items/ec5dd8d8ef0aab999b19)
- [SpringBoot RestTemplateでPATCHを使う場合の注意メモ](https://sugaryo1224.hatenablog.com/entry/2019/01/26/154733)

しかし、この通りやってもできませんでした。

エラーメッセージから推測できたのですが、Spring Framework 6.0よりApache HttpClient5になっていました。
画面側プロジェクトのpom.xmlに以下が必要になります。
```pom.xml
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5 -->
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.3.1</version>
</dependency>
```
### 画面側プロジェクト
```ManualsController.java
// マニュアルAPI URL（パラメータあり）
private static final String API_URL_PARAM = "http://localhost:8081/manual-api/{id}";

// 更新処理
@Transactional
@PostMapping("/manual/update")
public String update(int manualId,
                     @ModelAttribute @Validated ManualsForm manualsForm,
                     BindingResult result,
                     Model model) {
    // 入力エラーチェック
    if (result.hasErrors()) {
        return edit(manualId, manualsForm, model);
    }

    // FormをEntityにセットする処理は省略
    Manuals manual = setEntity(manualsForm);
    // マニュアルID
    manual.setManualId(manualId);
    
    // ★更新
    Manuals manualInfo = restTemplate.patchForObject(API_URL_PARAM, manual, Manuals.class, manualId);

    return "redirect:/manual/list";
}
```
### API側プロジェクト
```ManualsController.java
@PatchMapping("/manual-api/{manualId}")
public ResponseEntity<Manuals> update(@PathVariable int manualId, @RequestBody Manuals manual) {
    // 更新
    manualsMapper.update(manual);
    return ResponseEntity.ok(manual);
}
```

## さいごに
結果として当初の設計通りPUTに修正しましたが、PATCHでも実装でき勉強になりました。
【PHP初心者向け】for文の中で実行した関数内の処理によってループを終了させる

※プログラミング初学者なので内容に誤りを含む場合があります。

初歩的な知識であると思われるが、検索しても意外と見つけづらかったので残しておく。

例題として、1~10までの数字を生成する処理を無限ループさせ、1が2回連続出ればループを終了させるコードを考えてみる。

```error.php
for ($i = 0;; $i++) {
    echo $i + 1 . '回目の挑戦です。' . PHP_EOL;
    $num1 = rand(1, 10);
    if ($num1 === 1) {
        echo '1回目の1です！' . PHP_EOL;
        stop();
    } else {
        echo '1回目は1が出ませんでした。続きます。' . PHP_EOL;
    }
}

function stop()
{
    $num2 = rand(1, 10);
    if ($num2 === 1) {
        echo '2回目の1です。お見事。終了します。' . PHP_EOL;
        break;
    } else {
        echo '2回目は1が出ませんでした。惜しい。続きます。' . PHP_EOL;
    }
}
```
上記のようにすると
`PHP Fatal error:  'break' not in the 'loop' or 'switch' context`

このように怒られてしまうのでどうにかする必要がある。正解は以下のようにif文の条件の中で関数を実行し、真偽値を返す方法である。

```correct.php
for ($i = 0;; $i++) {
    echo $i + 1 . '回目の挑戦です。' . PHP_EOL;
    $num1 = rand(1, 10);
    if ($num1 === 1) {
        echo '1回目の1です！' . PHP_EOL;
        if (stop()) {
            break;
        }
    } else {
        echo '1回目は1が出ませんでした。続きます。' . PHP_EOL;
    }
}

function stop(): bool
{
    $num2 = rand(1, 10);
    if ($num2 === 1) {
        echo '2回目の1です。お見事。終了します。' . PHP_EOL;
        return true;
    } else {
        echo '2回目は1が出ませんでした。惜しい。続きます。' . PHP_EOL;
        return false;
    }
}
```

やってみれば全然難しい処理ではないし、in_arrayなど真偽値を返す関数があることを考えれば当然といえば当然なのだが、自分で作るとなるとちょっと苦労したのでメモ。
[2]Next.jsチュートリアルを進められたのでやってみる(2)cssスタイル

# はじめに

今回の記事では、Next.jsチュートリアルの第2章「CSS Styling」を進めていきます。

https://nextjs.org/learn/dashboard-app/css-styling

#### 前回の記事

https://qiita.com/2201009-kikukawa/items/4401bfe527f486e2181c

## chapter2:cssスタイル

このchapterでは以下の内容について学ぶことができます。

- グローバルcssファイルをアプリケーションに追加する方法
- 2 つの異なるスタイル設定方法: Tailwind モジュールと CSS モジュール。
- 条件に応じてクラス名を追加する方法 clsx ユーティリティ パッケージの使用

## グローバルcssの追加

グローバルcssファイルは、CSS リセットルールや共通のスタイルなどをアプリケーション内のすべてのルートに追加できます。これにより、共通のレイアウトのcssを設定することができます。

### Tailwind 

Tailwind は CSS フレームワークであり、クラス名にcssスタイルを設定し、それらのクラス名を各要素で使用することでスタイルを適用します。create-next-appを使用すると、新しいプロジェクトを作成する際にTailwindを使うかどうか尋ねられ、Yesと答えると自動的にTailwindを使える環境が構築されます。

### cssモジュール

![スクリーンショット 2024-04-21 121204.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619847/20c31e7b-37ba-5a99-0f2b-a0ef44cdee6b.png)
とのことですが、今回の学習目的であるシステムの開発にはcssにTailwind を使うようなので今回は飛ばします。

## clsx

条件に応じてcssスタイルを適用する際に使用するライブラリで、条件に応じてクラス名を簡単に変更できます。

```jsx
import clsx from 'clsx';

export default function InvoiceStatus({ status }: { status: string }) {
  return (
    <span
      className={clsx(
        'inline-flex items-center rounded-full px-2 py-1 text-sm',
        {
          'bg-gray-100 text-gray-500': status === 'pending',
          'bg-green-500 text-white': status === 'paid',
        },
      )}
    >
    // ...
  )}
```
## おわりに
今回の記事ではcssスタイルについて学習しました。Tailwindが中々に便利そうでNext.jsの学習だけではなく、Tailwindについても学習してみたいと思いました。

## 次の記事

https://qiita.com/2201009-kikukawa/items/5e5f5d98b04cdfdab81b
[1]Next.jsチュートリアルを進められたのでやってみる(1)環境構築 

## はじめに
今までフレームワークはVue.jsしか使ってこなかった自分ですが、現在開発中のシステムでNext.jsを使うことになり参考資料を探していたところ、メンバーからNextの公式チュートリアルを進められたので。チュートリアルを進めてNext.jsの理解を深めようとやってみることにしました。
今後、自分で見返す時などに便利だと思い記事を書いておこうと思います。

## chapter1:環境構築
https://nextjs.org/learn/dashboard-app/getting-started

既に用意されているひな形をダウンロードして１つずつ課題を進めていくって方式のようです。

## おわりに
[1]では既に用意されているひな形をダウンロードするだけだったので、そんなに難しい内容ではなかったと思います。次回からはコーティングなので頑張っていきましょう！

## 次の記事

https://qiita.com/2201009-kikukawa/items/c15429dadc555ae2b7a9
【はじめに】かわいい癒し系ロボットを作りたい…

# 自己紹介
はじめまして。社会人２年目のnami_fumiです！
主にpythonでAI少し勉強したり、アプリ作ったりしてます！
業務ではbigqueryを使って、データの抽出や集計、可視化などをチームで協力して行っています。

# はじめに
いきなりですが、**こんなかわいい・癒やしロボットを作りたい！!** という意思が芽生えた今月。
この可愛い・癒し系ロボットを作るための軌跡を記事にしていこうと思っています！

## 具体的なイメージ
具体的なイメージは、下記画像のような見た目のロボットです。

その外観のイメージを持ちつつ、人の笑顔を認識して目の部分が笑う…
「ただいま」と言ったら「おかえり」と返してくれる…
最終的には、撫でたらなんか反応する（触覚の追加）…
そんなのを作りたい！！

[パナソニック NICOBO （ストーングレー）](https://ec-plus.panasonic.jp/store/ap/storeaez/a2A/ProductDetail?HB=RW-NC1-H&utm_source=google&utm_medium=banner&utm_campaign=nicobo_OPT_D_G_ec-plus-pla-nicobo&utm_content=nicobo_OPT_D_G_ec-plus-pla-nicobo_pla&utm_term=RW-NC1-H_n_n_n_000273&saf_src=google_g&saf_pt=&saf_kw=&saf_dv=&saf_cam=20374342962&saf_grp=153927452120&saf_ad=666048300622&account_id=6647830452&saf_acc=6647830452&gad_source=1)

![スクリーンショット 2024-04-21 15.39.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3769985/cbbf1b3f-1d25-405e-34e8-964245498db4.png)

# 検討している機能
バージョン１〜３まで段階的に行っていければと思います。
- バージョン１
    - カメラから笑顔の識別
    -  笑顔を識別したら、目の役割をするもの（電光掲示板やさっきの癒し系ロボの目となるもの）
- バージョン２
    - 音声の認識：「ただいま」と言ったら「おかえり」と返したり、「今日の天気は？」と聞いたら、今日の天気予報が出てくる
- バージョン３
    - 触覚の追加：撫でたら、笑顔になってくれる

上記のバージョン２や３は、社内や同期とかに相談してもっと色々と追加される予定です！

# 構築環境
私は技術選定というよりも、とりあえずこれで作れそうっていう情報があったり、これだったら実現できそう！で動いてしまうので、あまり参考にしないでください…

- 構築するツール
    - Raspberry Pi
- 言語
    - python
- カメラ
    -  ラズパイにつなげるカメラ
-  目の役割となるもの
    -  現時点では電光掲示板で対応(その方が可愛げあるかな・・・と思いました)
    -  のちのち、癒し系ロボの目
-  パソコン
    -  Mac

次回は最初の難関、Macを使ってラズパイの環境構築をします！