OmniAICreator/Qiita-1.07M huggingface dataset:
MacOSのアップデートが繰り返されるときの対処法

## 問題
macOSが最新でないとき，システム環境設定のソフトウェア・アップデートに通知が表示されます。

![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698891/eec01d22-1eb0-27e1-1400-5af4bf09adb6.jpeg)

「今すぐアップデート」を押してMacを再起動してもmacOSがアップデートされず，ソフトウェア・アップデートの通知が消えないことがあります。

## 原因

インストールファイルをダウンロードするためのストレージ空き容量が足りていないことが原因でした。アップデートには12.5GB必要です。

なお，これが原因でアップデートできなくてもストレージ空き容量が足りていない旨の通知やアップデート出来なかった通知は表示されません。

## 対処法

以下の引用文は次の公式ドキュメントが引用元です。
[Macのストレージを増やす方法 - Apple Support] (https://support.apple.com/ja-jp/HT206996)

まず，ストレージ空き容量が足りていないことを確認します。ストレージ空き容量は次の方法で確認できます。

> Apple メニュー  >「この Mac について」の順に選択し、「ストレージ」をクリックします。棒グラフの各セグメントは、ファイルのカテゴリごとのストレージ使用量の概算値を表しています。各セグメントの上にポインタを置くと、詳細情報が表示されます。

次に，標準のストレージ管理機能を使って不要なファイルを見つけ，削除します。

> 「管理」ボタンをクリックすると、下図のようなストレージ管理のウインドウが表示されますこのボタンは、macOS Sierra 以降でのみ表示されます。

その後，もう一度ソフトウェア・アップデートの通知から「今すぐアップデート」を押すと正常にアップデートされます。
Web系の定番ソフトウェアのバージョンを確認する。

次のコマンドを発行する。

# CentOS
```bash
cat /etc/redhat-release
```

# Apache
```bash
httpd -v
```

# Java
```bash
java -version
```

# Python
```bash
python --version
python3 --version
```

# PHP
```bash
php -v
```

# WordPress
```bash
cat wordpress/wp-includes/version.php
# $wp_versionの値を確認する。
```

# MySQL
```bash
-- MySQL接続後に次のSQL文を発行する。
select version();
```

# Oracle Database
```bash
-- OracleDB接続後に次のSQL文を発行する。
select * from v$version;
```

# 備考
本記事はブログ「[雑用エンジニアの技術ノート](https://scrapbox.io/nezuq/)」からの移行記事です。先のブログは削除予定です。
[Xcode] チーム内でXcodeの設定項目を合わせて不要な修正差分が出ないようにする

今日からでもすぐに取り入れられて、

- コードをよりクリーンにできる、とか
- 工数を削減できる、とか

そんなTipsを紹介していく記事シリーズです。
「知らなかった」「気づかなかった」「忘れていた」そんな誰かの役に立てば幸いです。

# 前提環境

- Xcode 11.3.1
- Swift 5.1.3

# 課題

マージリクエスト（プルリクエスト）のレビュー時に、このような空白行の差分が出るとうっとおしくないですか？

![スクリーンショット 2020-09-09 11.14.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/d69acc44-a570-25d7-3c14-f3df1fbcc458.png)

# 解決策

Linterライブラリを入れるのも手ですが、そうも行かない場合は、この設定をONにすればだいぶ軽減されます。

Xcode > Preferences > Text Editingタブ > Editingタブ
![スクリーンショット 2020-09-09 11.14.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/2165a446-7ff8-f103-612a-f238f59add77.png)

- Automatically trim trailing whitespace : 行末のスペースを削除（デフォルトはON）
- Including whitespace-only lines : 空白のみの行について空白を削除（デフォルトはOFF）

チーム内で、**両方ともONに統一すると良いかと。**
[Xcode] Storyboard IDの2つの用途

今日からでもすぐに取り入れられて、

- コードをよりクリーンにできる、とか
- 工数を削減できる、とか

そんなTipsを紹介していく記事シリーズです。
「知らなかった」「気づかなかった」「忘れていた」そんな誰かの役に立てば幸いです。

# 前提環境

- Xcode 11.3.1
- Swift 5.1.3

# 割とメジャーなStoryboard IDの用途

こちらの記事にあるように、
画面遷移をコードで行う場合に、StoryboardからUIViewControllerのインスタンスを取得するために使う、という用途です。
[【Swift】画面遷移の方法まとめ - Segueを使わない画面遷移](https://qiita.com/misakiagata/items/b7f6c2f6c9f988ec38c7#3-segue%E3%82%92%E4%BD%BF%E3%82%8F%E3%81%AA%E3%81%84%E7%94%BB%E9%9D%A2%E9%81%B7%E7%A7%BB)

# （たぶん）マイナーなStoryboard IDの用途

Storyboard Referenceによって、Storyboard-AからStoryboard-Bに遷移する場合に、Storyboard-Bの先頭じゃなく**後方のUIViewControllerに遷移したい時に使う**、という用途です。

## 例

**Storyboard-B（遷移先）**

後方のViewController（この例ではFourthViewController）に任意のStoryboard IDを設定しておきます。
![スクリーンショット 2020-09-11 16.20.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/0eeff7be-8276-611a-de94-c38aa6b87c5d.png)

**Storyboard-A（遷移元）**
![スクリーンショット 2020-09-12 9.24.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/9afe0d4f-ce05-5c64-5b26-b6ec2ede96f5.png)
↑FirstViewControllerからStoryboard-BのFourthViewControllerに遷移する導線があります（赤枠部分）。

↓このStoryboard Referenceで、「Referenced ID」に上で設定したStoryboard IDを指定します。
そうするとFirstViewControllerからStoryboard-BのFourthViewControllerに遷移できます。
![スクリーンショット 2020-09-12 9.25.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/0be4b9cd-d6b1-2c63-cbf4-a3e71d45c350.png)

このように、Storyboard IDをうまく使うことで、**1つのStoryboardで複数の導線をカバーすることができます。**
GitHubにある Poetry プロジェクトを参照するサンプル

# はじめに

PipenvとPoetryの一番の大きな違いはPipenvはライブラリプロジェクトでは使えないが、Poetryは使えるところではと思い、Poetryを勉強しました。
 
mainプロジェクトとlibraryプロジェクトの２つのプロジェクトをGithub経由でインストールしてlibraryプロジェクトの関数をmainプロジェクトから実行するサンプルです。

ソースは [mainプロジェクト](https://github.com/va034600/poetry-main)と [libraryプロジェクト](https://github.com/va034600/poetry-lib) にあげてあります。


# ディレクトリ構成

```:poetry-lib
├── pyproject.toml
└── poetry_lib
    └── aaa2.py
```

```:poetry-main
├── pyproject.toml
└── main.py
```

# ファイル
## poetry-lib
```:pyproject.toml
[tool.poetry]
name = "poetry-lib"
version = "0.1.0"
description = ""
authors = ["va034600"]
packages = [
    { include = "poetry_lib" },
]

[tool.poetry.dependencies]
python = "^3.6"
python-dateutil = "^2.8.1"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
```

```:aaa2.py
from dateutil.relativedelta import *
from datetime import *


def bbb2():
    today = datetime.now()
    return today + relativedelta(months=+6)
```

## poetry-main
```:pyproject.toml
[tool.poetry]
name = "poetry-main"
version = "0.1.0"
description = ""
authors = ["va034600"]

[tool.poetry.dependencies]
python = "^3.6"
poetry-lib = { git = "ssh://git@github.com/va034600/poetry-lib.git" }

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
```

```:main.py
from poetry_lib import aaa2

print(aaa2.bbb2())
```

# 実行
```:setup
$ cd poetry-main
$ poetry install
$ python main.py
2021-03-13 08:00:14.266597
```

# 終わりに
Pytharmを使えば、mainプロジェクトにlibraryプロジェクトをアタッチすればデバッグもlibraryを修正した反映の確認もできます。
便利です
![スクリーンショット 2020-09-13 8.09.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203946/9f6e5b9b-a002-dc9f-8333-85ef958de960.png)
Rails uninitialized constant エラーの解決へのシンプルなチェックリスト

# これはなにか
Railsをそれなりにやっているにも関わらず、uninitialized constant error にハマってしまったのでチェックリストをここにまとめておこうと思う。

# 結論
チェックするべき項目は以下3点である。

1.  ファイル名とclass名が一致しているか？
- ファイルpathとmoduleの定義が一致しているか？
- Railsにファイルを読み込ませているか？

以下詳細をかいていく。

# ファイル名とclass名が一致しているか？

以下のようなclassが存在していたら、ファイル名は`qiita_user.rb` でないといけない。

```rb
class QiitaUser
  def hoge
  end
end
```

# ファイルpathとmoduleの定義が一致しているか？
以下のようなmoduleにnestされたclassが存在していたら、pathは `**/v1/auth/user.rb`　のようになるだろう。


```rb

class V1::Auth::User
end
```

OR 

```rb

module V1
  module Auth
    class User
    end
  end
end
```


# Railsにファイルを読み込ませているか？
Railsのデフォルトのフォルダ以外に、フォルダを追加する場合、autoload pathに追加したフォルダへのpathを定義する必要がある。
`app/lib/hoge/foo.rb` を追加したとする。この場合、`config/application.rb` に以下の様な形で、autoload pathを追加する必要がある。

```rb
module App
  class Application < Rails::Application
    # 省略
    
    config.autoload_paths += Dir.glob("#{config.root}/app/lib")
  end
end
```


### twitterもフォローよろしくおねがいします。 ！
https://twitter.com/yoshixj
OpenWrtでリピータ機能（WDS）を使う

部屋レイアウトなどの都合で、ルーター間のネットワークを有線だけで構成できない状況があるかと思います。例えば上記のような場合です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46856/0c2a14f1-2139-0959-f144-c9bd300a3f1a.png)

このような場合、いわゆる二重ルーター構成にするしかないと考える人が多いのではないでしょうか。少なくとも私はそう思っていましたが、WDSやメッシュネットワークを使えば途中に無線区間があっても全体を1つのサブネットに収容でき、ネットワーク構成をシンプルに保つことができます。

このような構成は市販の無線LANルーターでも実現できますが、本稿ではOpenWrtルーターでのWDS構成について説明します。

## WDSとは

WDS (Wireless Distribution System)は無線アクセスポイント間を接続し、両側のネットワークをシームレスに接続する技術の総称です。実現方法については規格化されておらず、概念に対する名称だというのが私の理解です。そのため、別会社の製品同士のWDS機能に互換性があるとは限りません[^1]。

[^1]: [Wireless distribution system \- Wikipedia](https://en.wikipedia.org/wiki/Wireless_distribution_system)

この機能は多くの市販製品で「WDS機能」「リピータ機能」として提供されています。また、リピータ機能に特化した機種も販売されています。

## OpenWrtでの設定

OpenWrtでのWDSの設定は若干わかりにくい気がしますが、端的に言えば「親APと子APの無線モードをWDSにする」のがキモです。

以下、LuCIから設定する手順を紹介します。

### 親AP

1. 「Network」「Wireless」の設定で「Interface Configuration」「General Setup」「Mode」を「Access Point (WDS)」に変更

### 子AP

1. 「Network」「Interface」のLAN設定「General Settings」で「Static address」にして親APのサブネット内の固定IPを設定
2. 「Network」「Interface」のLAN設定「DHCP Server」「General Setup」で「Ignore interface」としてDHCPを無効に
3. 「Network」「Wireless」で「Scan」をクリックして既存ネットワークに「Join Network」してPSKの設定などを行う。「Firewall zone」はlanに変更
4. 無線の「Mode」を「Client (WDS) 」に変更、所属する「Network」をwwanからlanに変更
5. 「Network」「DHCP and DNS」の「DNS forwardings」に親APのIPアドレスを設定
6. 「Network」「Interface」のLAN設定「General Settings」「IPv4 gateway」に親APのIPアドレスを設定
7. 「Network」「Interface」のLAN設定「Physical Settings」「Enable STP[^3]」にチェックを入れる
8. 他に必要なネットワーク設定を行う。例えばリピータ設定をする場合は同一のネットワーク設定でアクセスポイントを構成する（Client設定と両立できる）
9. 「Network」「Interface」のWWANは不要なので削除

[^3]: Spanning Tree Protocol。ブリッジループやブロードキャストストームを防止する。

上記の設定にするとDHCPやDNSは親APが全部担当することになります。

### 構成例

我が家の場合は旧型PS4が有線LAN配線しにくい位置にあるのですが、このPS4は5GHz無線に非対応なので、無線LANルーターを介して5GHzネットワークに参加するのにWDSを利用しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46856/2a3198db-ed00-cf21-241b-846f144fafa6.png)

シンプルな構成なんですけど、子APの無線モードを単に「Client」にしてしまうと無線インターフェースをブリッジインターフェースに参加させられず、どうしても二重ルーター構成になっちゃうんですよね。無線モードを「Client (WDS)」にすれば同一サブネットにできるので、二重ルーターにせずに離れ小島にネットワークを通せるというわけです。

余談ですが、最近のPS4は5GHz無線に対応しているのでこんな面倒な作業は不要です。

## 参考URL

- [Atheros and MAC80211 WDS to implement a wireless network bridge (wireless repeater)
](https://openwrt.org/docs/guide-user/network/wifi/atheroswds) 上記作業手順の元ネタです。
Powershell基本集

#Powershell基本

##PSCustomObject(Powershell V3.0以降)

```Powershell:test.ps1

$Object = [PSCustomObject]@{

  Name = "BOB"
  Aget = 17
  Sex = "Male"

}


```


##ハッシュ更新

```Powershell:test.ps1

#以下の2項目の配列があると想定（name属性をキーとする）
#$User.name
#$User.Value

#キーが存在した場合
If($HashTable.ContainsKeys($user.name)){

　#今回チェックしたものが登録されているものより大きいか確認
  If($HashTable[$user.name].Value -lt $user.Value ){

    #登録済みキーの値を更新
    $Hashtable[$user.name] = $user.value

  }

#キーが存在しなかった場合
}else{

  #キーを値と合わせて追加
  $Hashtable.Add($user.name,$user.value )

}




```
MacのTunnelblickでVPNつなぐとネットが全部遅くなる問題を解決する 

# 現象
MacのTunnelblickでVPNをつなぐとネットが全部遅くなる

以下の環境で確認

- マシン: MacBook Pro 16 (Late 2019) 
- OS: Mac OS 10.15.6 Catalina 
- ネット接続: Wi-Fi 
- Tunnelblickのバージョン: 3.8.3a (build 5521)  

(VPN接続前)
<img width="400px" alt="スクリーンショット 2020-09-13 1.52.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/713060/3b0f2859-d278-efb0-5d3f-22b4de911805.png">

(VPN接続後)
<img width="400px" alt="スクリーンショット 2020-09-13 1.53.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/713060/2e23ac4b-113f-5c5f-7349-bf6af5150623.png">

んーこれは辛い。。

# 原因
VPNに関係のないトラフィックもすべてVPNを通して送られてしまっているから

`netstat -rn -f inet` をターミナルで実行するとトラフィックの経路情報が確認できる

↓VPN接続前
![スクリーンショット 2020-09-13 1.53.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/713060/075e11ba-114c-39bb-614f-1913046f512b.png)


↓VPN接続後
![スクリーンショット 2020-09-13 1.54.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/713060/cce45680-1fd6-d48a-57c2-9389893e35bc.png)


`0/1 10.8.0.9 UGSc utun2` の行が追加されてしまっていることが原因。
なのでこれを削除すれば速くなる


# 解決策
以下のコマンドで上記の経路を削除できる

```bash
$ route delete 0/1 10.8.0.9
```

※ `10.8.0.9` の部分は環境によって違うかもしれないので注意

コマンドをVPN接続後に毎回打つのは面倒なので、Tunnelblickの機能を使ってVPN接続後に自動的に実行するように設定する

1. TunnelblickのVPNの設定ファイルが置いてあるディレクトリを開く。
恐らく `~/Libarry/Application Support/Tunnelblick/Configurations` か `/Library/Application Support/Tunnelblick/Configurations` に入っている
2. 設定ファイル (`*.tblk`) で右クリックして「パッケージの内容を表示」する
![スクリーンショット 2020-09-13 2.02.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/713060/f713df25-604d-11ae-798f-0b1fbf0137c2.png)
3. `Contents/Resources` を開き、そこに以下のスクリプトを `connected.sh` という名前で保存する

    ```bash
    #!/usr/bin/env bash -e

    echo "Deleting route 0/1 10.8.0.9"
    route delete 0/1 10.8.0.9
    ```

4. 念のため `chmod 777 connected.sh` をして実行できるようにしておく
5. 完了。


# 結果
↓VPN接続前
![スクリーンショット 2020-09-13 1.53.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/713060/52405332-a2c9-0321-1475-082dc9cedd59.png)


↓VPN接続後
![スクリーンショット 2020-09-13 2.14.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/713060/1e3563a4-8acc-9491-ef18-03c4e59d4340.png)



`0/1 10.8.0.9 UGSc utun2` が追加されていないことを確認。

↓VPN接続後の速度
<img width="400px" alt="スクリーンショット 2020-09-13 2.14.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/713060/de6553c0-9a9e-f970-8c3c-2e749e287851.png">


VPN接続前と同じくらいの速度になりました
めでたしめでたし。


# 参考にしたページ
https://tunnelblick.net/cUsingScripts.html
https://github.com/Tunnelblick/Tunnelblick/issues/634
STM32のバックアップ電源

　STM32F4のバッテリバックアップを使いたいと思い、Vbatにコンデンサを接続してみた。

　回路はこんな感じ。

![2020-09-13_2-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264452/646c3f64-a06c-2392-a79e-8610f35c38e7.png)

　VDDからダイオード経由でVbatに接続、Vbatから抵抗経由でキャパシタに接続。
　本来はVbatに0.1uFを接続するべきだが、今回使用したマイコンボード(STBee F4mini)にはすでに実装済みなので回路図上では省略している。

　電解コンデンサは内部抵抗が小さいから、ダイオード経由でVDDに直結すると大きな突入電流が発生する可能性がある。適当なRを挟むが、470Ωなら入手性も高いだろうし、電流も7mA程度で、丁度いい。数値に特に意味はないので、300-1kΩ程度であれば問題ないはず。
　D1を定電流素子としてR1を省略してもいいが、次に述べる理由によってある程度のRがあったほうがいい。

　Vbatは過渡的にVDDと接続されることがあるらしいので、Vbatとキャパシタを近い場所(低い抵抗値)で接続すると、キャパシタが放電状態のときにVbatが地絡状態となる可能性があるため、Vbatとキャパシタの間に抵抗が入るような構成にしている。D1とVbatの間にRを入れれば放電時のRによる損失を防げるが、その場合は過渡的な状態への対策が必要になる。
　SRAM素子のバックアップではR1をバイパスするようにダイオードを入れる例もあるが、STM32のバックアップ電流は極めて小さいので(数十マイクロアンペアのオーダー)、R1による電圧降下は無視できる程度のはず(数mV程度)。ダイオードを入れても順方向電圧が抵抗の降下電圧を上回るはずだから、抵抗単体で十分と判断した(回路もシンプルに済む)。

***

　電圧履歴は以下のようになる。

![2020-09-13_01-35-25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264452/31025caa-df2d-fbf8-196e-ee412f50f9dc.png)

　デジオシの10倍で観測しているため、入力抵抗は10MΩ、3.3Vに対して0.33uA程度のリークとなる。Vbatの消費電流より1桁小さいので、今回は誤差として無視する(プローブを外した場合は消費電流が減るので、バックアップ時間が伸び、大抵は安全側に振れる)。

　充電はほぼ瞬時に完了し、3.1V付近から1.7V付近まで降下するのに670秒程度かかっている。
　今回使用したダイオードは1N4148のはずで、これは順方向電圧が1V程度ある。ただし微小電流領域では300mV前後まで小さくなるので、数分の充電で3.3Vに対して3.1V程度まで充電される。

　電解コンデンサ1個で、おおよそ11分程度、バックアップできていることがわかる。
　ただし今回はRTCをONにし、バックアップSRAMをOFFにしているから、場合によっては更に消費電流が大きくなるし、温度によっては消費電流や自己放電が大きくなる可能性もある。
　例えばメインの電池交換時や、瞬断へのバックアップであれば数百uFでも十分だろうが、数日から数ヶ月程度のバックアップを行いたい場合はEDLCや一次リチウムイオン電池が選択肢になる(その場合は異なる回路が必要になる)。
　電解コンデンサ1個とはいえ、470uFともなるとかなりの大きさになるので、実際に使う場合はバックアップ用の小型EDLC等を使うべきだろう。今回は評価目的で手持ち部品を流用したのと、あまりに大きな容量では放電時の評価が大変ということもあって、電解コンデンサを使用した(今回想定している用途的に、数十秒程度バックアップできれば十分だから、という理由もある)。

***

　おまじない
　オシロで1箇所プローブしただけで、ちゃんと評価したわけじゃないので、電気特性の問題の有無や、確実にバックアップを行いたい場合は各自適切に評価すること。
Laravel 8 ルーティングで「Target class [コントローラ名] does not exist」がでたときの対処法

Laravel 8 をインストールして、とりあえずCRUDシステム作って8系がどんな感覚か確かめようとして、すぐにつまづいた…(; ･`д･´)
エラーがでたのはルーティングが正しく記述されていなかったのが理由でした。

## 注意すべきルーティングの設定
Laravel8のルーティングは今までのルーティングと記述の仕方が少しことなります。https://readouble.com/laravel/8.x/ja/routing.html

Laravel8のルーティングを定義する場合、

```php:web.php
Route::resource('/blogs', BlogController::class);
```
こんな感じで記述しています。

ここで注意しなければならないのが、**ルート定義ファイル `web.php` の冒頭にルート定義で使用されているコントローラの名前空間をuseキーワードを使用して書く**ということです。

上記の例でいえば

```php:web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\BlogController;

Route::resource('/blogs', BlogController::class);
```
BlogControllerクラスを使用しているならば、**`use App\Http\Controllers\BlogController;`**と記述してインポートしてください。これを書き忘れると、「Target class [コントローラ名] does not exist」とエラーがでてしまいます。

上記ではリソースフルなルート設定をしましたが、普通にルーティングする場合は、

```php:web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\BlogController;

Route::get('/blogs', [BlogController::class, 'index']);
```
第2引数のところは配列になっていますのでご注意ください。もちろん、この記述でも`use App\Http\Controllers\BlogController;`を忘れずに！
Firebase Authentication

Firebase AuthをWebアプリに追加する際のコンソール側の操作メモ。

# 前提条件

- Firebaseアカウント作成済
- Firebaseプロジェクト作成済

# Firebaseの設定

## サインイン方法の設定

FirebaseコンソールからFirebaseプロジェクトを選択する。
左のメニューもしくはコンテンツ領域から「Authentication」を選択する。
Authentication直下から「Sign-in method」タブを選択する。

## Sign-in method

### ログイン プロバイダ

有効にしたいログインプロバイダを選択する。
プロバイダにより追加の設定が必要。
例えばGoogleログインプロバイダの場合、プロジェクトのサポートメールアドレスが未設定の場合は設定必要になるが、FirebaseはGoogleと連携されている為、GoogleのAPIキー等は不要。
Twitter等の場合、APIキー、シークレットが必要となるのでFirebase単体では完結できない。別途入手する必要がある。

### 承認済みドメイン

Firebase Authenticationによる認証を実装したいアプリのドメインを登録する。
Firebase Hostingにホスティングしているアプリ等であればデフォルトで登録済みとなっていると思われる。そうでなければドメインを登録しておく。

## Templates

### テンプレート

Firebase Authenticationが送信するメールアドレスのテンプレートが設定できる。
恐らく日本人がターゲットのアプリとなるであろうから画面左下の「テンプレート言語」を「Japanese」に設定しておこう。
メール本文にわけわからんプロジェクトIDが掲載されているようであれば「プロジェクトの概要」の設定（歯車マーク）⇒「プロジェクトを設定」⇒「全般」タブ⇒プロジェクト欄にある公開名を分かりやすい名前を変更しておくことをお勧めする。


## Webアプリに Firebase を追加

FirebaseコンソールからFirebaseプロジェクトを選択する。
プロジェクト名の下にある「＋アプリを追加」ボタンを押下する。

Firebase Authenticationによる認証を実装したいアプリのニックネームを設定する。
Firebase Hostingを使用するのであれば「このアプリのFirebase Hostingも設定します。」にチェックする。
アプリへの設定の追加方法が示されるのでアプリに導入する。Firebase Hostingにデプロイするのであれば「/__/firebase/」配下にある設定が使用できる。

追加したアプリは
「プロジェクトの概要」の設定（歯車マーク）⇒「プロジェクトを設定」⇒「全般」タブのページ下部に追加されるので、設定内容はいつでも参照できる。
Python入門 モジュールと条件式について

#はじめに
Pythonのif文を使用した条件式について学習しました。その勉強記録です！

#概要
Python学習中です。初歩の条件式を「RPGゲームの対戦状況」を再現して、学習したのでアレンジを加えて、まとめてみました。

#学習内容
Python入門 
条件式
変数とデータ型
randomモジュール

#完成コード

```py
# RPGの攻撃状況を再現

# モンスターと戦っている。
# 1から10のサイコロをふって、
# 6未満の場合、サイコロの目だけダメージを与えたと表示。
# 6以上の場合、クリティカルヒットとして、100のダメージを与えたと表示。
# 0の場合、防御されたとして、０ダメージを表示。

import random
hit = random.randint(0,10)
if 1 <= hit < 6:
    print("スライムに、" + str(hit) + "のダメージを与えた！")
elif hit >= 6:
    print("クリティカルヒットダメージ１００を与えた")
else:
    print("ダメージは、" + str(hit) + "防御された")
```

#コードの説明
一つずつコードを確認していきます。
##import

```py
import random
hit = random.randint(0,10)
```

importとは、組み込むこと。

```py
import random
```

この場合、randomモジュールを組み込んだことになります。

##randomモジュール
モジュールとはプログラミングを設計する為のパーツです。
Pythonでは、こういったモジュールを組み合わせてプログラムを作成していくそうです。

今回使用するrandomモジュールには、「random関数」、「randint関数」などが格納されています。

* random関数・・・0から1までの間で、ランダムな数値を返す
* randint関数・・・指定した引数の範囲で、ランダムな整数の値を返す

randomモジュールを使用する際には、 「random.関数名()」 で使用します。

例) random.random()
　  random.randint()

```py
hit = random.randint(0,10)
```

よってこのコードでは、hitという変数にrandint関数で指定された０〜１０の値からランダムに数字が代入されます。

##if文（条件式）

```py
if 1 <= hit < 6:
    print("モンスターに、" + str(hit) + "のダメージを与えた！")
elif hit >= 6:
    print("モンスターにクリティカルヒットダメージ１００を与えた")
else:
    print("ダメージは、" + str(hit) + "防御された")
```
次のコードは条件式です。
まず基本的なif文による条件分岐の書き方を確認しておきます。

### if文による条件分岐
こちらが基本的なif文による条件分岐の書き方。

```py
number = 1
①if 条件式1:
    print( "OK！")  #条件式1が成立したときの処理
②elif 条件式2:
    print( "どちらでもない")  #条件式2が成立したときの処理
③else:
    print( "NG！")  #条件式がどれも成立しなかったときの処理
```


今回の条件式では、

hitに代入されたrandint関数で指定された０〜１０の値が条件となって処理が行われます。

当てはめていきます。

###①【条件式１】
```py
if 1 <= hit < 6:
```
※代入されたhitの値が「１以上６以下の場合」

→　print("モンスターに、" + str(hit) + "のダメージを与えた！")

の処理が実行されます。

###②【条件式２】
elif hit >= 6:
※代入されたhitの値が「6以上の場合」

→　print("モンスターにクリティカルヒットダメージ１００を与えた")

の処理が実行されます。

###③【条件式１と条件式２どちらも成立しない】
```py
else:
```

※今回だと、代入されたhitの値が「０の場合」

```py
print("ダメージは、" + str(hit) + "防御された")
```

の処理が実行されます。

##まとめ
以上です。

ランダムに生成された値によって条件分岐され、処理が実行される流れになります。

一連の流れを「RPGゲームの対戦状況」として再現してみました。

```py
import random
hit = random.randint(0,10)
if 1 <= hit < 6:
    print("スライムに、" + str(hit) + "のダメージを与えた！")
elif hit >= 6:
    print("クリティカルヒットダメージ１００を与えた")
else:
    print("ダメージは、" + str(hit) + "防御された")
```

###参考
randomモジュール
https://docs.python.org/ja/3/library/random.html
slimの長い構文を途中で改行する

Railsアプリ開発時にslimを使ってhtmlを書いているときに、このような長い構文がある。これを改行する方法。


```slim
.card-toolbar
  a.btn.btn-primary.status-editable data-confirm="全ユーザーの編集権限を「編集可」に変更します。よろしいですか？" style="font-size: 15px; margin: 0 10px;" href="#{toggle_status_editable_admins_supplier_company_path(supplier_company_code: model.code, supplier_company_id: model.id)}" role="button" 編集可
  a.btn.btn-danger.status-editable data-confirm="全ユーザーの編集権限を「編集可」に変更します。よろしいですか？" style="font-size: 15px; margin: 0 10px;" href="#{toggle_status_not_editable_admins_supplier_company_path(supplier_company_code: model.code, supplier_company_id: model.id)}" role="button" 編集不可
```

こんな感じで[]をつけて改行できる。

```slim
.card-toolbar
  a[data-confirm="全ユーザーの編集権限を「編集可」に変更します。よろしいですか？" 
    style="font-size: 15px; margin: 0 10px;" 
    href="#{toggle_status_editable_admins_supplier_company_path(supplier_company_code: model.code, supplier_company_id: model.id)}" 
    role="button"]
    .btn.btn-primary.status-editable
     | 編集可
  a[data-confirm="全ユーザーの編集権限を「編集不可」に変更します。よろしいですか？" 
    style="font-size: 15px; margin: 0 10px;" 
    href="#{toggle_status_not_editable_admins_supplier_company_path(supplier_company_code: model.code, supplier_company_id: model.id)}" 
    role="button"]
    .btn.btn-danger.status-editable 
     | 編集不可
```
[Rails]外部キー制限かかっているときにdestoryできないエラーを発生させたい時の対処

#はじめに
レコード削除時に関連づけられたレコードが存在するときに、例外やエラーを発生させたい。
外部キーで使用されていると親要素が消されては困るというシチュエーションです。

削除しようとするとRubyのエラー画面が出てしまう。
#原因
`Model`に関係づけされたモデルに対する挙動を定義するが無いからでした。
マイグレーションファイルの`foreign key: True`だけでは不十分でした。

#対策
`Model`に`dependentオプション`を記述することで解決しました。
該当のモデルに以下の記述をします。

```ruby
#contractというモデル
class Contract < ApplicationRecord
  has_many :...., dependent: :restrict_with_error

end
```
アソシエーション記述の後に`dependent: :restrict_with_error`と記述することで`destory`できないときのエラーを表示することができた。
以下の条件分が成り立ち解決しました！。

```ruby

#contractというモデルのコントローラー
def destroy
    contract = Contract.find(params[:id])
    if contract.destroy
      redirect_to contract_path, notice: "選んだ契約を削除しました"
    else
      redirect_to contract_path, alert: "使用中のため削除できません"
    end
end
```

#参照
・https://dorarep.page/articles/rails-dependent#dependent_restrict_with_exception_restrict_with_error
Raspberry Pi 4BにAndroid TV 10.0をインストール (LineageOS)


はじめに
---

近年、Android TVを搭載したTVが[多数発売されています](https://kakaku.com/kaden/lcd-tv/itemlist.aspx?pdf_Spec032=1)。
少し興味があっても、様々な理由で購入に踏み切れない方もいらっしゃるのではないでしょうか。
そんな方におすすめしたいのが、"Android TV on Raspberry Pi"の方法です！

そこそこお安くご家庭の環境に合わせたAndroid TVが手に入ります。
既存のテレビを簡単にAndroid TV化することもできますよね！

というわけで、今回は最新版Raspberry Pi4にAndroid TVをインストールするために、筆者が行った方法をご紹介します！

注意点
---

- 質問欄を通して可能な限りのサポートはしますが、故障の他、一切の事柄に対して筆者は責任を負いません。
- 商用利用できません。ROMイメージの一部に**表示-非営利-継承4.0国際ライセンス**が含まれています。
- 地上波放送・BS/CS放送は見られません。
- できること/できないことは作者である[KonstaTさんのポスト](https://konstakang.com/devices/rpi4/LineageOS17.1-ATV/)に説明があるため、参照されるとよいかと思います。


筆者の環境
---

- [Raspberry Pi 4B](https://www.yodobashi.com/product/100000001005272136/)
    - ちなみに[電源・ケーブル・ケースのセット](https://www.yodobashi.com/product/100000001005357287/)もあります。ゼロから始めるならこちらがおススメです。 
- 64GB Micro SDカード: 容量は最低16GBあれば良いらしいです。
- 16GBUSBメモリ：容量は1GBもあれば十分かと思います。
- Windows10 (イメージをSDカードに書き込むために用います。)
- USB接続のマウス・キーボード


方法
---

#### まずはファイルの準備
1. [KonstaTさんのポスト](https://konstakang.com/devices/rpi4/LineageOS17.1-ATV/)から必要なイメージをダウンロードしてください。3つ必要です。
    - ① OSのイメージ: lineage-17.1-20200815-UNOFFICIAL-KonstaKANG-rpi4-atv.zip というファイルです。
    - ② Google関係のアプリイメージ：Q&A欄 "How to install Google apps?"項にリンクがあります。
    - ③ リカバリーから復帰するためのイメージ：Q&A欄 "How to boot out of TWRP recovery?"項にリンクがあります。
    
2. パソコンから、先ほどDLした**①OSイメージ**をSDカードに書き込んでください。
書き込み方法は[RaspberryPi公式](https://www.raspberrypi.org/documentation/installation/installing-images/windows.md)にありますが、私はその中で[balenaEtcher](https://www.balena.io/etcher/)を用いました。

3. パソコンで、先ほどDLした**②Google関係のアプリイメージ**・**③リカバリー復帰用イメージ**を
USBメモリにコピーしてください。


#### そしてラズパイに書き込み、そしてリカバリ画面へ

4. ラズパイにSDカード・USBマウス・キーボードを挿し、起動します。

5. ひとまず適当にセットアップしてホーム画面を出してください。
後で設定情報が消えるので適当で結構です。
※ 今後、十字キーと、Enter→タップ、F2→戻る、でリモコンの代用ができます。

6. 開発者モードを有効化します。
設定画面（右上の歯車を押す）→「デバイス設定」→「デバイス情報」に移動し、
ビルド欄をEnter連打してください。

7. リカバリー画面に移動します。
設定画面→「開発者モード」に移動し、"Local Terminal"と"Root access"を有効化します。
※ Root Accessの有効化の際にセキュリティリスクが高まる旨の警告が出ます。
その後、ホーム画面「アプリ」内にTerminalがあるはずです。それを起動します。
Terminal内で以下のコマンドを打ってください。
この後、自動で青っぽいデザインの画面が表示されるはずです。
マウスを用いてスライドすれば、リカバリ画面（Install, Wipe, Mount等のボタンがある画面）に入ることができます。

```
$ su
# rpi4-recovery.sh
# reboot
```

#### リカバリ画面で追加インストール

1. USBの読み出し
まず、イメージを書き込んだUSBメモリをラズパイに挿します。
ラズパイ画面→"Install"→"Select Storage"→"USB OTG"を選択すると、
先ほどUSBに書き込んだ2ファイル②③が表示されるはずです。

2. パッケージのインストール
②、③の順でインストールを行います。ここでもスライドすれば開始します。
その後、前の画面に戻りWipeを押して実行します。

これらが完了したら、Rebootボタンを押してください。
再度セットアップを済ませれば、晴れてAndroid TVの準備完了です。

Android TVの世界をぜひお楽しみください！


参考文献
---
[LineageOS 17.1 Android TV (Android 10) for Raspberry Pi 4](https://konstakang.com/devices/rpi4/LineageOS17.1-ATV/)
[How to Install Android TV on a Raspberry Pi with LineageOS](https://itigic.com/install-android-tv-on-raspberry-pi-with-lineageos/)
関数へのポインタの定義時の注意点

```ptrToFunc.c
#include <stdio.h>
#include <string.h>

int main(void){
    char *(*ptr_strcpy)(char *,char *) ; //関数へのポインタを定義　(誤)
    char str1[10];
    char str2[] = "hoge";
    ptr_strcpy = strcpy; //ここでエラーになる
    printf( "%s\n", str1);
    return 0;
}
```
コンパイルしてみると、下記の警告メッセージが出る。

```
warning: assignment from incompatible pointer type [-Wincompatible-pointer-types]
ptr_strcpy = strcpy;
```

あれ、char*はstrcpy関数の戻り値でしょ　関数へのポインタとなる「ptr_strcpy」の各引数の型もちゃんと定義してるね
調べてみると、実は2番目の引数に
<font color="DeepSkyBlue">const修飾子</font>があるようだ…

```c
    char *(*ptr_strcpy)(char *, const char *) ; //const修飾子を2番目の引数に追加
```

問題なくコンパイルできた。
Laravel8を試したら即効でエラー「Target class [〇〇〇Controller] does not exist.」が表示された

#はじめに
最近になってLaravel8がリリースされたと聞いて、とりあえず動かしてみようと思ったら開始早々にエラーが発生しました。
![laravel8エラー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/554111/47c2073b-b036-62e3-f8c1-1ecdf1d5e0b4.png)
`web.php`で`PostController`を呼び出す処理を書いたのに上記エラーが発生。Laravel6ではちゃんと呼び出せたのに:joy:
そのときの`web.php`はこちら。

```php:web.php
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', 'PostController@index');
```

原因調査したら、どうやらLaravel8の変更点の一つである__「`RouteServiceProvider.php`ファイルからデフォルトの名前空間を削除」__したのが大きく関係していると判明！Laravel7.xのプロジェクトからLaravel8.xに移行した場合は、この変更点の影響は受けない。Laravel8で作成した新規プロジェクトは変更点を考慮する必要があるそうです。


#RouteServiceProvider.phpの変化
Laravel7までは、`RouteServiceProvider.php`には次のコードが含まれていました。
(ファイルはapp/Providers配下に格納されています)

```php:RouteServiceProvider.php
protected $namespace = 'App\Http\Controllers';

Route::middleware('web')
      ->namespace($this->namespace)
      ->group(base_path('routes/web.php'));
```
webミドルウェアと`App\Http\Controllers`の名前空間を使用して、`routes/web.php`にルートをロードするよう指示しています。

```php:web.php
// Laravelは、App\Http\Controllers\PostControllerを検索します
Route::get('/', 'PostController@index');
```

* * *
Laravel8では、$namespace変数が削除され、Route宣言が次のように変更されました。

```php:RouteServiceProvider.php
Route::middleware('web')
      ->group(base_path('routes/web.php'));
```
`App\Http\Controllers`の名前空間を使用していないです。

```php:web.php
// Laravelは、App\Http\Controllers内でコントローラーを検索しません
Route::get('/', 'PostController@index');
```

このあと、どう修正すればよいのか解決方法を3つご紹介します。

#解決方法

+ `RouteServiceProvider.php`で名前空間を手動で追加する
+ `web.php`で完全な名前空間を使用する
+ `web.php`でアクション構文を使用する

##RouteServiceProvider.phpで名前空間を手動で追加する
Laravel7.x以前と同じ方法です。

```php:RouteServiceProvider.php
protected $namespace = 'App\Http\Controllers'; //追加
public function boot()
    {
        $this->configureRateLimiting();

        $this->routes(function () {
            Route::middleware('web')
                ->namespace($this->namespace) //追加
                ->group(base_path('routes/web.php'));

            Route::prefix('api')
                ->middleware('api')
                ->namespace($this->namespace) //追加
                ->group(base_path('routes/api.php'));
        });
    }
```

##web.phpで完全な名前空間を使用する
コントローラーの名前の前に名前空間を追加します。
例として、`app/Http/Controllers`フォルダ内の`PostController`を呼び出したいときは、次の通り記載します。

```php:web.php
Route::get('/', 'App\Http\Controllers\PostController@index');
```

##web.phpでアクション構文を使用する
```php:web.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;
Route::get('/', [PostController::class, 'index']);
```
配列で使用したいクラスとメソッドを指定します。
具体的に配列内の値は
PostsController :: class　->　App \ Http \ Controllers \ PostsControllerを返している
index　-> PostController.phpのindexメソッドを呼び出している

#終わりに
Laravel開発で初めて遭遇したエラーでしたので勉強になりました。
ただ、Laravel8の新機能・変更点を理解してから使用してみようと思いました。理解せず使用すると痛い目に遭いますね:joy:

#参考
[How to fix ‘Target class does not exist’ in Laravel 8](https://medium.com/@litvinjuan/how-to-fix-target-class-does-not-exist-in-laravel-8-f9e28b79f8b4)
[Rails]errorsメソッドでメッセージ表示させるとビューが崩れる時の対処

#はじめに
`form`で入力に誤り（例えば無入力）があるとエラーメッセージを表示させるために`errorsメソッド`を用いて記述し、動きを確認するとビューが崩れるという問題が発生しました。その時の自分の解決した方法です。

#原因
崩れたビューを確認すると`field_with_errors`という`divクラス`が付与させていていることを確認した。これが原因である。

#対処
今回はこの`field_with_errors`という`divクラス`が付与されないようにした。

`config/application.rb`に以下の記述をした。

```ruby

module #モデル名
  class Application < Rails::Application
    
    config.load_defaults 6.0
    config.i18n.default_locale = :ja

    config.action_view.field_error_proc = Proc.new { |html_tag, instance| html_tag }
    #↑この記述をプラスした

  end
end
```
こうすることで余計なクラスが付与されずにできました！

#参考
・https://yukimasablog.com/rails-field-with-errors
ROS(rosserial)とArduinoの時刻同期について

# 概要

* ROS(rosserial)とArduinoの時刻同期は、Arduino側のnode handleのnow()で、接続しているPC時間を呼び出せる

# コード (Arduino)

ROSの公式サンプル
http://wiki.ros.org/rosserial_arduino/Tutorials/Time%20and%20TF

ros_lib -> TimeTf from the Arduino

```cpp
/*
 * rosserial Time and TF Example
 * Publishes a transform at current time
 */

#include <ros.h>
#include <ros/time.h>
#include <tf/transform_broadcaster.h>

ros::NodeHandle  nh;

geometry_msgs::TransformStamped t;
tf::TransformBroadcaster broadcaster;

char base_link[] = "/base_link";
char odom[] = "/odom";

void setup()
{
  nh.initNode();
  broadcaster.init(nh);
}

void loop()
{
  t.header.frame_id = odom;
  t.child_frame_id = base_link;
  t.transform.translation.x = 1.0;
  t.transform.rotation.x = 0.0;
  t.transform.rotation.y = 0.0;
  t.transform.rotation.z = 0.0;
  t.transform.rotation.w = 1.0;
  t.header.stamp = nh.now();
  broadcaster.sendTransform(t);
  nh.spinOnce();
  delay(10);
}

```

# うまくいかない場合

* loop()関数内に、nh.spinOnce()が書かれていること確認
* 書かれていない場合は、追記

https://answers.ros.org/question/312221/rosserial-arduino-losing-sync/
[Rails]errorsメソッドでエラーメッセージが出せないときの対処

#はじめに
例えば`result.save`のように変数`result`の保存をしようとした時に、失敗するとresultにエラーメッセージが格納される。
これを利用してエラーメッセージを表示させたいがRubyのエラー画面となる。

言い換えると思い通りのエラーメッセージが出ない。

エラー時にコンソールで確認の以下のようになる


```ruby

result.errors.any? => False
```
保存できてないのにエラー出てないことになってる。


今回は無入力で登録しようとするとエラーを出したい。
#原因
`Model`にバリデーション記述が無かったことが原因。
テーブル作成時にはカラムに制限（無入力制限のため　`null: false`とした）がそれだけではエラーメッセージがでない。

#対策

`Model`に以下のような記述をした。

```ruby

class Result < ApplicationRecord
  belongs_to ...
  has_many ...
  validates :name, presence: true
  
  ...
end
```
`validates :name, presence: true`と記述することで

```ruby

result.errors.any? => True
```

となり

```ruby

result.errors.full_messages
```

で、エラーメッセージ全件の内容を取得し`each`を用いて取り出せました！
EKSのExternalDNSを使ってDNSの紐づけ部分を自動化する

## 参考

https://kubernetes-sigs.github.io/aws-alb-ingress-controller/guide/external-dns/setup/

## External-DNS
- kubectlコマンド経由でDNSレコードを設定することができる

## 手順

### マニュフェストファイルをダウンロード
```
wget https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.8/docs/examples/external-dns.yaml
```

### args部分を変更する
- ここでは、`test-dns.com` がRoute53で管理しているHostedZone

```
args:
- --source=service
- --source=ingress
- --domain-filter=test-dns.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
- --provider=aws
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
- --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both)
- --registry=txt
- --txt-owner-id=my-identifier
```

### Apply
```
kubectl apply -f external-dns.yaml
```

### 確認

```
kubectl logs -f $(kubectl get po | egrep -o 'external-dns[A-Za-z0-9-]+')
```

## 使い方

## ingressのマニュフェストファイルのannotation部分に書く
- `external-dns.alpha.kubernetes.io/hostname: k8s.test.jp` を追記

```
apiVersion: extensions/v1beta1
kind: Ingress                                                                                                                        
metadata:
  name: "ingress"
  namespace: "business"
  annotations:
    kubernetes.io/ingress.class: alb 
    alb.ingress.kubernetes.io/scheme: internet-facing
    external-dns.alpha.kubernetes.io/hostname: k8s.test.jp
  labels:
    app: "sample-app"
spec:
  rules:
    - http:
        paths:
          - path: /
            backend:
              serviceName: "app"
              servicePort: 80
```
### 確認

```
kubectl logs -f $(kubectl get po | egrep -o 'external-dns[A-Za-z0-9-]+')
```


```
time="2020-09-12T16:05:29Z" level=info msg="All records are already up to date"
time="2020-09-12T16:06:29Z" level=info msg="Desired change: CREATE k8s.test.jp A [Id: /hostedzone/Z02817823VED9IW9EO9S7]"
time="2020-09-12T16:06:29Z" level=info msg="Desired change: CREATE k8s.test.jp TXT [Id: /hostedzone/Z02817823VED9IW9EO9S7]"
time="2020-09-12T16:06:29Z" level=info msg="2 record(s) in zone test.jp. [Id: /hostedzone/Z02817823VED9IW9EO9S7] were successfully updated"
time="2020-09-12T16:07:29Z" level=info msg="All records are already up to date"
time="2020-09-12T16:08:29Z" level=info msg="All records are already up to date"
```
Pythonで株価取得

Pythonの勉強をしていく中で面白そうなモジュールがあったので使ってみました

必要なもの
---
pandas_datareader


以下のコードでインストールできます
`pip install pandas_datarader`

本題
---
今回はGAFAと呼ばれている企業の株価を取得しました
ほかの銘柄は[Yahoo Finance](https://finance.yahoo.co.jp/)で検索してください

```python:read-stock
import pandas-datareader as web
import pandas as pd
import datetime
data=web.get_data_yahoo(['GOOG','AAPL','FB','AMZN'])['Adj Close']

print(data.pct_change().tail())
```
出力例
<pre>
Symbols         GOOG      AAPL        FB      AMZN
Date
2020-09-04 -0.030941  0.000662 -0.028820 -0.021787
2020-09-08 -0.036863 -0.067295 -0.040922 -0.043944
2020-09-09  0.016034  0.039887  0.009441  0.037707
2020-09-10 -0.016018 -0.032646 -0.020568 -0.028605
2020-09-11 -0.007376 -0.013129 -0.005521 -0.018547
</pre>

簡単な~~説明~~
data:各銘柄の調整後終値(Adj Close)
pct_change():変化量を求めるメソッド
tail():DataFrameの末5行を取得

```python:datetime
#もし取得期間を決めたいなら
data=web,get_data_yahoo(['GOOG','AAPL','FB','AMZN'],
                        start=datetime.datetime(20xx,yy,zz),
                        end=datetime.datetime(20xx,yy,zz))['Adj Close']
```

---
ほかにも
Open:始値
Close:終値
High:高値
Low:安値
Voume:出来高
などもあるそうです
Teradata ARC（ARCMAIN）

# 目的
Teradata ARCユーティリティであるarcmainを利用してバックアップを取得する。

# コマンド
| 内容 | コマンド | 実行例 |
|:-:|:--|:-:|
| arcmainの実行  | arcmain  |
| DBにログオン  | (arcmainコマンド)<br> logon [接続先]/[ユーザ名],[パスワード]; |logon 127.0.0.1/dbc,dbc;|
| 表のバックアップ  | (arcmainコマンド) <br>archive data tables ([表名]), file=[ファイル名]  | archive data tables (sysdba.t1), file=t1arc;|
| ログオフ |(arcmainコマンド) <br>logoff;||

# 実行例１：表のアーカイブ
arcmainを利用してSYSDBAユーザのt1表のアーカイブを取得します。

### arcmainの起動

```
TDExpress1620_Sles11:~ # arcmain
09/12/2020 09:09:43   Copyright 1989-2018, Teradata Corporation.
09/12/2020 09:09:43   All Rights Reserved.
09/12/2020 09:09:43  
09/12/2020 09:09:43     ***   ****    ****
09/12/2020 09:09:43    *   *  *   *  *        PROGRAM: ARCMAIN
09/12/2020 09:09:43    *****  ****   *        RELEASE: 16.20.00.07
09/12/2020 09:09:43    *   *  *  *   *        BUILD:   162020eLX64 (Oct 24 2018)
09/12/2020 09:09:43    *   *  *   *   ****    
09/12/2020 09:09:43  
09/12/2020 09:09:43  RESTARTLOG = ARCLOG200912_090943_23483.rlg 
09/12/2020 09:09:43   
09/12/2020 09:09:43  PARAMETERS IN USE:
09/12/2020 09:09:43   
09/12/2020 09:09:43  
09/12/2020 09:09:43  
09/12/2020 09:09:43  ACCESS MODULE IN USE: NONE
09/12/2020 09:09:43  
09/12/2020 09:09:43  CHARACTER SET IN USE: ASCII                         
```
###DBにログイン

```
logon 127.0.0.1/dbc,dbc;
09/12/2020 09:11:10  ;
09/12/2020 09:11:10  LOGON 127.0.0.1/dbc,;
09/12/2020 09:11:12  LOGGED ON 3 SESSIONS
09/12/2020 09:11:13   
09/12/2020 09:11:13  DBS LANGUAGE SUPPORT MODE Standard
09/12/2020 09:11:13  DBS RELEASE 16.20.32.23
09/12/2020 09:11:13  DBS VERSION 16.20.32.23
09/12/2020 09:11:13   
09/12/2020 09:11:13  STATEMENT COMPLETED
09/12/2020 09:11:13  
```

### sysdbaユーザのt1表をアーカイブ（ファイル名はt1arc）
```
archive data tables (sysdba.t1), file=t1arc;
09/12/2020 09:17:43  archive data tables (sysdba.t1), file=t1arc;
09/12/2020 09:17:43  
09/12/2020 09:17:43  ARC HAS REQUESTED 4 SESSIONS, TASM HAS GRANTED IT 2 SESSIONS
09/12/2020 09:17:43  
09/12/2020 09:17:43  UTILITY EVENT NUMBER  - 36
09/12/2020 09:17:44  LOGGED ON    2 SESSIONS
09/12/2020 09:17:44   
09/12/2020 09:17:44  ARCHIVING DATABASE "SYSDBA"
09/12/2020 09:17:51  "SYSDBA"."t1" - STAT COLLECTION ARCHIVED
09/12/2020 09:18:10  TABLE "t1" - 213,910,723 BYTES, 4,194,309 ROWS ARCHIVED 
09/12/2020 09:18:10  DUMP COMPLETED
09/12/2020 09:18:10  STATEMENT COMPLETED
09/12/2020 09:18:10  
```
### HUTロックの解放
```
release lock (sysdba);
09/27/2020 12:43:32  release lock (sysdba);
09/27/2020 12:43:32  "SYSDBA" - LOCK RELEASED
09/27/2020 12:43:32  STATEMENT COMPLETED
09/27/2020 12:43:32  
```


### ログオフ
```
logoff;
09/12/2020 09:18:35  logoff;
09/12/2020 09:18:36  LOGGED OFF   5 SESSIONS
09/12/2020 09:18:36  STATEMENT COMPLETED
09/12/2020 09:18:36  
09/12/2020 09:18:36  
09/12/2020 09:18:36  ARCMAIN TERMINATED WITH SEVERITY 0
```
# 参考
[ARCMAINとは • Teradata Archive/Recovery Utilityリファレンス • リーダー • Teradataドキュメント](https://docs.teradata.com/reader/cQ6cku5Z4fZFtVSJU3WORg/NcAAh4LhwKu0nVsRBTRxTA)
Python3 ctypesでWindowsの特殊フォルダを開く関数のコードと学んだこと

Python3でctypesを使ってWindowsの特殊フォルダを開く関数のコードとそれを書く過程で学んだことです。

```py:フォントフォルダを開く
import os
import ctypes
from ctypes import oledll, wintypes


def getshellfolderpath(csidl: int) -> str:
    if not isinstance(csidl, int):
        raise TypeError
    SHGetFolderPathW = oledll.shell32.SHGetFolderPathW
    SHGetFolderPathW.argtypes = [
        wintypes.HWND, ctypes.c_int, wintypes.HANDLE,
        wintypes.DWORD, wintypes.LPWSTR]
    path = ctypes.create_unicode_buffer(wintypes.MAX_PATH)
    SHGetFolderPathW(0, csidl, 0, 0, path)
    return path.value


CSIDL_FONTS = 0x0014
os.startfile(getshellfolderpath(CSIDL_FONTS))

```

- Python 3では標準ライブラリのctypesを使ってWin32 APIを操作できる。
- Win32 APIの一般的な型（`HWND`、`DWORD`等）は`ctypes.wintypes`に定義されている。
- `ctypes`は動的リンクライブラリのロードのために`cdll`、`pydll`、`windll`、`oledll`をエクスポートする。`cdll`、`pydll`はcdecl呼び出し規約、`windll`と`ctypes.oledll`はstdcall呼び出し規約。`ctypes.oledll`は戻り値を`HRESULT`と仮定して失敗時に`OSError`を送出する。[公式ドキュメント](https://docs.python.org/ja/3/library/ctypes.html)。
- `WCHAR`型のバッファーは`ctypes.create_unicode_buffer`関数で作成する。戻り値の`value`で文字列を取得できる。[公式ドキュメント](https://docs.python.org/ja/3/library/ctypes.html#ctypes.create_unicode_buffer)。
- ファイル／フォルダを関連付けで開くには`os.startfile`関数が使える。
【Swift】String.SubSequenceとは何か？

# はじめに
文字列の日付を桁揃え（yyyy/mm/dd）しようとして、こんな感じで書いてみたところ・・・

```Swift
let str2 = "2020/7/1"
let array = str2.split(separator: "/")

if (array.count >= 3){
    let str3 = array[0]+"/"+String(format: "%02d", Int(array[1]))+"/"+String(format: "%02d", Int(array[2]))
}
```
Int(array[1])) のところで、以下のようなエラーが出ました。

```Swift
Initializer 'init(_:)' requires that 'String.SubSequence' (aka 'Substring') conform to 'BinaryInteger'
```

# String.SubSequenceとは？
String.SubSequenceって何？ってことで[公式ページ](https://developer.apple.com/documentation/swift/string/subsequence)を見ると「typealias SubSequence = Substring」となっているので、[Substringのページ](https://developer.apple.com/documentation/swift/substring)を見てみる。
> Overview
When you create a slice of a string, a Substring instance is the result. Operating on substrings is fast and efficient because a substring shares its storage with the original string. The Substring type presents the same interface as String, so you can avoid or defer any copying of the string’s contents.

> 概観
文字列のスライスを作成すると、Substringインスタンスが結果になります。サブストリングは元のストリングとストレージを共有するため、サブストリングの操作は高速で効率的です。 SubstringタイプはStringと同じインターフェースを提供するため、文字列の内容のコピーを回避または延期できます。

つまりString.split()の戻り値が、StringではなくSubstringになっているらしい。typeを確認してみると、確かにSubstringの配列になっていた。

```Swift:
let array = str2.split(separator: "/")
print(type(of: array))
```
> Array\<Substring>

SubStringをStringに変換すれば問題なさそうなので、以下のようにコードを書き換えました。

```Swift:
let str2 = "2020/7/1"
let array = str2.split(separator: "/")

print(type(of: array))

if (array.count >= 3){
    let str3 = array[0]+"/"+String(format: "%02d", Int(String(array[1]))!)+"/"+String(format: "%02d", Int(String(array[2]))!)
}
```
これで正しく変換できました。

> "2020/07/01"

# まとめ
この辺はたぶんはSwift4でStringがコレクション化されたことによるのかなと思われます。
以下、参考リンクです。

- [[Swift4]StringのSwft4変更点と文字列操作](https://re-engines.com/2018/09/27/swift4stringのswft4変更点と文字列操作/)
- [Swift Stringの切り出し、文字列操作について(Swift4のsubstring周りの挙動変更対応, Swift5対応バージョン）](https://qiita.com/risuke/items/cdd6a75f236faae3ce6b)

# 実行環境
- Xcode 11.2.1（Playgroundで実行）
notificationをdiscordで受ける

本日 9/12(土) に isucon10 の予選に参加をしました。
最近のisuconはdiscordを連絡手段に使われることが多いので、チームのコミュニケーションも discord に今回は集約をしました。
そんな中やっぱり deploy の notification も slackじゃなく discord で受けたいなーと思い、botとか作れないのかなと調べたんですが、
すごく簡単にできたので、備忘録がてら残しておきます。

## discord で Webhook を作成する

1. discord の サーバの設定を開く
2. メニューにある「連携サービス」を押下する
3. 「新しいウェブフック」ボタンを押下する
4. 名前とアイコンを適当に設定をし、発言させたいチャンネルを選ぶ (ここでは #deploy を選択をしてます)
5. 「ウェブフックURLをコピー」ボタンを押下する

## curl で メッセージを送ってみる

```
curl -H "Content-Type: application/json" -X POST -d '{"username": "bot", "content": "@everyone デプロイできたで〜＼(^o^)／"}'  <<上記でコピーしたウェブフックURL>>
```

以上でメッセージを送る事ができる。かんたん。

今回は以下のような簡易的なスクリプトを書いてデプロイをしていました。

```bash
#! /bin/bash -x

notify_discord () {
    curl -H "Content-Type: application/json" -X POST -d '{"username": "bot", "content": "@everyone デプロイできたで〜＼(^o^)／"}'  <<上記でコピーしたウェブフックURL>>
}


echo "start deploy ${USER}"

ssh isucon-server-1 "cd isuumo/webapp; git pull"
ssh isucon-server-2 "cd isuumo/webapp; git pull"
ssh isucon-server-3 "cd isuumo/webapp; git pull"

echo "finish deploy ${USER}"

notify_discord
```
Safariのオートコンプリートを防ぐ方法

　
# iphone Safariでの入力補完に関して
　Webサイトの入力欄において、iphoneのSafariで入力補完が行われ、想定した値が入力できないという話がありました。
この入力補完の厄介な点は以下の通り
・on/offはユーザのデバイスの設定でしか制御できない
・autocomplete="off"を無視して入力補完してくる
・近くのテキストから候補を予測して、値を入力してくる

下記のサイトを参考にしたがどうやらサイト側で入力補完自体を無効にする手段はないっぽいということが分かりました。
・[Safariのオートコンプリートの不思議な挙動](https://qiita.com/ymrl/items/bd24b9850a5a403b4c0d)
・[Safariのパスワードの自動入力を防ごうとしたら結構めんどくさかった話](https://www.aipacommander.com/entry/2016/09/09/092351)
・[iPhone Safari の「連絡先を自動入力」機能が賢くなりすぎて、電話番号じゃない入力欄に電話番号を自動補完しちゃうから困った話](https://mypacecreator.net/blog/archives/11226)

　どうしても入力補完をさせない入力欄を作る必要があり、いろいろ試したが、やはりユーザ側のデバイスの設定で無効にする以外、入力欄の入力補完をサイト側で禁止する方法は見つかりませんでした。

# 解決に至った回避策
　「そもそも入力欄にしなければ、入力補完などできないのではないか？」と考え、inputタグを使用しない方法で解決に至りました。

```test.html
    <div contentEditable="true" onkeyup="setInputValue" class="input-box" id="displayinput"> 表示する入力欄 </div>
    <input value="" id="hiddeninput" style="display: none;"/>

    <script>
        var inputValue = document.getElementById('displayinput').innerHTML;
        document.getElemetById('hiddeninput').value = inputValue;
    </script>

```

本来の入力欄とは別のcontentEditable="true"を用意して、input-boxクラスで入力欄っぽいスタイルをあててあげれば、入力補完の利かない入力欄が完成します。
さらにその要素のinnerHTMLを本来の入力欄の値に入れてあげれば、既存のコードの修正も最低限で済みます。
本来の入力欄はdisplay:noneにして、非表示にします。

ちなみに上記のdivの入力欄ではtype属性は使えませんがinputmode属性が使えるので、スマホ入力時のキーパッドの指定も出来ます。

上記のような感じで、入力補完ができない入力を完成させることが出来ました。
（入力補完をさせたいとなっても出来ないので要注意）
reduceでasyncする方法はこれ！

reduceのなかでasyncをしたい時の書き方がわかんなくて時間くっちゃったのでメモ。

# 答えはこれだ！！
```typescript
const itemCodeList = [
  '000000001',
  '000000002',
  '000000003',
];

const itemList = await itemCodeList.reduce(
  async (accum: Promise<Item[]>, itemCode) => {
    const prev = await accum;

    // APIとかから情報引っ張ってくるの待つ
    const item = await getItemFromCode(itemCode);

    item && prev.push(item);

    return Promise.resolve(prev);
  }, Promise.resolve([]));
```

答えが分かっちゃえば、あーなるほどねってなられる方多いかと。

**追記**
コメントでご指摘いただいて、上のようなAPIの返答を待って、次のAPIを実行して...
だと全然遅いことがわかりました。
APIを並列に捌きたい方は[こちら](https://qiita.com/_pipo_/items/6027bef4575c6f072496)を参考にしてください。

## 軽い解説

解説というほどでもないんですけど、ミソはPromiseをうまく使うことです。
・returnが`Promise.resolve(hoge)`
・初期値は`Promise.resolve([])`
の２つかな。

TypeScript使ってる場合は
・溜まってくaccumの型は`Promise<Hoge[]>`になりますね。

## 感想
時間取られちゃった原因はPromiseの理解不足かな...
reduceの初期値が`Promise.resolve([])`になるってなかなか気づけませんでした。><

## 参考
https://qiita.com/sakymark/items/e80069ae90a56e94360c
Laravelでneo4jを使う④マイグレーション


#事前準備
・app/database/labelsフォルダの作成

#

$ php artisan neo4j:make:migration  create_race_label

$ php artisan neo4j:migrate


| コマンド　| 説明 |
|:-:|:-:|
|  $label->unique('email') |  プロパティに一意の制約を追加する |
| $label->dropUnique('email')  | プロパティから一意の制約を削除する  |
| $label->index('uuid')  |  プロパティにインデックスを追加する |
| $label->dropIndex('uuid')  |  プロパティからインデックスを削除しています |
|   |   |

コマンド	説明
心理的安全性の話

# 心理的安全性とは
「無知、無能、否定的、邪魔だと思われる可能性のある言動をしても、このチームなら大丈夫だという『信念』」

---

# とても主観的な概念

「リスクのある言動をしても大丈夫だよ」と口で言うだけではダメ
相手がそう感じるまで言動で示し続ける

---

# Q: 「雰囲気が良い」「仲が良い」ってこと？

---

# A: 観点が違う
その場を収めるために雰囲気を読むようになれば、それは心理的安全性の欠如そのもの。

---

# 〇「安全(safety)」×「安穏(calm)」
「無知」「無能」「否定的」「邪魔」の不安がない。
→自分の言いたいことがきちんと言える。
→同時に、相手も言いたいことを言ってくる。

**安心して「波風を立たせる」ことができる状態**

---

# チームが成果を出すための心理的安全性

「無知」の不安がない
→わからないことはすぐに質問、相談ができる。

「無能」の不安がない
→失敗を認めたり、他のメンバーの考えを受け入れることができる。

「否定的」の不安がない
→議論をすることでより良い方向性を模索することができる。

「邪魔」の不安がない
→新しいやり方を試すことができる。

**チームがより高い成果を達成するために、心理的安全性の「土台」の上で、チームが学習し、変容する。
(心理的安全性自体が目的となるのは誤り)**

---

# 注目を集めた経緯
Googleの「プロジェクト・アリストテレス」
パフォーマンスの高いチームに共通的に見られる要素として、心理的安全性が挙げられ注目された。

---

# ただし...
「Google調べ」であることに注意。
→ダントツに優秀な人たちが集まったところ。

---

# 心理的安全性を測る質問
1. チームの中でミスをすると、たいてい非難される。
1. チームのメンバーは、課題や難しい問題を指摘し合える。
1. チームのメンバーは、自分と異なるということを理由に他者を拒絶することがある。
1. チームに対してリスクのある行動をしても安全である。
1. チームの他のメンバーに助けを求めることは難しい。
1. チームメンバーは誰も、自分の仕事を意図的におとしめるような行動をしない。
1. チームメンバーと仕事をするとき、自分のスキルと才能が尊重され、活かされていると感じる。

---

# 心理的安全性を高める取り組み
1. 仕事を実行の機会ではなく学習の機会と捉える。
1. 自分が間違うということを認める。
1. 好奇心を形にし、積極的に質問する。

---

# 心理的安全性を考えるヒント

---

# HRTの原則
こちらはGoogleのチームビルディングから出てきたもの。
背景や文脈は異なるが、心理的安全性の基礎となる部分がある。

---

>謙虚（Humility）
　世界の中心は君ではない。君は全知全能ではないし、絶対に正しいわけでもない。常に自分を改善していこう。
>尊敬（Respect）
　一緒に働く人のことを心から思いやろう。相手を1人の人間として扱い、その能力や功績を高く評価しよう。
>信頼（Trust）
　自分以外の人は有能であり、正しいことをすると信じよう。そうすれば、仕事を任せることができる。

---

# HRTの原則(裏技)
心構えではなく「しぐさ」の話。
(尊敬できてもできなくても、尊敬している「しぐさ」ができることが大切)

---

# さいごに

---

# プロジェクトはチーム戦
チームワークもエンジニアに必要なスキル
(もしかしたらプログラミングと同じくらい大事かもしれないこと)

---

# もっと知りたいときは

---

チームが機能するとはどういうことか
Amy C. Edmondson 著、野津 智子 訳
http://www.eijipress.co.jp/book/book.php?epcode=2182

Team Geek ―― Googleのギークたちはいかにしてチームを作るのか
Brian W. Fitzpatrick、Ben Collins-Sussman 著、角 征典 訳
https://www.oreilly.co.jp/books/9784873116303/

Google re:Work - ガイド:「効果的なチームとは何か」を知る
https://rework.withgoogle.com/jp/guides/understanding-team-effectiveness/steps/introduction/

---

# おしまい
特定のリポジトリだけ別のemailを設定する

# 目的

- 特定のローカルリポジトリのみコミッターのメールアドレスを任意の物に変更する方法をまとめる
- ※本記事は筆者のメモ的要素が強いため簡易的にまとめる

# 実施方法

- .gitディレクトリが見えるディレクトリで下記コマンドを実行する。

```
$ git config --local user.email "設定したいメールアドレス"
```
プロンプトに表示するパスを簡略化する

# /home/username/foo/barは長い
fishみたいに`~/f/bar`でプロンプトに表示させたい

### fishのソースを見る

[`prompt_pwd`のソース](https://github.com/fish-shell/fish-shell/blob/49c5f964701b8bb92adbe3f448e86fea4337d70e/share/functions/prompt_pwd.fish)だと

```fish
 string replace -ar '(\.?[^/]{'"$fish_prompt_pwd_dir_length"'})[^/]*/' '$1/' $tmp
```

となっている。

`string replace`はfishの関数なのでbashでこれを再現する。

```bash
function abbr_pwd() {
    local p=$(echo $PWD | sed "s@$HOME@~@")
    local directories=(${p//\// })
    local _n_d=${#directories[@]}
    local _last=${directories[_n_d - 1]}
    local _abbr_pwd=""
    if [ ${directories[0]} != "~" ]; then
        _abbr_pwd=$_abbr_pwd/
    fi
    # 本当はif [[ $p =~ (.?[^\/]{1})[^\/]*\/ ]]で処理したい
    for d in "${directories[@]:0:_n_d-1}"; do
        _abbr_pwd=$_abbr_pwd${d:0:1}/
    done
    echo $_abbr_pwd$_last
}
```

fishのコードのように正規表現で処理をしたいが、` if [[ $p =~ (.?[^\/]{1})[^\/]*\/ ]]; then; echo ${BASH_REMATCH[@]};fi`では最初のマッチ`~`のみしか得られなかった。(ちゃんと調べればやりようはありそう)



ディレクトリのパス`/home/username/foo/bar`を`~/foo/bar`にするため`sed`で置換をし、
`"/"`で文字列を分解、最後の以外の部分を先頭文字だけ残して"/"で結合、最後にbarを足すことで`~/f/bar`を得た。

`$HOME`より根のパスでは`/usr/share/theme` -> `/u/s/theme`のように`/`が先頭に来るようにした。

ただし、自ユーザ以外のホームディレクトリ下($HOMEで置換ができない)だと`/h/o(otherUser)/f/bar`みたいに冗長になる。

### PROMPTに表示する

```bash
PS1='[\u@\h] $(abbr_pwd $PWD) \$ '
```
みたいに適宜いじってください。
Android ボタンの共通化

フラグメントで共通ボタン記述。

フラグメント内のstartボタンでリスナーを記述。

```kotlin:CommonButtonFragment.kt
    override fun onStart(){
        super.onStart()
        btToday.setOnClickListener{onCommonButtonClick(it)}
        btCreate.setOnClickListener{onCommonButtonClick(it)}
    }
```
共通ボタンファンクション

```kotlin:CommonButtonFragment.kt
private fun onCommonButtonClick(view: View?){
        if(view != null) {
            if(activity != null) {
                //login画面の場合
                if (activity!!.localClassName == "TodayToDoTaskLoginActivity" || activity!!.localClassName == "TaskCreateLoginActivity") {
                    when (view.id) {
                        R.id.btToday -> {
                            val intent = Intent(activity, TodayToDoTaskLoginActivity::class.java)
                            startActivity(intent)
                        }
                        R.id.btCreate -> {
                            val intent = Intent(activity, TaskCreateLoginActivity::class.java)
                            startActivity(intent)
                        }
                    }
                //login画面以外の場合
                } else {
                    when (view.id) {
                        R.id.btToday -> {
                            val intent = Intent(activity, TodayToDoTaskActivity::class.java)
                            startActivity(intent)
                        }
                        R.id.btCreate -> {
                            val intent = Intent(activity, TaskCreateActivity::class.java)
                            startActivity(intent)
                        }
                    }
                }
            }
        }
    }
```
Pythonで参照という概念が一瞬ゲシュタルト崩壊したので、少し実験した

- あってるかは自信ない
- まじで自信ない



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534442/2d6ce403-2b6d-23cc-96ee-495b9d39a116.png)