OmniAICreator/Qiita-1.07M huggingface dataset:
Google Assitant V2 対策（IFTTTの変数入力の実現）

# はじめに
●2022年9月からGoogle Assitantが改悪され、変数入力ができなくなりました。いままで、
[WSLのUbuntuでyoutube音楽をGoogleHomeで操作する](https://qiita.com/kobbeko/items/db08ac856d9051fd4812)
などの複数のレシピを作成していたので、対策を考えました。

# 方法
1)変数を保存する場所としてGoogle keepを使用する
keepのタイトルのメモは「タスク」としました。
2)5秒周期でkeepのメモを監視し、変更があったら、task.jsを起動する。起動オプションで変数を渡す。
認証は16桁のアプリパスワードを使用する。
当初、ラズパイで構築しようとしたが、認証エラーになるので、WSLのUbuntu環境で実行しています。
3)task.jsで起動オプションをコマンド（例：音楽）と変数（例：あいみょん）に分割し、コマンドに対応したfirebaseに
変数を書き込むことで既存のレシピの代わりに、使用できます。（既存のレシピは不要）

# 使用方法
自分：OK グーグル タスクリストに追加
自分：OK グーグル タスク　（ルーティンでタスク＝タスクリストに追加と定義した場合）
GoogleHome：追加するものを教えて下さい
自分：音楽　あいみょん

# Google keepを監視するプログラム(gkeeper.py)

``` javascript
import gkeepapi
import os
import requests
import time
import subprocess

WISHLIST_NOTE_NAME="タスク"

# dump_all_list print all list
def dump_all_list(target):
    for i in target:
        print(i.text)
        # リストが空でなければtask.jsを起動
        if i.text != "":
            say = i.text.replace(' ','')  #文字列の置換(半角空白を削除）
            command = 'node /home/ユーザ名/task.js ' + say
            subprocess.run(command, shell=True)
            print(command)
        # リストアイテムを削除
        i.delete()

# getnotes checks last and current wish list and returns True if any updates are there.
def getnotes():
    keep = gkeepapi.Keep()

    # 16桁のアプリパスワードを使用
    success = keep.login("XXXXXX@gmail.com", "XXXXXXXXXXXXXXXX")

    gnotes = keep.all()
    for i in gnotes:
        if(i.title == WISHLIST_NOTE_NAME):
            # keep.sync()
            # print("Current list")
            dump_all_list(i.unchecked)
            keep.sync()

            break

if __name__ == "__main__":
    while True:
        getnotes()
        time.sleep(5)
```

# firebaseに変数を書き込むプログラム(task.js)

```JavaScript
var firebase = require("firebase");
var iconv = require('iconv-lite');
var value1 ;

//firebase config
var config = {
    apiKey: "XXXXXXXX",
    authDomain: "XXXXXXXX.firebaseapp.com",
    databaseURL: "https://XXXXXXXX.firebaseio.com",
    projectId: "XXXXXXXX",
    storageBucket: "XXXXXXXX.appspot.com",
    messagingSenderId: "XXXXXXXX"

};
firebase.initializeApp(config);

value = process.argv[2]
console.log(value)

//コマンドと変数を分離
value1 = value.substring(0,2);
value2 = value.substring(2,);

//firebaseに書き込み
if(value1 === "音楽") {
  var commentsRef = firebase.database().ref('googlehome5');
  commentsRef.set({ body: value2 });
} else if(value1 === "開け" || value1 === "開く") {
  var commentsRef = firebase.database().ref('googlehome8');
  commentsRef.set({ body: value2 });
} else if(value1 === "PC" || value1 === "ＰＣ") {
  var commentsRef = firebase.database().ref('googlehome1');
  commentsRef.set({ body: value2 });
}

function exit(){
  process.exit();
  }
setTimeout(exit,1000);
```
# 参考にした記事
[Google AssitantでIFTTTの変数入力を再現する](https://qiita.com/airosB/items/40432a06e76b1eae8f91)
[GoogleKeepの買い物リストをLINE通知する](https://zenn.dev/zono_dev/articles/28bac7a0a26fe5)
M5Stack Core2 for AWSを工場出荷状態に戻す

手順はWindows 10の場合です。

全てのツールのダウンロードページは下記です。
https://docs.m5stack.com/en/download

# USBドライバーをインストールする

M5Stack Core2 for AWSのデバイスドライバをインストールします。CP2104チップが使われているので、対応するUSBドライバをダウンロードします。

https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/drivers/CP210x_VCP_Windows.zip

認識されない場合は、[ダウンロードページ](https://docs.m5stack.com/en/download)のUSB DRIVER & OPEN SOURCE LIBRARYから、CH9202のドライバも入れてみましょう。

# M5Burnerをインストールする

https://m5burner.m5stack.com/app/M5Burner-v3-beta-win-x64.zip

# COMポートの番号を確認する

M5Stack Core2 for AWSを接続して、デバイスマネージャーでCOMポート番号を調べます。下記のように、今回はCOM3を使っているようです。
![comport.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157684/46eaf19e-b602-0cae-f078-a91b301ca0f7.png)

# M5Burnerを起動して、ファームウェアイメージを選ぶ
![firmware.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157684/12350875-d8ac-bbf6-3a28-07c6ee40a442.png)
Core2_For_AWSを選択してDownloadを押します。

![burn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157684/a6de18f3-c52f-422d-abd4-895b89bd1bde.png)
そののちBurnを押すと、COMポート選択画面が出るので、デバイスマネージャーで選択したCOMポートを選んでStartすれば、書き込みが始まります。
お母さんでもわかる要素の横並びについて

# 使い分け
横並びを実行する際、今までなんとなく「flex」を使ってきました。
しかし他にも方法（inline-block,float）があるので、結局何が適正なのかをはっきりさせるためにこの記事を書くことにしました！

## それぞれの特徴

### float
横並びにしたい要素全てにfloatを指定する。
（クラス名を統一すれば解決できる）
一部にfloatを指定すると、その要素が浮いてしまう・・・。

↓通常ver.

```index.html
<div class="image">
    <img src="images/IMG_9345.PNG" alt="">
</div>
<p>テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</p>
```
```style.css
img{
  width: 100%;
}

.image{
  width: 200px;
  height: auto;
}
```
<img width="400" alt="スクリーンショット 2022-09-10 8.15.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782070/ae6c3139-0971-5e15-8ec8-d55abde96b27.png">

浮いちゃうver.

```index.html
<div class="image">
    <img src="images/IMG_9345.PNG" alt="">
</div>
<p>テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</p>
```
```style.css
img{
  width: 100%;
}

.image{
  width: 200px;
  height: auto;
  float:left;/*こいつを追加*/
}
```
<img width="400" alt="スクリーンショット 2022-09-10 8.21.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2782070/c9b5b647-7f97-b875-28d4-0d41a932b6d6.png">

画像の後ろに文字が回り込む！
あえてこの形を表現したい場合は「float」を使えばいいが、ただ横並びにするだけなら「float」じゃなくていい。
なぜなら浮いている要素を元に戻すには、cssが3行以上増えてしまうから。

### inline-block
inline-blockはblockとinleneのいいとこ取りの要素。
| |block |inline  |  inline-block |
|:---:|:---:|:---:|:---:|
|width| ○ | × |  ○ |
|height|○|×|○|
|margin| ○ | × |  ○ |
|padding| ○ | △(上下のpaddingが他の要素とかぶってしまう) | ○  |
|並び| 縦 | 横 |  横 |

inline-blockを使って要素を横並びにしたい場合は、要素全てに「inline-block」を指定しなければならない。
(クラス名を統一すれば解決できる)

### flex
横並びにしたい親要素に「display:flex」を指定するだけ。
最大のメリットは、要素配置のアレンジをしやすくなること。
↓一部の例

| |使い方 |
|:---:|:---:|
|flex-direction| 子要素の並ぶ向き |
|flex-wrap|子要素の折り返し|
|flex-flow| flex-directionとflex-wrapをまとめて指定 |
|justify-content| 水平方向の揃え |
|align-items| 垂直方向の揃え | 

## 結局何がいいの？
結論からすると「flex」がおすすめ！
要素の配置をアレンジしやすいのが大きいメリットです！！！
(比較的新しい方のcssプロパティだが、現在はほぼ全てのサイトで使用でき憎きIEがサポート終わったことも一つの要因です)
Webdriver managerを使用していて、Google Chrome のバージョンをサポートしていないとメッセージが出た場合の対処方法

## ■記録した理由
>通常Google Chrome のバージョンは<font color='Red'>ChromeDriverManager</font>（「from webdriver_manager.chrome import ChromeDriverManager 」）をインポートしていれば自動で修正されるが、下記のような表示がTerminal上に出て、たまに修正されない場合がある。
>その場合は、手動で直す必要があるので記録した。

![スクリーンショット 2022-09-10 8.32.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634708/ff245f52-29d7-15c6-afe2-63cecdc2ef44.png)
## ■対処方法
>①まず、chromedriverのホームページを開く
urlはここ https://pypi.org/project/chromedriver-binary/#history

https://pypi.org/project/chromedriver-binary/#history

>②ターミナルに表示されたバージョンを探しクリック
Terminalに表示された<font color='Red'>「Current google-chrome version is  
>XXX.X.XXXX」</font>と<font color='green'>***同じかもしくは近い***</font>バージョンをクリックする。

![スクリーンショット 2022-09-10 7.58.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634708/eb11751c-1277-7f8c-f4d3-bc004b124d0f.png)

>③赤枠で囲まれた部分（下記の例：<font color='Red'>「pip install chromedriver->binary==105.0.5195.52.0」</font>）をTerminalに貼り付け実行

![スクリーンショット 2022-09-10 7.58.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634708/4b1c32c5-bb47-00d8-3f62-ac9110394b18.png)

>④コレが出たら終了です！！
（ターミナル画面）
![スクリーンショット 2022-09-10 8.34.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634708/67f194a8-15a4-faa7-e2c5-ce879a768e12.png)
ISUCONのGo利用割合

Goの紹介を書くために思い立ってグラフ化してみました。情報源はこちらです。

https://isucon.net/

2014ぐらいまでは、利用言語は自由記述と本文に書いてあったり、無記名のチームもあったようなので参考値です。2013では予選はGoで突破したけど本戦は別の言語で出たチームがあったみたい（2チーム突破で8%だったけど決勝は4%になってた)。

<iframe width="774" height="371" seamless frameborder="0" scrolling="no" src="https://docs.google.com/spreadsheets/d/e/2PACX-1vSEk5DAfYKGg3kyiy8qTz4bgwWwxC9FREC5rtnuWVnLfVelGlK-QHTXFbYJoh9s5IBxOmylUQLSTznR/pubchart?oid=952210900&amp;format=interactive"></iframe>

Goの開発効率と実行効率を証明する証拠として、JTBCで上司を説得するような場合に便利にご利用いただけるかと思います。

ソースの生データも貼っておきます。

```tsv
	予選	予選突破
ISUCON3(2013)	4.23	8
ISUCON4(2014)	16.1	24.1
ISUCON5(2015)	14.2	22.2
ISUCON6(2016)	19.2	42.3
ISUCON7(2017)	28.4	53.3
ISUCON8(2018)	40	63.3
ISUCON9(2019)	49.9	73.3
ISUCON10(2020)	59	84.8
ISUCON11(2021)	62.2	90
ISUCON12(2022)	70.8	86.7
```
エレキ素人が何か考える（その11）：7セグメントLED

# 7セグメントLEDをArduinoから操作
（無理やり「エレキ」にこじつけ感がある。）
秋月電子で購入した[「7セグメントLED A-551SRD」](https://akizukidenshi.com/catalog/g/gI-00639/)をArduinoから操作するのであるが、少々ハマったので記録。

## データシート
データシートは[ここ](https://akizukidenshi.com/download/ds/paralight/a-551srd.pdf)にあるが、ピン配置記載が十分でない。
![a-551srd pin assignment.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/4acd288a-8ffe-4a1d-5931-737671b03e7e.png)
PIN1しか記載がない（最初はこれにも気が付かなかった）。トライ＆エラーした結果、正しいピン配置は下記のとおり。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/812513e8-4635-b435-63fd-98ff45db0164.png)
個人的には、アルファベット表記の代わりに、ピン番号を各LEDに割り付けたほうがわかりやすいと感じる。

## やりたいこと
キーボード（シリアル）入力した数値を表示するのみ。

### ソースコード
ArduinoのGPIOピンを、適当に各LEDに割り当てる。この7セグメントLEDはアノードコモンのため、LOWで点灯となるため、それを考慮し、0から9までのLEDテーブルを作成する。あとは入力値により、それぞれのLEDをdigitalWrite()するのみ。なお、エラー入力は未考慮。

```c++
#define A7_PIN  6
#define B6_PIN  9
#define C4_PIN  8
#define D2_PIN  2
#define E1_PIN  3
#define F9_PIN  5
#define G10_PIN 4

uint8_t ary[] = {A7_PIN, B6_PIN, C4_PIN, D2_PIN, E1_PIN, F9_PIN, G10_PIN};
int b_ary[] = {HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH};

void show_num(uint8_t num) {
  int disp_ary[][sizeof(ary)/sizeof(uint8_t)] = {
     // A   B   C   D   E   F   G
    {LOW, LOW, LOW, LOW, LOW, LOW, HIGH},     // 0 
    {HIGH, LOW, LOW, HIGH, HIGH, HIGH, HIGH}, // 1
    {LOW, LOW, HIGH, LOW, LOW, HIGH, LOW},    // 2
    {LOW, LOW, LOW, LOW, HIGH, HIGH, LOW},    // 3
    {HIGH, LOW, LOW, HIGH, HIGH, LOW, LOW},   // 4
    {LOW, HIGH, LOW, LOW, HIGH, LOW, LOW},    // 5
    {HIGH, HIGH, LOW, LOW, LOW, LOW, LOW},    // 6
    {LOW, LOW, LOW, HIGH, HIGH, HIGH, HIGH},  // 7
    {LOW, LOW, LOW, LOW, LOW, LOW, LOW},      // 8
    {LOW, LOW, LOW, HIGH, HIGH, LOW, LOW}     // 9
  };
  for (int i = 0; i < sizeof(ary)/sizeof(uint8_t); i++) {
    digitalWrite(ary[i], disp_ary[num][i]);
  }
}

void setup() {
  Serial.begin(9600);
  for (int i = 0; i < sizeof(ary)/sizeof(uint8_t); i++) {
    pinMode(ary[i], OUTPUT);
    digitalWrite(ary[i], b_ary[i]);
  }
}

void loop() {
  if (Serial.available() > 0) {
    String data = Serial.readStringUntil('\n');
    uint8_t val = data.toInt();
    // Serial.println(val);
    show_num(val);
  }
}

```

## 実験（＆配線状況）
![IMG_20220628_203335のコピー.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/d328c567-9e9b-f385-36e2-041f1fecfb2b.jpeg)
成功。
なお、抵抗が8個以上見えるが、同一値の抵抗が見つからず、複数の抵抗を並列接続して、できるだけ7箇所の抵抗値を近づけているため。

EOF
LPC812MAXのシステムクロックを見る


x mbed2リビジョン144
x リビジョンを変更できる人むけ


目的
動作クロックの確認


![o_con737.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/840352/f7538513-483c-1870-0d7f-5364d05359fe.jpeg)



参考


https://os.mbed.com/users/okini3939/notebook/memo/


```c++


//SER_SystemCoreClock_812_1

#include "mbed.h"

//         TX    RX
Serial pc(P0_4, P0_0); //812

//メイン関数
int main()
{

    //無限ループ
    while(1) {

        //周波数の表示
        pc.printf("%d Hz\r\n", SystemCoreClock);

        //1秒の待ち
        wait_ms(1000);

    } //while

} //main



```


![o_con833.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/840352/ac257ba5-02cd-05b5-f041-af2e467cb49d.jpeg)


-19-
LPC812MAXと秋月シリアル7セグで3分タイマー SPI



x mbed2リビジョン144
x リビジョンの変更ができる人むけ

x 赤色７セグメントＬＥＤシリアルドライバモジュール　完成品
x OSL10564-74HC595-R M-12642


![o_con735.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/840352/00f6a661-09a6-3b3a-c670-d90c91b44fa0.jpeg)



![o_con736.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/840352/7fcbde4c-dfad-9c0a-714f-8fd0abe2f359.jpeg)




```c++


//spi_7seg_3min_812_1

#include "mbed.h"

//*******   ******   *****  *****
//     *         *   *      *
//    *         *    *****  *  ***
//  *       ****     *      *    *
//  *       *        *      *    *
//  *        *****   *****  *****

//SPIとGPIOの設定
SPI spi( P0_14, P0_15, P0_12);   // mosi, miso, sclk //812
DigitalOut en( P0_13 ); //812

//7セグ表示パターン
char seg[32] = {
    0x00,  //0 @ -> ' '
    0x77,  //1 A  o
    0x7c,  //2 B     　combined use "6"
    0x39,  //3 C
    0x5e,  //4 D
    0x79,  //5 E  o
    0x71,  //6 F
    0x3d,  //7 G

    0x76,  //8 H  o
    0x06,  //9 I    　combined use "1"
    0x1e,  //10 J
    0x75,  //11 K
    0x38,  //12 L  o
    0x15,  //13 M
    0x37,  //14 N  o
    0x3f,  //15 O  o　combined use "0"

    0x73,   //16 P
    0x67,   //17 Q　combined use "9"
    0x50,   //18 R
    0x6d,   //19 S　combined use "5"
    0x78,   //20 T
    0x3e,   //21 U
    0x1c,   //22 V
    0x2a,   //23 W  o
    0x64,   //24 X

    0x6e,   //25 Y
    0x5b,   //26 Z　combined use "2"
    0x4f,   //27 [  --> "3"
    0x66,   //28 \  --> "4"
    0x27,   //29 ]  --> "7"
    0x7f,   //26 ^  --> "8"
    0x08    //31 _
};

//60秒の待ち
int ii; //ループカウンター
#define s60()   for(ii=0;ii<60;ii++)wait_ms(1000);

//メイン関数
int main()
{

    //SPIの設定
    spi.format(8,0);
    spi.frequency(1000000);

    //ラッチをLOWにする。
    en=0;

    //0分
    spi.write(0);
    spi.write(0);
    spi.write(0);
    spi.write(seg['O'-64]);
    en=1;
    en=0;
    s60();

    //1分
    spi.write(0);
    spi.write(0);
    spi.write(0);
    spi.write(seg['I'-64]);
    en=1;
    en=0;
    s60();

    //2分
    spi.write(0);
    spi.write(0);
    spi.write(0);
    spi.write(seg['Z'-64]);
    en=1;
    en=0;
    s60();

    //3分
    spi.write(0);
    spi.write(0);
    spi.write(0);
    spi.write(seg['['-64]);
    en=1;
    en=0;

    while(1) {} //無限ループ

}//main



```


![o_con833.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/840352/5a85d505-6084-9a32-f959-5afaedff9fd8.jpeg)


-18-
Python,Ruby,PHP,Java,JavaScript,Perlのwhile文の比較 | プログラミング言語比較


<a href="https://qiita.com/yuki_kimoto/items/f9c2454e7f9130ddb003">Qiita > プログラミング言語の比較</a>

プログラミング言語Python,Ruby,PHP,Java,JavaScript,Perlのwhile文の比較

# Python

https://qiita.com/Morio/items/10d33f6dbb0845ae5038

# Ruby

https://qiita.com/Hashimoto-Noriaki/items/f406bcfb00e79e3937d0

# PHP

https://qiita.com/miriwo/items/2c2aa77d5de66c074ec5

# Java

https://qiita.com/takahirocook/items/05bfe37bb4ba64f3a78e

# JavaScript

https://qiita.com/kaz7991/items/7f98cc17c4df9ab6a009

# Perl

https://perlzemi.com/blog/20100123126425.html
RustのschemarsでDateTimeでエラーになる場合の対処

# 対処方法

chronoパッケージのchronoフィーチャーフラグを有効にします。

```sh:Cargo.toml
[dependencies]
schemars = { version = "0.8", features = ["chrono"] }
```

# エラー全文

```
error[E0277]: the trait bound `DateTime<Utc>: JsonSchema` is not satisfied
  --> src/main.rs:22:48
   |
22 | #[derive(Deserialize, Serialize, Clone, Debug, JsonSchema)]
   |                                                ^^^^^^^^^^ the trait `JsonSchema` is not implemented for `DateTime<Utc>`
```
GitHub ActionsでTerraformを実行中に「Failed to unlock state」となった場合

# はじめに
GitHub Actionsにて、Terraformを実行中に下記のエラーが出て、実行がロックされてしまったので、その解決法を記載します。

```sh
Failed to unlock state: lock id "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" does not match existing lock
Lock Info:
  ID:        XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
  Path:      akane-dev-terraform-state/terraform.tfstate
  Operation: OperationTypePlan
  Who:       runner@akane
  Version:   1.1.6
  Created:   2022-09-09 07:52:09.98761957 +0000 UTC
  Info:
```

Terraformのバージョンは ```v1.1.6```とする。

# ローカルのブランチでの処理
1. ローカルのブランチで```terraform init```を実行する。

1. ローカルのブランチで```terraform.lock.hcl```をコミットする。

1. ローカルのブランチでリモートのブランチにプッシュする。
【css】writing-mode（縦書き）要素をposition:fixed;で右固定すると端が切れる　Safari/Firefox


タイトルの通り、縦書きのボタンを画面右端に固定したところ、Chromeやedgeでは問題なく見れるのに、SafariとFirefoxでは右端が切れてました。

## 横書きだったり、左固定なら問題なし

縦書き・右固定限定の不具合です。
縦ボタンなど普通設置しない?ものなのでしょうか？？^^;

ネットで調べてはみたんですが、分からなかったので、検証してみました。
まずはこちらのソースをご覧ください。

```HTML
<!-- 縦ボタンのhtml -->
<div class="btn">
  <a href="#">縦ボタン</a>
</div>
```

```CSS
/* 縦ボタンのCSS */
.btn {
  position: fixed;
  top: 50%;
  right: 0;
  z-index: 10;
}

.btn a {
  display: block;
  color: #fff;
  background-color: #277BC0;
  font-size: 20px;
  font-weight: bold;
  text-decoration: none;
  writing-mode: vertical-rl;
  padding: 10px;
}
```

![スクリーンショット-2022-09-10-0.45.22.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/787211/5ce94d15-01ab-fe28-a5d8-34eaa0a04ac2.jpeg)
すると、Chromeやedge↑では、右端ピッタリの位置にある。
![スクリーンショット-2022-09-10-0.49.43.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/787211/57429bb6-38b2-1710-3b51-46ac8f9c722a.jpeg)
しかし、SafariやFirefox↑だと、こんな感じで右端の向こうに切れる。

# widthとheightを設定してみる

このボタンパーツには元々文字数の異なるテキストをあれこれ入れたかったので、あえてwidthとheightの値を設定していなかったので、設定してみると…↓

```CSS
/* 縦ボタンのCSS */
.btn {
  position: fixed;
  top: 50%;
  right: 0;
  z-index: 10;
}

.btn a {
  display: block;
  color: #fff;
  background-color: #277BC0;
  font-size: 20px;
  font-weight: bold;
  text-decoration: none;
  writing-mode: vertical-rl;
  padding: 10px;
  width: 50px;   /*　widthの値を追加！！ */
  height: 100px; /*　heightの値を追加！！ */
}
```
![スクリーンショット-2022-09-10-0.57.55.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/787211/b1c9a424-b8cd-4561-c913-e7c144874439.jpeg)

↑Safari、FireFoxも両方右端ピッタリに！

# 親要素はいらなかった‥

…そして、これらを再現しようとコードを書いてるうちに、ボタンの構成を親要素(div)と子要素(a)で作らないで、a要素だけのボタンなら、widthとheightを入れなくても、SafariとFirefoxで右端切れずに表示されることを発見？しました…↓
```HTML
<!-- 縦ボタンのhtml　a要素のみ -->
<a href="#" class="btn-02">縦ボタン其ノ二</a>
```
```CSS
/* 縦ボタンのCSS　a要素のみ */
.btn-02 {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 10;
  color: #fff;
  background-color: #FF7F3F;
  font-size: 20px;
  font-weight: bold;
  text-decoration: none;
  -ms-writing-mode: tb-rl;
  writing-mode: vertical-rl;
  padding: 10px;
}
```
![スクリーンショット-2022-09-10-1.14.11.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/787211/e2ce34ee-278f-a0d5-039e-96f27879e633.jpeg)
↑a要素だけのボタンで右端ピッタリの位置です！
（元々横書きのボタンを流用したので、位置調整をするために親要素がありました…）

## 何かの要素でwidthが100%より大きくなっているか

「css safari 画面 切れる…」などのキーワードで検索をすると、画面から何か要素がはみ出て、widthが画面100%より大きくなっている場合を指摘する記事などあるのですが、この検証ではbodyの中にボタン要素しか置いてないので、それは無いです。
paddingがwidthからはみ出たりしないよう、要素全体（body）にbox-sizing: border-box;も指定しています。


### 終わりに

実はiPhoneのChromeでも同じことが起きていましたが、メディアクエリ（max-width: 600px）でボタンのwidthとheightを入れると右端が切れることは無くなりました。
ではiPhoneで、ボタンの親要素無しなら右端切れないのか、Safariなど他のブラウザでの見え方は？というと、検証出来てないので、また別の機会にと思います。

CSSは奥が深いですね。
とういか…何か私に基礎的な知識が足りて無いだけなんでしょうか‥^^;

今回の縦書き要素固定の不具合は、とりあえずの解決策は見出したんですが、なぜ起こったのかというと、理由はよく分かりません…。
ご存知の方はご教授くだされば幸いです:ok_woman_tone3:
grub2 におけるカーネルの指定方法

/etc/dnf/dnf.conf の以下のエントリ

```
exclude=kernel*
```

上記が抜けてしまって、dnf update でカーネルもアップデートしてしまった？
ちょっとまって、カーネルはインストールされるものなので、前のが消えたりしません。

ひとつ前のカーネルで立ち上げればいいだけ。

今は、grubby という便利なコマンドがあるようです。

```
# uname -r
<新しいカーネル>
# grubby --default-index
0
# grubby --set-default-index=1
# shutdown -r now
# uname -r
<ひとつ前のカーネル>
# grubby --default-index
1
```

よかったー。

（参考）
https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/system_administrators_guide/sec-making_persistent_changes_to_a_grub_2_menu_using_the_grubby_tool
Ender5Pro+SKR3+OctoPrint+Klipperの環境構築

# 記事一覧
[その他の3Dプリンタ記事はこちら。](https://qiita.com/tanutanup/items/be6a6166d814a5882c7f)

# 何があったか？
[前回構築した3Dプリンタ環境](https://qiita.com/tanutanup/items/b3d6d973e286503a8620)で課題であった、[SmartFilamentSensor](https://biqu.equipment/products/btt-sfs-v1-0-smart-filament-sensor-detection-stuck-blocking-filament-module?_pos=1&_sid=3333bca90&_ss=r)の導入であったが、なんやかんやうまくいかなかったので、ゴソっと環境を作り直すことにした。

# DL;TR
- Klipperはボードメーカーがビルドしたファームをそのまま使ってしまおう
- Octoprint + Klipperの環境はRaspberryPiImagerで"Octoprint"を導入し、Klipperはgitからダウンロードしよう。
- 間違ってもpython3に乗り換えようとしたり、ユーザー名をpiから変えたり、homeディレクトリが気持ち悪いから/usr/local/share配下にしたりしないこと。

# Klipper + SKR3の導入を決めた理由

- SKR2のファームの修正
    - コンパイルはOK。だが、フィラメントが動いているのにもかかわらずフィラメント切れを検出。
（[こちら](https://github.com/bigtreetech/smart-filament-detection-module)を参照して設定したのだが失敗）
    - SKR2にKlipperを導入
        - ファームを書き込みできたものの、octoprintとの通信に失敗。

# Klipperとは
[klipper](https://www.klipper3d.org/)とは、Marlinと異なりPC(含むRaspberryPiなどのシングルボードコンピュータ）による演算支援を前提とした3Dプリンタ用ファームウェア。
ベルトの緩みやヘッドの慣性により生じる振動を打ち消すなど、従来のマイコンのみで完結するファームでは不可能な高速＆高品質な印刷が可能。


# Octoprintのインストール
昔からRaspberryPiを使っている人はご存知ないかもしれないが、最近は[RaspberryPIImager](https://www.raspberrypi.com/software/)というGUIツールによりSDカードへOSイメージ書き込みが可能。OSの種類も様々選ぶことができる。このツールの素晴らしいことは、イメージ書き込み時点でホスト名やWifi設定、SSH設定などが可能。ディスプレイ無しでのセットアップができるので便利。
今回は"Other specific-purpose OS" ⇨ "3D printing" ⇨ "OctoPi" ⇨ "OctoPi(stable)"を選択。

# Klipperのインストール
OSのセットアップ後、klipperを入手しインストール。
（インストールディレクトリはHome以外はやめたほうがよい）
```bash
cd ~/
git clone https://github.com/Klipper3d/klipper.git
~/klipper/scripts/install-octopi.sh
```

# Octoprintのセットアップ
Octoprintを通常どおりセットアップ後、plugin managerから"OctoKlipper"を導入。

# Klipperファームの書き込み
[klipper公式](https://www.klipper3d.org/)のマニュアルではウィザードに従ってファームを作成し、USB経由で書き込みを行うように記載されているが、今回は限りなくサボることで確実に動作させたいので[biggtreetechのgithub](https://github.com/bigtreetech/SKR-3)から落としたファームをSDカードにリネーム(firmware.bin)し書き込む。

# Klipper用設定ファイルの作成
Klipperでは3Dプリンタごとの設定はファームでは行わず、cfgファイルを送信することで設定する。[biggtreetechのgithub](https://github.com/bigtreetech/SKR-3)には汎用的に使えるcfgファイルがあるのでHomeディレクトリにコピー＆リネーム(printer.cfg)。
[mcu]タグの下にあるserial：の後ろを以下のコマンドで調べたデバイスIDに書き換える。

```bash
ls /dev/serial/by-id/*
```

# まとめ＆今後
この段階でうまく設定できていれば、Octoprintの画面上で接続ができる。
（おそらく温度センサーが接続されていないことでエラーが出るが通信自体は可能なはず。）
次回はEnder5Proに合わせてcfgファイルの編集を行う。
scoopのcache, bucketsを共用化する話

# 概要

scoopのキャッシュフォルダを、Windowsファイル共有フォルダに配置、複数マシンで共用化する。


# 最初にいいわけ

これは「もしかして他の人がすでに有用な記事を書いているかもしれないが、被ったり恥ずかしいバッドノウハウであることを恐れずに自分の手持ちネタを晒す」記事です。


# はじめに

scoopのパッケージ管理の仕組みは、単純化されている一方で洗練されていて、この仕組みに乗っかろうと腹をくくると非常に使い勝手がよい。
（変なことを自分でいろいろ考えて決める必要がない）

一方で、一般ユーザーにはあまり馴染みのないジャンクションを多用しているので、なにができるのか、なにをやったらよくて何をやったらいかんのか、直感的にわかりづらい。


# 自分の出した結論

## 共用していいもの

- cache
- buckets

## 共用したらいかんもの

- apps
- shims
- persist


# どうやって共用化するか

シンボリックリンクを使う。

```
> ren cache cache.orig
> mklink /d cache \\some-host\scoop\shared\cache
> ren buckets
> mklink /d buckets \\some-host\scoop\shared\buckets
```

各bucketsの同期には、おそらくほぼ `git` を使っている（はず）だが、最近のgit（いつ頃からかはちょっと忘れた。。）では、共有フォルダ配下のリポジトリ更新時に警告が出る[^91]。

警告を出さなくするには、safe directory設定をする。
警告に従ってもいいが、個人フォルダの `.gitconfig` を直接追記編集するほうが楽かもしれない。

[^91]:おそらく将来的には禁止するのだろうと思われる。

```.gitconfig
[safe]
	directory = %(prefix)///some-host/scoop/shared/buckets/main
	directory = %(prefix)///some-host/scoop/shared/buckets/extras
	directory = %(prefix)///some-host/scoop/shared/buckets/java
	directory = %(prefix)///some-host/scoop/shared/buckets/k2ok
```

上記の例のとおり、リポジトリごとのrootパスを指定しないといけないらしい。


# apps, persistを共用化できない理由 

ジャンクションの特性による。

scoopで多用している [ジャンクション] とは、[NTFSファイルシステム] 特有のもので、任意のユーザが作成可能である[^92]。

ところがこのジャンクションは、 `*.lnk` （ショートカット）や `*.url` （インターネット ショートカット）のような「実体は只の一般ファイル」であるのと異なり、「ローカルの」NTFS上にしか存在することしかできない。

言い換えると、「共有フォルダの実態がNTFSであったとしても、ネットワーク経由では作成できない」。

[^92]:scoopが基本的に管理者権限を必要としない仕組みも、このジャンクションの特性に依存してる。

appsフォルダの扱い方としては、各バージョンのファイル一式をバージョンのフォルダに展開した上で、インストール／アップデートしたバージョンを「current」というジャンクションで参照させる仕組みになっている。

ジャンクションが共用可能なファイルシステムがあれば別だが、少なくとも自分は存在を知らない。

[ジャンクション]:<https://qiita.com/SAITO_Keita/items/eae21799fd3d136cb813>

[NTFSファイルシステム]:<https://docs.microsoft.com/ja-jp/troubleshoot/windows-client/backup-and-storage/fat-hpfs-and-ntfs-file-systems>


# shimsを共用化できない（？）理由

もしかすると（条件つきで）共用化できるかもしれないけど。。。

shimsに配置されるのは、「appsフォルダの実行ファイルを起動するコマンド群」であるはず。

で、これ自体は「実行ファイル」のはずなので、少なくとも32bit環境と64bit環境のものを混在させることはできない。

じゃぁアーキテクチャ一緒な環境同志だったら共用できるかというと、そもそもappsを共用できてないので、存在しないappsを参照するshimsが存在することになるんじゃない？

当然、配置先には存在しないので起動失敗するだろうけど、、、

shimsで占有されるのファイルサイズを考えたら、無理に共有する必要なくない？という話。


# その他補足

じゃぁ、「複数ユーザーでcache, bucketsは共用できるの？」という話になると思うけど、おそらくできないと思う。

同時実行される前提で排他制御などしてないのでは。

`git` が `safe-directory` の警告を出すのも、複数ユーザで同時に作業フォルダを操作することなど想定していないので禁止したいのでしょ。


とはいえ、これは自分のあてずっぽうなので、違うようならコメント等で教えていただけると嬉しいです。
[Flutter] TextFormFieldの値を変えた時にもう一つのTextFormFieldの値も変える方法


一つ目のTextFormFieldのonChangedの中で二つ目のTextEditingControllerを呼び出せばOKです。


必要ないものも入ってますが、消すのめんどくさかったので残してます。※ご了承ください。
<img width="400" alt="Screenshot_1662738372.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494487/29095ac4-b5f3-76f0-32e9-7a474917e382.png">


    TextEditingController te1 = TextEditingController();
    TextEditingController te2 = TextEditingController();

    TextFormField(
      controller: te1, //ここに初期値
      onChanged: (text) {
        print('First text field: $text');
        var a = int.parse(text);
        var b = 10 - a;
        print("b ${b}");
        te2.text = b.toString();
      },
    ),
    TextFormField(
      controller: te2, //ここに初期値

    ),


やり方わからずに再描画で値を変わるようにしてたんですが、再描画じゃダメだと言う指摘を受け重い腰を上げて探したけど全然なくて他の作業を行いながら色々考えてこれなら行けんじゃねってやってみたら行けたので、自身の忘備録がメインですが、誰かの参考になれば幸いです。

コピペでやりたい人はこちらをどうぞ。

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({Key? key, required this.title}) : super(key: key);
    
      final String title;
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
    
      @override
      Widget build(BuildContext context) {
    
        TextEditingController te1 = TextEditingController();
        TextEditingController te2 = TextEditingController();
    
        @override
        void initState() {
          super.initState();
          te1.text = "123"; // <- こんな感じ
        }
    
        return Scaffold(
          appBar: AppBar(
    
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
    
                TextFormField(
                  controller: te1, //ここに初期値
                  onChanged: (text) {
                    print('First text field: $text');
                    var a = int.parse(text);
                    var b = 10 - a;
                    print("b ${b}");
                    te2.text = b.toString();
                  },
                ),
                TextFormField(
                  controller: te2, //ここに初期値
    
                ),
    
              ],
            ),
          ),
        );
      }
    }
【Swift】コードでアラートを閉じる

# はじめに
たぶん普通の事なんだと思いますが、初心者すぎて悩んだので記録しておきます。

# 実装
ボタンでアラートを表示して、アプリがバックグラウンドになったらアラートを閉じます。
```swift
import UIKit

class ViewController: UIViewController {

    private var alert: UIAlertController!

    override func viewDidLoad() {
        super.viewDidLoad()

        // バックグラウンド監視
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(background),
            name: UIApplication.willEnterForegroundNotification,
            object: nil
        )
    }

    // アラートを表示する
    @IBAction private func alertButtonTapped(_ sender: Any) {
        alert = UIAlertController(title: "タイトル", message: "メッセージ", preferredStyle: .alert)
        let button = UIAlertAction(title: "OK", style: .default)
        alert.addAction(button)
        self.present(alert, animated: true)
    }
    
    // アラートを閉じる
    @objc private func background() {
        alert.dismiss(animated: true)
    }
}
```

# おわり
最初に定義しておくなんて技思いつきません
LaravelのJetstreamで新規ユーザ登録時のデフォルトチームを指定する

## 前提条件
- Laravel8.x
- Jetstream Team版導入済み

Teamを事前に複数作成しておいてください

以下を編集します。

app>Actions>Fortify>CreateNewUser.php


```php:CreateNewUser.php
 protected function createTeam(User $user)
    {
         $user->ownedTeams()->save(Team::forceCreate([
             'user_id' => $user->id,
             'name' => explode(' ', $user->name, 2)[0]."'s Team",
             'personal_team' => true,
         ]));

    }
```

を以下のようにします。


```php:CreateNewUser.php
 protected function createTeam(User $user)
    {
        // $user->ownedTeams()->save(Team::forceCreate([
        //     'user_id' => $user->id,
        //     'name' => explode(' ', $user->name, 2)[0]."'s Team",
        //     'personal_team' => true,
        // ]));

        //指定のチームに所属させる
        $team = Team::find(3);//findでID3を指定
        //以下はロールの指定。無くても動くかも。
        $user->teams()->attach($team, array('role' => 'editor'));
        $user->switchTeam($team);

    }

```

is_Nullを入れていないので、確実にteamのID3があることが前提となります。
技術同人誌を初めて書いた話【Qiita駆動執筆】

### 技術書典13に参加します

2022/09/10-2022/09/25に開催される技術書オンリーの同人イベント、「技術書典13」にサークル参加します。

https://techbookfest.org/

ちなみにサークル名は「choco_hack」で、新刊はQiitaに投稿した「[サイバーセキュリティお嬢様シリーズ](https://qiita.com/choco_py3/items/51c04bdc8bfe25fe84f3)」のまとめ本になります。

https://techbookfest.org/product/hMKMEetWAdaEGaGAmSt1u3

# 技術書典とは
> 技術書典（ぎじゅつしょてん）は新しい技術に出会えるお祭りです。
技術書典は「いろんな技術の普及を手伝いたい」という想いではじまりました。技術書を中心として出展者はノウハウを詰め込み、来場者はこの場にしかないおもしろい技術書をさがし求める、そんな技術に関わる人のための場として我々は技術書典を開催しています。
([公式サイトより](https://techbookfest.zendesk.com/hc/ja/articles/360050930552))

技術書典は、様々な技術同人誌に出会えるイベントです。

今回の技術書典13は以下のようなスケジュールとなっています。
オンライン開催：2022/09/10-2022/09/25
オフライン開催：2022/09/11（@池袋サンシャインシティ 展示ホールD）

# 執筆のきっかけ
セキュリティ関係の仕事をしているため、今年6月に勉強のため「[ゆるいハッキング大会](https://sys-guard.com/post-19519/)」というイベントに参加しました。
（半分は「ホワイトハッカーの教科書」などを執筆されている[IPUSIRON](https://twitter.com/ipusiron)さんの講演目当てで行きました）

IPUSIRONさんの講演内容自体には深く触れないでおきますが、「商業の本を出すステップの一つに、最近では**技術書典**などの（イベントで）同人誌を出す、というものもありますよ」というようなことを話されていました。
「**いつか商業で技術書を出版したい**」という野望があった私は「これはぜひともやらなければいけない」と火がついたのでした。

# どうして商業で技術書を出版したいのか
理由は以下です。

+ 技術書が好きだから
+ 日頃お世話になっている、分かりやすく解説されている技術書を自分も書いてみたいから
+ 書店に自分の本が並んでいる姿を見たいから

「ものづくりが好き」という気持ちが根底にある気がします。

# Qiita駆動執筆
執筆にあたり、モチベーションを保つ為に「Qiita駆動執筆（命名）」を行うことにしました。
やり方はシンプルです。
1. Qiitaに記事を投稿する
1. フィードバックがあれば取り入れて修正する
1. いいねやストック数を見て自己肯定感とモチベーションを上げる
1. 都度Re:VIEWなどに転記する

このやり方なら、「**Qiitaに記事を投稿していたらいつの間にか本がほぼ完成していた**」という状態になります。（ページ数は50pを超えました）
だいたい週2回くらいの投稿頻度で、1.5ヶ月くらい執筆していました（書き下ろし含む）。
ちなみに表紙は自分で描きました（知人の原稿合宿に混ぜてもらい、一晩で描き上げました）。

# テーマ選定など
テーマは勉強も兼ねて、仕事で扱っている「サイバーセキュリティ」としました。
また、書き方については[無職やめ太郎（本名）さん](https://qiita.com/Yametaro)の「ワイ記法」を参考にした「お嬢様記法」を採用することにしました。（執筆のハードルがグッと減ったのでこれはかなり良かったです）

※気になった方は「[文章が下手なワイが、技術記事を書くときにやっていること](https://qiita.com/Yametaro/items/21b8772e6c0d1d77e176)」と、「[サイバーセキュリティお嬢様シリーズ](https://qiita.com/choco_py3/items/51c04bdc8bfe25fe84f3)」を見てみてください！

# 参考になった動画や書籍、ブログなど
+ 技術書典公式チャンネルの初参加者向けYouTubeのライブ配信

https://youtu.be/0bYVYNEDJ-8

https://youtu.be/kb546igciLI

+ 運営さんがおすすめしていた執筆ツール「Re:VIEW」の解説本

https://booth.pm/ja/items/586727

+ 設営関係

https://note.com/mochikoastech/n/nf484f114855c

あとはググればなんとかなりました。インターネットってすごいね。
（※サークル参加者向けの公式Discordチャンネルがあるのですが、これにもかなり助けられました。）

# おわりに
技術書典の存在は数年前から認知していつつも、中々参加するきっかけがありませんでした。
（IPUSIRONさんとやめ太郎さんには勝手にめちゃめちゃ感謝しています。。。）
書き終えてみての感想は一言で言うと「ノリと勢いがあればだいたいなんとかなるもんなんだなぁ」です。
イベント中は目一杯楽しもうと思います！

もし良かったら[Twitter](https://twitter.com/choco_py3)をフォローして見守っていただけると嬉しいです！
【VSCode】snippet を作るための snippets（を作るための snippets ）



# snippets の作り方

VSCodeをエディタに使っている人であれば user snippets 機能を一度は使ったことがあると思います。予測変換を自分で作れるあれです。これがために VSCode を利用していると言っても過言ではありません。

でも snippets を作るファイルって書くのにちょっと時間がかかるんですよね。こんな感じで作ります。
```jsonc
{
	"（snippetのタイトル）": {
		"prefix": "（そのsnippetのトリガー）",
        "scope": "（そのsnippetを使う言語）",
		"body": [
			"（そのsnippetを展開して出したい内容）",
		],
		"description": "(そのsnippetの説明）"
	},
}
```
タイトルとかトリガーとかの入力欄をその都度作って書くのはちょっと大変です。

もし snippets ファイル内で自作の snippets を使って入力欄を一気に出せたらいつも使う snippets がもっと速く快適に作れちゃうんじゃないかと思いました。

# snippets を作るための snippets

user snippets の入っているファイルって拡張子が`.-codesnippets`とか`json`になっていますが実は`jsonc`ファイル（`json`ではないので注意）扱いなので「snippets ファイルへの入力を補助する snippets」は`jsonc.json`に作れます。コマンドパレットに snippets などと入力すると出てくる「スニペット: ユーザー スニペットの構成」を選択すると user snippets が書けるファイルがでてくるので`jsonc.json`を入力して選択してください。

なお`jsonc`というのは`json with comments`の略です。本来`json`にはコメントを書けないところを`jsonc`にするとコメントを書けるという具合です。`settings.json`も`jsonc`で書かれています。

```jsonc:jsonc.json
{
	"snippet": {
		"prefix": "snippet",
		"body": [
			"\"$1\": {",
			"\"prefix\": \"$2\",",
			"\"body\": [",
			"\"$3\",",
			"],",
			"\"description\": \"$4\"",
			"},$0"
		],
		"description": "snippet"
	},
	"fastsnippet": {
		"prefix": "fastsnippet",
		"body": [
			"\"$1\": {",
			"\"prefix\": \"$1\",",
			"\"body\": [",
			"\"$2\",",
			"],",
			"\"description\": \"$1\"",
			"},$0"
		],
		"description": "fastsnippet"
	},
	"lmdsnippet": {
		"prefix": "lmdsnippet",
		"body": [
			"",
			"\"$1\": {",
			"\"prefix\": \"$1\",",
			"\"scope\": \"latex,markdown,markdown_latex_combined,html\",",
			"\"body\": [",
			"\"$2\",",
			"],",
			"\"description\": \"$1\"",
			"},$0"
		],
		"description": "lmdsnippet"
	},
	"thetime": {
		"prefix": "thetime",
		"body": [
			"\\${CURRENT_YEAR}-\\${CURRENT_MONTH}-\\${CURRENT_DATE}_\\${CURRENT_HOUR}-\\${CURRENT_MINUTE}-\\${CURRENT_SECOND}",
		],
		"description": ""
	},
	"tmselected": {
	"prefix": "tmselected",
	"body": [
	"\\${1:\\${TM_SELECTED_TEXT}}",
	],
	"description": "tmselected"
	},
}
```

`snippet`は`name`、`prefix`、`body`、`description`を順に入力していくものです。
`fastsnippet`は`name`と`prefix`と`description`を同じにして一気に入力した後に`body`を入力するものです。
`lmdsnippet`は`fastsnippet`に`scope`で`markdown`と`latex`をつけたものです。数式を markdown と latex で入力するときに同じ snippets を使いたいのでつけました。
`thetime`は時間のプレースホルダーを入力します。一回一回コピペするのが面倒なのでつけました。
`tmselected`はプレースホルダー`TM_SELECTED_TEXT`を入力します。間違えないようにするのに便利なのと大体のコマンドは`$1`を`${1:${TM_SELECTED_TEXT}}`にした方が使いやすいのでこれも snippet にしてます。

# 使用例

html で`sin`を入力すると`\sin `が出てくる snippet と`mathrm`を入力すると`\mathrm{}`（カーソルがはじめ`{}`の中に入る）が出てくる snippet を作っています。

![SS_28.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2823185/bfd5e41f-04bf-30b8-83de-26f78d023906.gif)


# snippets を作るための snippets を作るための snippets

`jsonc.json`自体が`jsonc`で書かれているので「snippets を作るための snippets を作るための snippets」（もはやなんだかわからない）を作ることもできます。ただし先ほどまで作っていた「snippets を作るための snippets」と区別して登録することはこのままではできません。


このファイルでは「snippets を作るための snippets を作るための snippets」を適用、あのファイルでは「snippets を作るための snippets」を適用するという使い方は、workspace や folder 等を変えてそれ用の snippets として定義すれば可能です。



# 参考文献

https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables
ruby debugを使ってみた話　~RubyKaigi2022に触発されたエンジニア~

RubyKaigi2022の方に参加してきまして、そちらの方で「debug」というツールの話を聞きそういえば一生`pコマンド`で出力していたと思い試して見たくなりました。
ということで実際触ってみての感想とかを書いていきます。


## 前準備

今回使ったコードはこちら
Gemfile

```gemfile
source 'https://rubygems.org'

gem 'nokogiri'
gem 'debug'
```

crawler.rb
```rb
require 'net/http'
require 'uri'
require 'nokogiri'
require 'kconv'

def main
    uri = URI.parse("https://qiita.com/itaya")
    request = Net::HTTP::Get.new(uri)

    req_options = {
      use_ssl: uri.scheme == "https",
    }

    response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
      http.request(request)
    end

    doc = Nokogiri::HTML.parse(response.body.toutf8, nil, 'utf-8')
    p '###HTMLタイトル取得###'
    p doc.title
end

main
```

```sh
$ bundle install --path .bundle
```

```sh
$ bundle exec ruby crawler.rb
"###HTMLタイトル取得###"
"itaya - Qiita"
```

何をしてるコードかというと、Qiitaの自分のページの情報をとってきてそのタイトルを取得するという簡単なものです。

## 使ってみる

crawler.rb
```rb
require 'net/http'
require 'uri'
require 'nokogiri'
require 'kconv'

def main
    uri = URI.parse("https://qiita.com/itaya")
    request = Net::HTTP::Get.new(uri)

    req_options = {
      use_ssl: uri.scheme == "https",
    }

    response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
      http.request(request)
    end

    doc = Nokogiri::HTML.parse(response.body.toutf8, nil, 'utf-8')
    p '###HTMLタイトル取得###'
    p doc.title
    debugger
end

main
```
こんな感じでdebuggerを仕込みまして

```sh
$ bundle exec ruby -r debug crawler.rb
```
デバッグモードで実行すると

```sh
"###HTMLタイトル取得###"
"itaya - Qiita"
[16, 24] in crawler.rb
    16|     end
    17|
    18|     doc = Nokogiri::HTML.parse(response.body.toutf8, nil, 'utf-8')
    19|     p '###HTMLタイトル取得###'
    20|     p doc.title
=>  21|     debugger
    22| end
    23|
    24| main
=>#0	Object#main at crawler.rb:21
  #1	<main> at crawler.rb:24
(rdbg)
```

このような画面が表示されるかと思います。
これでブレークポイントで止まってるのがわかるかと思います。

ここでいろいろなコマンドが入力できまして、
n: 次の行へ
s: 次のステップへ

などいろいろできます。
詳しいコマンド一覧はこちらです
https://github.com/ruby/debug

### rubyのコードを追加で実行してみる

上記の状態でコマンド一覧以外の入力をするとrubyのコードとして解釈され実行できます。

```
doc.text
```
と入力すると、docに入っているテキスト情報が取れたりします。


## まとめ
スクレイピングのコードとかだと毎回pで出力確認していると都度コードを実行してデータ取得からやり直す必要があったんですけど、これならとってきたあと色々ゴニョゴニョすることができてやりやすそうに感じました！
Keycloakによる認証基盤の立ち上げ(2) Quarkus版

# この記事の概要

- 以前 [Wildflyベースで立ち上げたKeycloak環境] が壊れたのを機に、Quarkusベースで再立ち上げ。

- Keycloakについては、前項記事を参照。

[Wildflyベースで立ち上げたKeycloak環境]:<https://qiita.com/k2ok/items/dbf41ac8a7e3cd873bbb>


# やってみる

[Get started with Keycloak on bare metal] によると、OpenJDK 11以降が必要になったとのこと。

[Get started with Keycloak on bare metal]:<https://www.keycloak.org/getting-started/getting-started-zip>

32bit Windows環境では選択肢少ないが、Zulu 17 JDKをscoopから入れる。

```
> scoop install zulu17-jdk
```

Keycloak本体は、こんな感じでbucket自作。

```keycloak.json
{
    "version": "19.0.1",
    "description": "Open Source Identity and Access Management",
    "homepage": "https://www.keycloak.org/",
    "env_add_path": "bin",
    "extract_dir": "keycloak-19.0.1",
    "url": "https://github.com/keycloak/keycloak/releases/download/19.0.1/keycloak-19.0.1.tar.gz",
    "hash": "b1e9f89a6d02b9bf43848a970c69d94fb7e75c75fc8ebe2f69a1b7d2e0739733",
    "persist": [
        "conf",
        "data",
        "providers",
        "themes"
    ]
}
```

とりあえず永続化したほうがよさそうなものは `perisist` に移したけど、基本的にscoopは上等なzip展開ツールくらいにしか使っていない。

まず何も考えずに `kc.bat start-dev` してみる。
追加オプションなにかつけたかもしれないけど忘れた。

（だいぶ昔のスクリーンショットベースで記事書いてます、雑で申し訳ない）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50759/bca539c4-0a16-796c-f3d6-6bacb059a639.png)

環境変数設定を先にするのもありだけど、案内に従って http://localhost:8080/ を開く。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50759/267df1c4-ae6c-67d5-c52d-3600bb4b1afd.png)

管理用アカウントと初期パスワード設定。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50759/edfa2483-1df2-cede-8f57-28f7a8f757ab.png)

管理用アカウント作成。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50759/9f5b52cf-f717-8bb2-384a-87d78eed74e0.png)

あらためてログインする。

管理画面まで入ると、旧Wildfly版と変わらなそう。

Realm作成したので、いろいろ見ながら設定。。

（このへんも詳細忘れた）

```
kc build
```

```
kc show-config
```

ここからは、
[リバースプロキシで立ち上げる記事を参考](https://www.keycloak.org/server/reverseproxy) に、実環境用の準備。

Wildfly版のときと同様、httpsはフロント側で担い、バックエンドをhttpにする。
この場合は、 `--proxy edge` を引数指定したらいいようだ。

```
kc start --hostname=example.com --proxy edge
```

あらためてフロント側から管理画面にアクセス、、すると、ログイン直後の画面でエラー。
なんだこれ。

エラー画面でHTMLソースを見てみると、リバースプロクシ環境を誤認しているのか、httpとhttpsが混在しているようなコードが生成されている。

仕方ないので、フロント側で使っているLet's Encryptの鍵と証明書を与えてhttps起動に変えてみよう。

引数指定は `--proxy=reencrypt` に。ホスト名指定もやめる。

```start-keycloak.cmd
kc start --hostname-strict=false --http-enabled=false ^
    --proxy=reencrypt ^
    --https-certificate-file=cert.pem ^
    --https-certificate-key-file=privkey.pem ^
    --https-port=7161 ^
    --log-level=INFO
```

だめでした。証明書チェックでエラー、、というのは、以下の鍵ではだめだという。

```privkey.pem
(略)
-----BEGIN EC PRIVATE KEY-----
(略)
```

どうせバックエンドだし、もう [Snake Oil] でいいや。。

[Snake Oil]:<https://gihyo.jp/admin/serial/01/ubuntu-recipe/0387>

無事開きました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50759/2c6b582a-18ad-44e8-95e8-cce59bd39dbb.png)


# サービス化

Wildflyと違い、QuarkusではWindowsサービス関係のヘルパーは無いようだ。

[WinSW](https://github.com/winsw/winsw/) で独自に仕込むか。。

[別記事にします。](https://qiita.com/k2ok/items/bdafb9bbeeedcd626093)
Slack Platform を使って、Google Apps Script から Webhook を飛ばす

## 概要
Slack で「Incoming Webhook を追加しようかなー」と思ったら、こんな記述が。
![](https://storage.googleapis.com/zenn-user-upload/765e3f36b33a-20220904.png)

なるほど。今後は Slack apps とやらに置き換わっていくと。
というわけで、ドキュメントの手順を踏みながら Slack apps の設定と Google Apps Script で Webhook 飛ばすところまでやってみた。

## とりあえず公式ドキュメントをみる。
Slack apps のリンクを踏んで飛んだ先は Slack platform の説明。
https://api.slack.com/start/overview

今回は Messaging API を使います。
Navi. を見ると "Using Webhooks" とあるので以下のページを確認。
https://api.slack.com/messaging/webhooks

とりあえず見た感じは、設定手順もすごく簡単。

## 手順
以下の手順で進めていく。
### Slack App を作る
アプリを作ります。(プロジェクトみたいな概念ですかね？)
https://api.slack.com/apps/new

### Webhooks を有効化します
作ったアプリのページにいくと左の Navi. に [Incoming Webhooks] があります。
![](https://storage.googleapis.com/zenn-user-upload/ea3a9588d765-20220904.png)

[Activate Incoming Webhooks] を On に。
![](https://storage.googleapis.com/zenn-user-upload/bf3a33c01e76-20220904.png)

### Webhooks URL を作成
ページ一番下の [Add New Webhook to Workspace] をクリック。
チャンネルを選ぶ。
**※プライベートチャネルでも使えるので最初はプライベートチャネルでテストするのが良さそう。**

こんな感じで表に追加されてれば OK.
![](https://storage.googleapis.com/zenn-user-upload/a7fa2a8b7b7f-20220904.png)

ちなみに、追加が成功すると Slack チャネルにもこんな感じで通知が出る。
![](https://storage.googleapis.com/zenn-user-upload/b3e38b3f2fa0-20220904.png)

### 送信してみる。
今回は最終的に Google Form に回答が来たら送信する作りにするので Google Apps Script で書いてみました。
```
/* 作成した Webhook URL を入れます。 */
const webhookUrl = "https://hooks.slack.com/services/***/****"

/* text に送信したいメッセージを与えて送信できます。 */
/* 以下のコードの例では、"hello slack" と送信されます。 */
function sendWebhook() {
  UrlFetchApp.fetch(webhookUrl,
    {
      "method" : "POST",
      "contentType" : "application/json",
      "payload" : JSON.stringify({
        "text": "hello slack."  // 送信したいメッセージ
      })
    }
  );
}
```

こんな感じで書いて、実際に実行してみた結果がこんな感じ。送信できた。
あとはメッセージの内容だけ変えてあげて、form イベントと紐つければやりたいことはできそう。
![](https://storage.googleapis.com/zenn-user-upload/21ef6cf05ddc-20220905.png)

## おまけ： アイコンと名前が殺風景なので変えてみる
[Basic Information] > [Display Information] から変えられるっぽい。
![](https://storage.googleapis.com/zenn-user-upload/6c25609e3050-20220905.png)

の下の方に設定するところが。
![](https://storage.googleapis.com/zenn-user-upload/f807c73d5f43-20220905.png)

アイコンは 512px X 512px か、2000px X 2000px でなければならないらしい。

![](https://storage.googleapis.com/zenn-user-upload/b8e88fc8e611-20220905.png)
色々変えてみた。設定して [Save Changes] を押すだけ。簡単。

![](https://storage.googleapis.com/zenn-user-upload/ee564c0982ec-20220905.png)
こんな感じになる。

## 感想
アイコンやアプリ名などの設定も含め、設定がほとんど Slack platform の UI 上ででき、ものすごく簡単に使えた。
渡す値も基本的に text が渡せれば良い人によってはシンプルで楽だと思う。
blocks を定義することでセクションを作ったり、マークダウン記法、画像が載せたりもできるらしいのでこっちも積極的に使っていこうと思う。
カラムが多いと地獄的に見づらくなる"SELECT * FROM DBテーブル名;"の表示を見やすくする方法

はじめに
--
開発をしていると、きちんとDBに保存がされているか確認するためにMySQLの中身をチェックする場面は多いのではないでしょうか。
現在作成しているポートフォリオではログイン機能をdevise_token_authを使って実装しました。

ターミナルからMySQLの中に入ってusersテーブルを確認しようと"select * from users;"を叩くと、
下記のように表示がずれて地獄的に見づらいです。(モニター使ってるのに。。)

認証のために必要なデータを格納するカラムが多いのが原因なようです。(カラム数が少ないテーブルは見づらくありませんでした。)
![スクリーンショット 2022-09-09 22.27.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641341/90a03e72-1ed4-567e-1e42-28fb9c583f33.png)
これではどのカラムに何が入っているのかとても分かりづらいので、見やすくする方法は何かないか調べました。
とても簡単なものでしたが、備忘録として残そうと思います。


";"の代わりに"\G"をつけるだけ
--
見出しの通りです。
下記のように";"を"\G"にすると縦に表示されるようになり、とても見やすくなります。
~~~
#地獄的に見づらい
"SELECT * FROM DBテーブル名;"

#縦に表示されてとても見やすい
"SELECT * FROM DBテーブル名\G"
~~~

実際に"\G"を使用して表示されたDB内の情報が下記になります。
(自分のemailアドレスとか映っちゃってるので隠してます)

![スクリーンショット 2022-09-09 22.37.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641341/4c3db442-3bdb-94f8-4e45-67aac24986a1.png)

とても見やすいですね！もっと早く知っておきたかった。。
自分が不便だと感じることは大抵解決策があるんですね。
不便だなぁで終わらずに快適にするためにまず調べてみようと思います。

参考:
--
https://dev.mysql.com/doc/refman/5.6/ja/show-slave-status.html
ESP32でHMAC(SHA-256)を生成する

ESP32で、HMAC(SHA-256)を生成する機会があったので、備忘録として残しておきます。
ESP32のArduinoでは、mbedtlsを利用できるのでそれを使います。
p_hmacResultは、32バイト以上の配列である必要があります。

M5StickCだろうが、M5StampC3だろうが、ESP32であればPlatformIOを使ってコンパイルできてちゃんと動作します。追加のplatformio.iniのlib_deps指定は不要です。

```cpp
#include <mbedtls/md.h>

long hmac_sha256(const char *p_key, const char *p_payload, unsigned char *p_hmacResult)
{
  mbedtls_md_context_t ctx;
  mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
  mbedtls_md_init(&ctx);
  mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
  mbedtls_md_hmac_starts(&ctx, (const unsigned char*)p_key, strlen(p_key));
  mbedtls_md_hmac_update(&ctx, (const unsigned char*)p_payload, strlen(p_payload));
  mbedtls_md_hmac_finish(&ctx, p_hmacResult); // 32 bytes
  mbedtls_md_free(&ctx);

  return 0;
}
```

以上
シェルスクリプトでエクセルファイルを全文検索してみる

## 概要
特定の文字列を含むエクセルファイルがどこにあるのか、エクセルファイルを開かずに検索したい時もある。(※1)
方法は色々ある(※2)が、今回はシェルスクリプトでやってみる。(何でもシェルスクリプトでやりたくなってしまう悪い癖ですねほんと。。。)
基本的にはMacで動かしていますが、bashが動く環境ならWindowsでもLinuxでも動くはず。多分。(WindowsのGitBashでなら確認しました！)

※1：試してはないが、エクセルだけでなくOffice系ファイルは同じ方法でいけるはず。今回のシェルスクリプトを、エクセルファイルだけでなくOffice系ファイル全般に使えるスクリプトにアレンジすることもできるはず。
※2：Macならmdfindコマンドとか

## カラクリ
どうやってExcelファイルを全文検索するのか。
知っている人も多いと思うが、実はエクセルファイルというのは、xmlファイル群をzipで固めたものだ。
だから、zipを解凍して全xmlファイルについてgrepをかけてやればエクセルファイルの全文検索が可能ということだ。

## シェルスクリプトのコード
エクセルファイルを全文検索するシェルスクリプトのコードは以下の通り。

```bash:excelgrep
#!/bin/bash

main() {
  # 引数からgrepの操作内容を取り出す
  operation=""
  for arg in "${@}"; do
    if [ $(echo ${arg} | grep -vE '\.xlsx$|\.xls$') ]; then
      # FIXME シングルクォーテーションが消えてしまう
      operation=${operation}" "${arg}
    fi
  done

  # 一つ一つのxlsxファイルに対してgrepする
  for arg in "${@}"; do
    # *.xlsx以外の引数の場合何もしない
    if [ $(echo "${arg}" | grep -vE '\.xlsx$|\.xls$') ]; then
      continue
    fi

    # 一時ファイルなら何もしない
    if echo "${arg}" | grep -qE '^~\$'; then
      continue
    fi

    target_file="${arg}"

    # zip展開用一時ディレクトリ作成
    tmpdir=$(mktemp -d)
    # grepの標準出力用一時ファイル作成
    tmpgrep=$(mktemp)

    # エクセルファイルを一時ディレクトリに解凍する
    # 標準出力とエラー出力は鬱陶しいので捨てる
    unzip "${target_file}" -d "${tmpdir}" 1>/dev/null 2>&1

    # 全xmlファイルにgrepする
    find ${tmpdir} -type f \
      | grep -E 'xml$' \
      | xargs -I{} grep ${operation} {} \
      | tr '\"' '\n' \
      | tr '>' '\n' \
      | sed -e 's/<\/.*//' \
      | grep ${operation} >${tmpgrep}

    # 検索文字列があればファイル名とその内容を出力する
    if [ $? = 0 ]; then
      # FIXME
      # awkを使って出力しているので、
      # grepの--color=autoが効かない
      cat ${tmpgrep} | awk '{print filename": "$0}' filename="${target_file}"
    fi

    # 一時ディレクトリとファイルを削除
    rm -r ${tmpdir}
    rm ${tmpgrep}

  done
}

main "${@}"
```

## インストール方法

(1)PATHが通っているディレクトリに配置するか、配置したディレクトリのPATHを通す
```
例)
・~/binというディレクトリを作成し、本スクリプトを配置
・~/binのPATHを通す
```
(2)実行権限を与える
```bash
chmod +x excelgrep
```

## 使用方法
基本的にgrepコマンドと同じような使い方ができる。
```bash
# カレントディレクトリ内のエクセルファイルに対して全文検索を行う
excelgrep '検索文字列' *.xlsx
# カレントディレクトリ以下の全てのディレクトリ内のエクセルファイルに対して全文検索を行う
find . -type f | grep -E 'xlsx$' | xargs -I{} excelgrep '検索文字列' {}
```

## 使用例
普通にgrepコマンドのように使用し、同じように出力を得ることができる。

```bash
$ excelgrep 'test' *.xlsx
test1.xlsx: test1
test2.xlsx: test2
test3.xlsx: test3
```


## オプション
基本的にgrepコマンドと同じオプションが使える。
- 大文字小文字を区別しない
→```-i```

- 正規表現
→```-E```
```
例)大文字小文字を区別しないor検索
excelgrep -iE '検索文字列1|検索文字列2' *.xlsx
```
本番環境でActionMailerを動かす 【Rails Heroku Gmail】

## 環境

- Ruby3系
- Rails6系
- Heroku
- Gmail
## 実現したいこと
- ActionMailerを使って本番環境でパスワードリセットメールを送信できるようにしたい。
## 現状

- 本番環境でメール送信できない。（ActionMailerがうまく動かない）
- 開発環境ではうまく動いている。（letter oppner webで確認した。）
- エラーがいくつか発生した。`Rails Missing host to link to!`や`Net::SMTPAuthenticationError (535-5.7.8 Username and Password not accepted. Learn more at)`**といったエラー。
## 解決策
一言で言うとdevelpment.rbの編集とGmailの設定をしないといけない。
これをやっていなかったのでうまくいかなかった。
## Gmailの設定
Gmail側の設定については以下の記事が参考になった。
[https://skillhub.jp/courses/137/lessons/977](https://skillhub.jp/courses/137/lessons/977)
development.rbの記述についても説明があったが、個人的に分かりにくい点があったので下で補足しています。
## development.rb（herokuの場合）
以下は`production.rb`。
```ruby
config.action_mailer.default_url_options = { protocol: 'https', host:'アプリのURL'}
#ホストの設定

config.action_mailer.delivery_method = :smtp
#smtpを使う

config.action_mailer.smtp_settings = {
    port: 587,
    address:"smtp.gmail.com",
    domain: 'gmail.com', #Gmailを使う場合
    user_name: ENV['GMAIL_ADDRESS'], #Gmailアカウントのメールアドレス
    password: ENV['GMAIL_PASSWORD'], #Gmailで設定したアプリパスワード
    authentication: :plain,
    enable_starttls_auto: true
  }
```
### config.action_mailer.default_url_options
ここの書き方は以下のようになる。
```ruby
config.action_mailer.default_url_options = { protocol: 'https', host:'アプリのURL'}
```
### user_name: , password: の箇所は環境変数を使うべき
セキュリティからして危ないので。環境変数を使わないとエラーにつながったというケースもある。([https://qiita.com/kobayashimakoto/items/c7a96ea9f6cbc8d2da77](https://qiita.com/kobayashimakoto/items/c7a96ea9f6cbc8d2da77))
### password: の箇所はGmailで設定したアプリパスワードを使う
Gmailアカウントのログイン時のパスワードではないので要注意。
### 参考にした記事
[メールの設定](https://skillhub.jp/courses/137/lessons/977)