OmniAICreator/Qiita-1.07M huggingface dataset:
AWSのサブネット一覧と使用中IP一覧を見るコマンドラインツールを作った

AWSの環境内にあるVPCとサブネットの一覧、そしてその中にあるIP一覧をコマンドラインで見るツールを作りました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/d41db186-3cef-d315-e4bc-e8a62bf9d404.png)

Python製です。boto3でAWSのリソースを見にいっています。

https://github.com/suzuki-navi/aws-list-subnets

# インストール

```sh
$ pip install aws-list-subnets
```

# 使い方

使い方はコマンド名を打つだけです。これで上のスクショのような表示がされます。

```sh
$ aws-list-subnets
```

AWSのprofileが複数あるならawscliと同じくオプションで指定します。

```sh
$ aws-list-subnets --profile PROFILE_NAME
```

`--simple` を付けると、使用中のIP一覧表示が省略されます。

```sh
$ aws-list-subnets --simple
```

`-v` を付けると、IPごとの詳細情報が表示されます。1行が長くて折り返されてしまい見づらいので `--color | less -SR`  を付けたほうがよいです。`aws-list-subnets`コマンドの出力をリダイレクトやパイプにするとカラー表示されなくなりますが、 `--color` を付けると強制的に色が付きます。

```sh
$ aws-list-subnets -v --color | less -SR
```
100日後くらいに個人開発するぞ！day031

## 今日はHTML＆CSS中級編を復習してみた！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2632394/bea32be4-626f-47f6-5095-aacccab05aa4.png)

## 今日のアウトプット
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2632394/6925c476-1ead-0654-013b-8d718db529b1.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2632394/c1aad7b7-7f98-663f-fdc9-024b6c7f61ee.png)




## 今日の備忘録

### 背景画像を指定する方法
- CSSで背景画像を指定するにはbackground-imageプロパティを用いる
    - 定型文：`background-image: url(画像のURL);`

### 背景画像の大きさを指定する方法
- background-imageで指定された背景画像は表示範囲を埋め尽くすまで繰り返し表示される性質がある
- `background-size: cover;`を指定すると1枚の画像で表示範囲を埋め尽くすように画像の大きさが拡大縮小する

### 要素を中央に配置する方法
- 中央に配置するためにはmarginの左右にautoを指定する必要がある
    - 例：`margin:0 auto;`
- marginにautoを指定する場合は必ずwidthを併せて指定する
- 上下のmarginにautoを指定することはできない


### 要素を透過させる方法
- 要素を透明にするにはopacityプロパティを使う
    - 例：`opacity:0.5;`
- 透明度は完全に透明「0.0」から完全に不透明「1.0」の間で指定します。

### 文字の間隔を指定する方法
- letter-spacingプロパティを用いることで文字の間隔を指定できる
    - 例：`letter-spacing：10px；`


### ボタンを角丸にする方法
- 要素の角を丸めるにはbroder-radiusプロパティを用いる
    - 例：角を半径10pxの円にしたい
        - `.button{border-radius:10px;}`

### アイコンを用いる方法
- アイコンを表示するにはFont Awesomeを使うと便利
    - https://fontawesome.com
- Font Awesomeは以下の手順で使用できる
    -　Font AwesomeのCSSファイルをHTMLの<head>タグで読み込む
        -　`<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">`
    - <span>タグにfaクラスとfa-アイコン名クラスを指定する
        - `<span class="fa fa-twitter"></span>`



### 背景色のみ透明にする方法
- opacityとrgbaの違い
    - opacity：要素の中身全てを透明にする
    - rgba:特定の色だけ透明にする
- 色を指定するrgb
    - rgbは色の指定方法の一種で3つの値を組み合わせて色を指定する
        - 例：背景色を指定する場合
            - `{background-color:rgb(255,147,30);}`
        - （例：#ffffffのような記法で背景色を指定する場合）
            - `{background-color:#ff931e;}`
- rgbaで特定の色を透明にする場合
    - rgbaは4つ目の値0~1で透明度を指定する
        - 例：背景色の透明度を0.5にしたい場合
            - `{background-color:rgba(255,147,30,0.5);}`


### 要素同士を重ねて表示させる方法
 - `position:absolute;`を用いると要素を重ねて表示できる
    - 例
        - `.box{position:absolute;}`

### 要素の位置を指定する方法
- サイト全体の左上部分を基準として位置を指定する場合
    - 例：サイト全体の左上部分から50px下の位置を指定する場合
        - `top:50px`
    - 例：サイト全体の左上部分から50px左の位置を指定する場合
        - `left:50px`
- `position:relative`を親要素に指定するとその親要素の左上部を基準位置にすることができる
[Django]プロジェクト前にやっておいた方が楽な設計

## この記事のメリット

- ソースコード管理が楽になる
- 多人数のプロジェクトのときにコンフリクトを起こしにくくなる
- 大幅な変更にも対応しやすい
- ファイルが細かく分かれているためloggingで発見しやすい

## viewsの構成作る

startappでsampleディレクトリを作ることを仮定する

```
sample/
    ├ views.py
    ├ src/
       └ first_func/
            └ first_func_get.py
              first_func_post.py

```

views内にある1つの関数に対してMethodで呼び出す関数を変更する。
例えば下のようにgetが飛んできた場合はfirst_func_get()という関数をfirst_func_post.pyから呼び出す。

その他の認証ユーザーのみや共通的なロギングなどもviews内の関数に書くかデコレーターを使うのがおすすめ。

```python:views.py
from .src.sample.first_func_get import first_func_get
from .src.sample.first_func_post import first_func_post

def sample(request):
    if request.method == "GET":
        return first_func_get(request)
    elif request.method == "POST":
        return first_func_post(request)

```

通常であればviews.pyに各処理と全く同じように書く。

```python:first_func_get.py
from django.shortcuts import render

def first_func_get(request):

    return render(request,'sample/sample.html')

```

このようにファイルを関数が毎に細かく書くことでloggingで出力するときに確認が容易になる


## 共通で使う自作ライブラリ、フレームワークの管理

Djangoをmanage.pyのあるディレクトリから見てPathを指定する。
問えば下のようにlibというディレクトリにありライブラリをimportするときは

```python:views.py
from lib.ファイル名 import 呼び出したい関数

```

のように記述する

### ディレクトリ構成を考える

```
project/
    ├ lib/
       └ samplelib.py

```

### ほぼ全ての関数に使う処理を考える

libディレクトリに入れるライブラリは共通で使うを処理を行うものを入れる
例えば毎回処理に掛かる時間を計測するライブラリやrequestの中身やreturnの中身を毎回編集するような処理。

## settingsをカスタマイズする

settings.pyはなるべく最初に細かくカスタマイズすることで開発、運用を効率的に行うことが容易になる。

### CICD向けに設定を変える

CICDを効率的にやるためにデプロイ時とローカルで行う処理を環境変数で切り替えられるようにする。
例えばDebug変数やデータベースのパラメーターなどは優先的に環境変数に切り替える。

### PYTHONPATHの設定をする

PYTHONPATHを設定することでimport文を短くすることができる
これは好みなので必須ではない


### loggingの設定をする

settings.pyにloggingの設定を記述することでログの管理がだいぶ楽にやることができる

```python:setting.py
logging.basicConfig(
    level=logging.DEBUG,
    format='[%(levelname)s][LineNo.%(lineno)d][FileName:%(filename)s]: %(message)s',
    datefmt='%m/%d/%Y %I:%M:%S %p'
)

```

出力

```
[DEBUG][LineNo.43][FileName:views.py]: loggingしたいメッセージ
```

## まとめ

ここに書かれているおすすめの設定は一部だけれどもこれだけでもだいぶソースコードの管理や修正作業、運用保守も楽になってくる。
質問があれば受け付けるのでメッセを飛ばして欲しい。
2022/5/23主にITとかセキュリティの記事

日立など、脱PPAP　パスワード付きファイル廃止相次ぐ(5/21)
https://www.nikkei.com/article/DGXZQOUC112B30R10C22A5000000/

Microsoft、SQL Serverを標的とした新しいブルートフォース攻撃について警告(5/21)
https://news.mynavi.jp/techplus/article/20220521-2348587/

不審メールにご注意ください　地域包括支援センター利用者等のメールアドレス流出疑いについて（5月20日）仙台市
https://www.city.sendai.jp/kaigo-suishin/040520houkatu-maleadress-ryusyutuutagai.html

Appleの最新CMに見る「プライバシー情報搾取」の現状(5/20)
https://www.itmedia.co.jp/pcuser/spv/2205/20/news048.html

「ロシアによるウクライナ侵攻から考える情報リテラシー、サイバーセキュリティ」と題し、（株）第一生命経済研究所　柏村　祐氏によるライブセミナーを2022年6月21日（火）開催!!
https://www.jiji.com/sp/article?k=000001967.000032407&g=prt

過去12カ月に従業員の電子メール関連のミスによってデータを損失した企業は6割超(5/21)
https://news.mynavi.jp/techplus/article/20220521-2348733/

Windows 11、幅広い展開の準備が整う(5/21)
https://www.zaikei.co.jp/article/20220521/673242.html

Vivaldi、ルノーの新型車両システムのブラウザとして採用へ(5/21)
https://www.zaikei.co.jp/article/20220521/673214.html

米Red Hat、「Red Hat Enterprise Linux 9」をリリース(5/21)
https://www.zaikei.co.jp/article/20220521/673218.html
エロマーケットプレースの研究シリーズ 1: エロマーケットプレースを調べたい

# エロマーケットプレースという言葉を定義しよう

以下のようなエロいコンテンツを置いている場所で

- FANZA ビデオ
- FANZA 同人
- FC2 コンテンツマーケット
- FC2 ビデオ
- DLSite
- KoeKoe

そのコンテンツが以下のような情報を持っている

- タグ、タイトル、本文などコンテンツを分類する情報
- 再生数、販売数、お気に入り数、評価数、などのコンテンツの品質や成功度合いを示す情報

僕は、これを今日からエロマーケットプレースと呼ぶことにする

# まずやりたいことを考える

二つ考えた

- タグ、タイトル、本文などから、売り上げなどを予測する（順問題）
- どんなタグ、タイトル、本文が売れているのか（逆問題）

逆問題の方が圧倒的に面白そう

# データや具体的なサイト名は公開しない

これは、人のサイトをクロールして情報を取得してくる必要がありそうなので、その情報やサイト名などは公開しない方針とする。

進捗とできる限りのコードは載せていこうと思う。

# 分析どうやってやる？

例によってパワフルなGPUもないし、クラウドもめんどくさいので、分析に関しては LightGBM を使って feature_importance を取るのが良いのではないか

特徴量どうやって出す？

タグの並び、本文、タイトルをそれぞれ TG-IDF でベクトル化、その他価格とか数値系の情報を結合すれば良いのかな？

やったことないけど

# クロールどうやってやる？

Scrapy ってやつを使ってみるか

# 次は

https://qiita.com/c1z/items/3df2a2f7e424958bb706

Scrapy を使ってみます
Google Chrome起動時にウィンドウを２つ同時起動する方法

# 経緯
ながら勉強が取り柄のだめ人間です。

普段から２つのウィンドウを開いて、左でYoutube、右で調べ物やエキセントリックネットサーフィングなどをやっております。娯楽が尽きないいい世界になりました。

さて、毎回ドラッグ&ドロップで一つのウィンドウから新しいウィンドウを生み出していたのですが、<font size="5">めんどくさい!</font>

そこで色々調べたのですがついにいい方法に辿り着きました。
# 方法
この方法では特別な拡張機能やプログラムは一切登場しません。Chromeの設定のみでいけます。
### 手順
1. Chrome起動時に開きたいページを各ウィンドウ(皆様の自由な数、形で結構だとおもいます)に表示。
1. いずれかのウィンドウの右上の点３個ボタンを押して「設定」
1. 左上のハンバーガーメニュー(三本線ボタン)を押して「起動時」
1. <font color="red">「前回開いていたページを開く」を選択</font>

以上で、終了です。

# 注意！！
Chromeを終了するときはウィンドウを閉じるのではなく、アプリごと一気に閉じる必要があるかと思います。

では皆様良いながらライフを！
Excel VBA+seleniumでjqueryを利用する方法

Excel VBAでseleniumを利用してスクレイピングツールを作っているときに、javascriptのコードを実行しているのですが、jqueryでコードが書きたくなりました。
しかし、調べたけれど見当たらなかったので残します。

実行するjavascriptのコードは下記の通りです。

```js
  var script = document.createElement('script');
  script.setAttribute("src", "https://code.jquery.com/jquery-3.6.0.min.js");
  script.setAttribute("integrity", "sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=");
  cript.setAttribute("crossorigin", "anonymous");
  document.body.appendChild(script);
```
scriptタグを挿入して実行するコードです。

Excel VBAで実行するコードは下記の通りです。
```vb
   'javascriptのコードを指定
    strJS = "var script=document.createElement('script');return script.setAttribute('src','https://code.jquery.com/jquery-3.6.0.min.js'),script.setAttribute('integrity','sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4='),script.setAttribute('crossorigin','anonymous'),document.body.appendChild(script)"
    
    'javascriptのコードを実行
    Driver.ExecuteScript (strJS)
```
これでjqueryが読み込まれます。

そうしたら後はjqueryが使えます。例えばjqueryでGoogleのトップページのタイトル画像のURLを取得する場合は下記のように書けます。

```vb
    'jqueryの実行 googleのタイトル画像のurlを取得
    strJS = "return $('.lnXdpd').attr('src')"
    
    'javascriptのコードを実行して結果をメッセージボックスに表示する
    MsgBox Driver.ExecuteScript(strJS)
```

上記のようにjqueryの構文が使えるようになります。

ポイントとしてscriptタグを埋め込んでライブラリを読み込むのに少し時間がかかるため、scriptタグを埋め込むコードと読み込んだライブラリを利用するコードの間にwaitを入れて時間に余裕を持たせた方がいいです。

今回はjqueryを利用しましたが、html2canvasなど他のライブラリも同様の方法で利用することができます。

作成できるプログラムの幅が広がるので楽しいですね。

最後に今回作成したプログラムの全文を載せて置きます。
何か参考になれば嬉しいです。
```vb
Sub test()
    Dim strJS As String
    Dim result As String
    Dim Driver As New Selenium.ChromeDriver
    
    Call Driver.Start("chrome")
 
    'Googleのページを開く
    Driver.Get ("https://www.google.com/?hl=ja")
    
    '表示が終わるまで待機する(ミリ秒指定で2秒)
    Driver.Wait 2000
    
    'jqueryの読み込み
    strJS = "var script=document.createElement('script');return script.setAttribute('src','https://code.jquery.com/jquery-3.6.0.min.js'),script.setAttribute('integrity','sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4='),script.setAttribute('crossorigin','anonymous'),document.body.appendChild(script)"
    
    'javascriptのコードを実行
    Driver.ExecuteScript (strJS)
    
    'ライブラリの読み込み時間確保
    Driver.Wait 3000
    
    'jqueryの実行 googleのタイトル画像のurlを取得
    strJS = "return $('.lnXdpd').attr('src')"
    
    'javascriptのコードを実行して結果をメッセージボックスに表示する
    MsgBox Driver.ExecuteScript(strJS)
    
    'ウィンドウを閉じる
    Driver.Close

End Sub
```
Warning: An update to ○○ inside a test was not wrapped in act(...)　がでる。

React18でReact Tesing Libraryを使っていたら、
```React:
      Warning: An update to App inside a test was not wrapped in act(...).
      
      When testing, code that causes React state updates should be wrapped into act(...):
      act(() => {
        /* fire events that update state */
      });
      /* assert on the output */
      
      This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
          at App
```
という警告がでる。

言われた通りにact()でstateを更新した部分を囲んでみる。
```React:
import { act } from "react-dom/test-utils";

const Sample = () => {
  const [user, setUser] = useState(null);

  const getUser = async () => {
    return { id: "1", name: "Robin" };
  };

  useEffect(() => {
    const loadUser = async () => {
      const user = await getUser();
      act(()=>setUser(user));
    };
    loadUser();
  }, []);

return <></>
}
```
するとエラーが変わった。
```React:
Warning: The current testing environment is not configured to support act(...)
```
React18ではこのような警告がでで、テスト前に
**globalThis.IS_REACT_ACT_ENVIRONMENT = false**
と書くと無視できるらしい。

https://ja.reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html



設定ファイルがわからなかったのでテストに直接書き込んでみる。

```React:
describe("sample", () => {
  // warning 回避
  beforeEach(() => {
    globalThis.IS_REACT_ACT_ENVIRONMENT = false;
  });

  test("async await", async () => {
    // 非同期で最後に存在する場合は findBy を使います。
    render(<Sample />);
    expect(screen.queryByText(/Signed in as/)).toBeNull();

    screen.debug();
    expect(await screen.findByText(/Signed in as/)).toBeInTheDocument();

    screen.debug();
  });
});
```
Warningが消えて解決した。

そのうちこれを書かなくてもいいようにアップデートされるらしい。
Chrome Extension(拡張機能) MV3 × Firebase(RealTime Database)

## 開発する拡張機能の前提 
- MV(manifest version)3  
- Firebaseを使用
- 「Node.js」, 「npm」を使用しない
- 開発に使用しているのはHTML, CSS, JavaScriptのみ
- contentもしくはbackgroundからFirebaseを使用したい

## 本題
まず簡単に拡張機能でFirebaseを使用する手順を以下に示します。
1. 使用したいFirebase アプリケーションのコードを取得
1. インポートしたコードを修正
1. Manifestを修正する

#### 1.使用したいFirebase アプリケーションのコードをインポートする
npm経由でのインストールができないので、必要なアプリケーションのコードだけを取得します。
コードは以下のURLから取得します。
https://www.gstatic.com/firebasejs/VERSION/firebase-SERVICE.js
- VERSION：ライブラリのバージョン
- SERVICEはfirestoreなどのSDK名

URLを開くとコードが表示されるので、全体をコピーし自身の拡張機能内にペーストします。
この手順でapp(必須)とdatabaseのコードをローカルに取得しました。

例）自分はRealTime Databaseを使用したかったので以下のURLからコードを取得しました（１つ目は必須）
また、コードは「firebase」というディレクトリを作成し、その中に保存しました。
URL
- https://www.gstatic.com/firebasejs/9.6.4/firebase-app.js
- https://www.gstatic.com/firebasejs/9.6.4/firebase-database.js

ローカル
```
    ├── firebase
    │   ├── firebase-app.js
    │   └── firebase-database.js
```

#### 2. インポートしたコードを修正
注意点として、firebaseのすべてのサービスファイルがfirebase-app.jsに依存しているため、プロジェクト内のfirebase-app.jsの相対パスへのインポートを編集する必要があります。

自分はapp以外の「firebase-〇〇.js」のフォルダ内で「app」のインポート箇所を検索し、ローカルのfirebase-app.jsを参照するように修正しました。

あとは、ライブラリをインストールした場合と同様に操作を行えます。

#### 3. Manifestを修正する
インポートを使用しているため、firebaseを使用するコードはモジュールである必要があり、manifest.jsonファイルに次のように記述します。
```json
"background": {
    "service_worker": "〇〇.js",
    "type": "module"
  }
```

## 補足
#### ローカルに持ってきたライブラリを使用してRealTime Databaseにデータを挿入する処理の例
```js
import * as firebase from "../firebase/firebase-app.js"; //ローカルを参照
import * as database from "../firebase/firebase-database.js"; //ローカルを参照

// Initialize Firebase
var config = {
    apiKey: "",
    authDomain: "",
    databaseURL: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: ""
};
const app = firebase.initializeApp(config);
const db = database.getDatabase(app);

chrome.runtime.onMessage.addListener(function (info) {
    database.set(database.ref(db, 'aaa'), {
        aa: "aaaa",
    });
    return true
})
```

## 参考文献
本記事は以下の内容をもとに執筆しました
https://stackoverflow.com/questions/69160743/connect-a-manifest-version-3-chrome-extension-to-a-firestore-database

## 終わりに
最後までお読みいただきありがとうございました。
少しでもお役に立てれば幸いです。

また、内容について不備があればコメントをいただけると幸いです。
Simple Data Integrator (SDI)  ユースケース

#  社内システム間データ連携
企業内の様々なシステム間異なるフォーマットのデータを連携します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560129/7fc21a71-2fe1-c791-445e-8add96bb7b7c.png" width="500">

#  複数拠点のデータ一元管理
複数拠点展開する企業のデータ一元管理、データ活用。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560129/05948d79-e0c5-4ef0-bf6a-84ae458e5d69.png)

#  オンプレミスとクラウド、マルチクラウド間のデータ双方向連携
拠点と拠点、拠点とクラウド間柔軟な接続設計、双方向データ連携が可能です。
### オンプレミスとクラウド
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560129/c5072c35-b94c-4dec-99b2-b3f2526c47fa.png)


### マルチクラウド間データ連携
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560129/0f1d0724-a9cc-4193-9ad5-dbbab2c976d7.png)


# CSV、Excel、JSON、XML ファイルを定期的に自動作成・自動取り込み
CSV/Excel ファイルを定期的に自動作成・自動取り込み　（ファイルシステム、FTP/FTPS/SFTP/AWS S3/OCI Object Storageを対応）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560129/9a89f192-0718-492a-b4b6-f6dc92f933ca.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560129/bcc6d1cb-67e0-4a5d-b0be-923ab5abecd3.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560129/1fb319d7-33ef-497d-b6d3-0af7158beb54.png)

# Web Service や Web API との連携
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560129/cd32d2bb-c827-402d-9534-7559f90f9be3.png)


#  データベース可用性監視、 不整合データの検知
データベースの可用性監視、イレギュラーデータや欠落データ、不整合データなどを検知、通知することができます。
※SDI WEB Service経由してリモートデータベースの監視も可能です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560129/8deb03f9-4d07-3166-f27f-b856692f95f9.png" width="530">


以上です。

[Simple Data Integrator (SDI) 実践入門](https://qiita.com/RiCCT/items/3dc109ff82546459a8b0 "Simple Data Integrator (SDI) 実践入門")

# 参考資料
[SDI日本語公式サイト](https://ja.sdi.ricct.com/ "SDI日本語公式サイト")
[SDI English Official Website](https://sdi.ricct.com/ "SDI English Official Website")
Pulumi で AWS ELBv2 (ALB) と ACM発行の証明書を用いた Listener を作成する際に "UnsupportedCertificate" のエラーが発生したときに疑うこと


## はじめに

- Pulumi で AWS Elastic Load Balancing v2 (type は ALB) (以下 ALB) を AWS Certificate Manager で発行する証明書を用い Listener の作成を試みたところ "UnsupportedCertificate" のエラーが発生しました
- 最初 "UnsupportedCertificate" だけでは何が原因かわからなかったため調査した結果を記載します

## 先に結論を...

- 私の場合 AWS Certificate Manager (ACM) の証明書発行(認証)が終わっていないため "UnsupportedCertificate" が発生していました

## 実行環境

```zsh
% pulumi version
v3.33.1
% ag -A1 "@pulumi/aws@" yarn.lock | grep -o "version .*"
version "4.38.1"
```

## 作成しようとした resource について

:::note warn
説明に関連しない VPC や SecurityGroup などのリソース、パラメータなどは省略しています
:::

Pulumi JavaScript(TypeScript) と Pulumi Amazon Web Services (AWS) provider を用いてリソースの作成を行っていました
下記は構築を試みたリソースの一部を再現したコードです

```ts:index.ts
import * as pulumi from "@pulumi/pulumi"
import * as aws from "@pulumi/aws"

const testCertificate = new aws.acm.Certificate("test-certificate", {
  domainName: "sample.example.com",
  validationMethod: "DNS"
})

const testLoadBalancer = new aws.lb.LoadBalancer("test-load_balancer", {
  internal: false,
  loadBalancerType: "application",
  // NOTE: subnets, securityGroups, accessLogs 等の定義は省略
})
const testLBTargetGroup = new aws.lb.TargetGroup("test-lb-target_group", {
    port: 80,
    protocol: "HTTP",
    vpcId: testVpc.id, // VPC作成の定義は省略
})
const testLBListener = new aws.lb.Listener("test-lb-listener", {
    loadBalancerArn: testLoadBalancer.arn,
    port: 443,
    protocol: "HTTPS",
    sslPolicy: "ELBSecurityPolicy-2016-08",
    certificateArn: testCertificate.arn,
    defaultActions: [{
        type: "forward",
        targetGroupArn: frontEndTargetGroup.arn,
    }],
})
```

問題となった処理は、 AWS Certificate Manager (ACM) で証明書を発行する処理です
今回私は、 「NS レコードの登録が完了していないドメイン」に対して証明書の発行を行うも NS レコードの登録が完了していないため、 認証に必要な A レコードももちろん参照することはできません

```ts
const testCertificate = new aws.acm.Certificate('test-certificate', {
  domainName: 'sample.example.com',
  validationMethod: 'DNS',
});
```

## エラーメッセージ

下記は `pulumi up` 時に出力されていたメッセージ (一部置換済み) です
"UnsupportedCertificate" は字のごとく有効な証明書出ないことを表しており、証明書関連の処理を洗ったところ今回の原因にたどり着きました

```console
* error creating ELBv2 Listener (arn:aws:elasticloadbalancing:***:***:loadbalancer/app/xxx-lb/xxxxxxxx): UnsupportedCertificate: The certificate 'arn:aws:acm:***:***:certificate/xxxx-xxxx-xxxx-xxxx-xxxx' must have a fully-qualified domain name, a supported signature, and a supported key size.
      	status code: 400, request id: xxxx-xxxx-xxxx-xxxx-xxxx
```

念のため、 AWS Console から AWS Elastic Load Balancing v2 (type は ALB) (以下 ALB) の作成試みたところ、
当たり前ですが、有効化が完了していない証明書を 選択することはできませんでした。

## Reference

Terraform を用いて構築する際に、 似た条件で "UnsupportedCertificate" が発生し対応された方の記事を一応貼っておきます
※ Pulumi Amazon Web Services (AWS) provider は中身が terraform のため

https://dev.classmethod.jp/articles/dnsrecord-acm-alb-with-terraform/

https://github.com/pulumi/pulumi

https://github.com/pulumi/pulumi-aws
【TypeScript】オブジェクトに配列が含まれている場合のユーザー定義型ガード

TypeScript の「ユーザー定義型ガード」(User Defined Type Guard) で、オブジェクトのデータ型を判定する方法です。
基本の形と、プロパティに配列が含まれている場合について備忘として残しておきます。

## 1. 一般的なオブジェクトのユーザー定義型ガード

前提として、普通のオブジェクトのユーザー定義型ガードです。

```person.ts
// 判定対象のデータ型
type Person = {
  name: string;
  age: number;
};
```

```sample.ts
// Person オブジェクトか否かを判定するユーザー定義型ガード
function isPerson(arg: unknown): arg is Person {
  if (!arg) return false; // null, undefined の場合を除く
  const person = arg as Person; // 型アサーションで Person 型とみなす
  return typeof person.name === 'string' && typeof person.age === 'number';
}
```

型アサーション（Type Assertion）を適用した上で、`person.name` および `person.age` のデータ型を個別に判定しています。

## 2. 配列を含んだオブジェクトのユーザー定義型ガード

### 2-1. プロパティに文字列配列を含んでいる場合

次のように、プロパティに `hobbies: string[]` という文字列配列を含んでいる場合についてです。

```person.ts
// 配列を含んだデータ型
type Person = {
  name: string;
  age: number;
  hobbies: string[];
};
```

```sample.ts
// 配列を含むオブジェクトを判定するユーザー定義型ガード
function isPerson(arg: unknown): arg is Person {
  if (!arg) return false; // null, undefined の場合を除く
  const person = arg as Person; // 型アサーションで Person 型とみなす

  // 配列か否かを判定
  if (!Array.isArray(person.hobbies)) return false;
  // 全てstring型か否かを判定
  if (person.hobbies.some((v) => typeof v !== 'string')) return false;
  
  return typeof person.name === 'string' && typeof person.age === 'number';
}
```

配列の判定は、次のように行っています。
① Array.isArray() メソッドで配列か否かを判定する。
② some() メソッドで、配列内の要素が全て string 型かを確認する。

＜参考サイト＞
・[Type guards for types of arrays](https://stackoverflow.com/questions/49813443/type-guards-for-types-of-arrays)

### 2-2. プロパティにオブジェクト配列を含んでいる場合

次に、プロパティの配列の要素がオブジェクト型の場合です。

```person.ts
// オブジェクトを要素とする配列を含んだデータ型
type Person = {
  name: string;
  age: number;
  purchase: Purchase[];
};

// purchaseプロパティで使用する配列
type Purchase = {
  name: string;
  quantity: number;
};
```

考え方は文字列配列と同じですが、対象のプロパティ（purchase）についてもユーザー定義型ガードを用意して、ループ処理で判定を行います。

```sample.ts
// プロパティのPurchase型を判定するユーザー定義型ガード
function isPurchase(arg: unknown): arg is Purchase {
  if (!arg) return false;
  const purchase = arg as Purchase;
  return  typeof purchase.name === 'string' && typeof purchase.quantity === 'number';
}

// 配列を含むオブジェクトを判定するユーザー定義型ガード
function isPerson(arg: unknown): arg is Person {
  if (!arg) return false;
  const person = arg as Person;

  // 配列か否かを判定
  if (!Array.isArray(person.purchase)) return false;
  // 全てPurchase型か否かを判定
  if (person.purchase.some((v) => !isPurchase(v))) return false;

  return typeof person.name === 'string' && typeof person.age === 'number';
}
```
Django REST Frameworkのバージョン管理を誤った結果、AttributeErrorでドハマりした。


### 環境情報

- Python...3.8.13
- Django...3.2
- Django REST Framework.. 3.2.0(※注目)
- 認証方式... Cookie認証

### 実行失敗したコマンド

```
# マイレーションファイルを生成
python manage.py makemigrations shop
```


### 出力エラーの一部

```
File "/Users/ユーザー名/python-projects/drf_vue_cookie_auth/config/urls.py", line 23, in <module>
    path('api-auth/', include('rest_framework.urls')),

File "/Users/ユーザー名/opt/anaconda3/envs/djangoenv/lib/python3.8/site-packages/rest_framework/urls.py", line 23, in <module>
    url(r'^login/$', views.login, template_name, name='login'),


AttributeError: module 'django.contrib.auth.views' has no attribute 'login'
```
マイグレーションファイル関連のエラーというより別の箇所のAttributeErrorが原因のエラーのよう。
特に最終行のエラー文が気になったので、下のようにPyCharmで遡って該当のdjango.contrib.auth.viewsを見てみたが、確かに'login'なんてフィールドもメソッドも無かったので変だと思った。
![スクリーンショット 2022-05-22 20.49.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/793102/fb510bf1-9b2c-1bf6-7bc7-697f9fdbd436.png)

その'login'を読もうとしているのが、site-packages/rest_framework/配下のurls.pyだが
`url(r'^login/$', views.login, template_name, name='login'),`
エラー文にも書いてあるこの部分がおかしいような気がしてきた。
「でもライブラリのコードじゃん。一体なぜ？　というか、こんなの自分で直せないしどうしようか。」

という事で自分でよく考えた結果、以下の対応を試してみた。

### 行った対応(解決策)

```
pip install -U djangorestframework
```

上記コマンドでdjangorestframeworkのパッケージ更新を行ってみたところ、rest_framework/urls.pyが更新され以下のようになった。
![スクリーンショット 2022-05-22 20.53.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/793102/531cd56f-cd3b-443a-8d06-1289c23d5fe7.png)

```diff_python
   
- url(r'^login/$', views.login, template_name, name='login'),
+ path('login/', views.LoginView.as_view(template_name='rest_framework/login.html'), name='login'),

```
エラー文の`url(r'^login/$', views.login, template_name, name='login')`なんて記述は無くなり、これが正しいの姿のようだ。
対処を考える中でなんとなく予想したが、古いライブラリのコードを取得していた模様。
その原因がどうしても気になり、historyでコマンド履歴を見てみたら過去にこんなコマンドを発見。

`pip install djangorestframework=3.2`

おそらくDjangoのLTSのバージョン(3.2)と混同して、Django REST Frameworkを同じようにしたら古いバージョンになってしまったと... ちなみにDRFの最新は3.13.X系。  DRFで3.2となると2015年のものになるようです(笑)
(参考:　https://www.django-rest-framework.org/community/release-notes/)

DRFのバージョンちゃんと元に戻したところで実行失敗したマイグレーションコマンドを再度打ったら無事に成功しました。
些細なミスだけど、Django初学者を脱するための一歩になるといいな。
部分テンプレートを使って記述を楽にしよう

WEBアプリケーション作成をしようとプログラミングを学んでいるとファイルは違うけど中身の記述は同じ記述をするファイルを作成することが多々あります。

毎回同じコードを入力するのがめんどくさいので部分テンプレートを使ってコードを使い回しする方法を紹介します。
今回はRubyonRailsのフレームワークを使用してWEBアプリを作成する際に使用するrenderメソッドについて解説していこうと思います。

今回はWEBアプリでユーザーが画像やテキストを投稿するWEBアプリ作成する際に新規投稿フォームと編集フォームの中の形が同じ場合を想定して記述していきます。

:::note info
部分テンプレートなしver.
:::
```new.html.erb　　＃新規投稿ページ
<div class="contents">
  <div class="content">
    <h3>投稿する</h3>
    <%= form_with(model: post, local: true) do |form| %>
    <%= form.text_field :image, placeholder:"Image" %>
    <%= form.text_area :text, placeholder:"text" , rows: "10" %>
    <%= form.submit "SEND" %>
    <% end %>
  </div>
</div>
```
```edit.html.erb　　＃編集ページ
<div class="contents">
  <div class="content">
    <h3>編集する</h3>
    <%= form_with(model: post, local: true) do |form| %>
    <%= form.text_field :image, placeholder:"Image" %>
    <%= form.text_area :text, placeholder:"text" , rows: "10" %>
    <%= form.submit "SEND" %>
    <% end %>
  </div>
</div>
```


:::note info
form部分が同じなのでをrenderを使って楽するコードに変える
renderメソッドにparticalオプションで読み出す部分テンプレートディレクトリ、ファイルを指定する。
locals: { 変数名: 値 }のオプションをつけて部分テンプレート内で使う引数を渡すことができる。
:::

先頭に_(アンダーバー)を付けたテンプレート用ファイル名に
共通の処理を記述する。
```_form.html.erb　　　　＃form部分を記述するテンプレートファイル
    <%= form_with(model: post, local: true) do |form| %>
    <%= form.text_field :image, placeholder:"Image" %>
    <%= form.text_area :text, placeholder:"text" , rows: "10" %>
    <%= form.submit "SEND" %>
    <% end %>
```
```new.html.erb　　＃新規投稿ページ
<div class="contents">
  <div class="content">
    <h3>投稿する</h3>
    <%= render partial: "form", locals: { post: @post} %>
  </div>
</div>
```
```edit.html.erb　　＃編集ページ
<div class="contents">
  <div class="content">
    <h3>編集する</h3>
    <%= render partial: "form", locals: { post: @post} %>
  </div>
</div>
```

それぞれのファイルの記述がシンプルになり可読性や保守性が向上するのでぜひ覚えておきたいメソッドです。
rails以外のフレームワークなどにもこういった記述はあるようなので今後は他の言語を学習する際にまた記事にしていきたいと思います。
【REDMINE】Markdown で折りたたみを表現する方法

# 概要

REDMINEのマークダウンでの記載で折りたたみする場合の記載方法が独自にあったのでメモ

# 折りたたみ

折りたたみ表示させたい情報を `{{collapse` と `}}`で囲う

## 記載例

```
{{collapse

記載したい長い文章


}}

```

# 参考
https://www.redmine.org/issues/12167
/> で終わるタグを<...><.../>に変換する

```<color=#fff/>```みたいな短縮形のタグを```<color=#fff></color>```のような形に戻したかったのですが、良い方法が見つからなかったので下記のようなプログラムで変換しました。

```
using System.Text.RegularExpressions;

/// <summary>
/// <aaa=bbb/>形式のタグを<aaa=bbb></aaa>に変換
/// </summary>
/// <param name="_msg">タグが含まれる文字列</param>
/// <returns>変換後の文字列</returns>
static string replaceShortTag(string _msg)
{
    string retMsg = _msg;
    var matches = Regex.Matches(_msg, @"<.[^<>]*/>"); // <で始まり/>で終わる
    foreach(var match in matches)
    {
        string matchStr = match.ToString();
        string data = match.ToString().TrimStart('<').TrimEnd('>').TrimEnd('/'); // </>を削除
        string tag = Regex.Replace(data, @"[=| ].*","");
        string longTag = $"<{data}></{tag}>";
        retMsg = retMsg.Replace(matchStr, longTag);
    }
    Debug.Log(_msg + " -> " +retMsg);
    return retMsg;
}
```

```<style=name>太郎</style>こんにちは。<style=flag1/><link=msgptr:0/>```
このようなメッセージが
```<style=name>太郎</style>こんにちは。<style=flag1></style><link=msgptr:0></link>```
こんな形に変換されるようになりました。
**ただし、```<br/>```等、```<br></br>```のように変換されて困るものはあらかじめ除いておく必要があります。**

UnityのTextMeshProは短縮タグを認識してくれなかったのですが、これで正しく認識してくれるようになりました。
【TypeScript】EventEmitterを継承してクラスを作る

typescriptにおいてEventEmitterを継承したクラスを作成します。

## EventEmitterとは？

Node.jsの標準モジュールの一つ、Eventsから読み込めるクラス。イベントリスナー(イベント実行時に動く関数)の登録を行い、イベントの実行をトリガーとして登録した処理を実行することができる。

## EventEmitterを使ってみよう

EventEmitterをimportし、インスタンスを作成します。

```typescript
import { EventEmitter } from "events";
const emitter = new EventEmitter();
```

イベントリスナーの登録処理は```eventEmitter.on```もしくは```eventEmitter.addListener```で行います。ちなみにこの二つは[同じものだそうです](https://github.com/nodejs/node-v0.x-archive/blob/master/lib/events.js#L188)。

```typescript
emitter.on('test', () =>{
    console.log('hello, world!');
})
```
これで実行。```'hello, world!'```の出力が得られます。
```javascript
emitter.emit('test');
```



イベントリスナーに引数を持たせるときはこのように書きます。
```javascript
emitter.on('testArgument', (name: string) =>{
    console.log(name + ', hello, world!');
})


emitter.emit('testArgument', "takashi");
```
また、一度しか実行されないリスナーの登録```eventEmitter.once```やイベントリスナーの削除を行う```eventEmitter.removeListener```(もしくは```eventEmitter.off```)等が用意されています。

```ts
emitter.once('testObj', (obj:any) =>{
    console.log(obj.name + ', hello, world!');
})
emitter.emit('testObj', {name: "hiroshi"});
emitter.emit('testObj', {name: "hiroshi"}); // こちらは実行されない
```

```ts
const testFunc = () => {
    console.log("testFunc");
}
emitter.on("testFunc", testFunc);
emitter.emit('testFunc'); // 実行される
emitter.removeListener("testFunc", testFunc);
emitter.emit('testFunc'); // 実行されない
```





## EventEmitterクラスの継承

EventEmitterを継承してイベントを登録できるクラスを作成してみましょう。

```ts
import { EventEmitter } from "events";


export class TestEmit extends EventEmitter{
    constructor(){
        super();
        this.on("test", (name: string)=>{
            console.log(name + ": hello, world!");
        });
    }
}
```

このファイルを外部ファイルから読み込み、```test```イベントを動かしてみます。また、同時に外部ファイルから```newEvent```イベントを新しく登録し、動かしてみます。

```ts
import {TestEmit} from "./testClass";

const testEmit = new TestEmit();

testEmit.emit("test", "hiro");
// hiro: hello, world!


testEmit.on("newEvent", ()=>{
    console.log("newEvent");
});
testEmit.emit("newEvent");
// newEvent
```

これで外部から読み込み、イベントを操作することができました。
過剰ともいえるサイトのご利用条件（利用規約）をつくってみた

## これはすごい。やりすぎ

と、社内から賞賛を受けましたので、その一部を掲載してみます。

## 利用規約の考え方

どんなケースにおいての問題が発生しても、
特に優先的に自身（オレオレ主義）の損害を防止するために
あらゆる人に同意（見てもらうだけでも）してもらいたいな、

と考えます

## まず、読み慣れた軽い文書で掲載する

https://policies.google.com/terms?hl=ja

突然ですが、googleの利用規約をご確認ください

:::note info
Google は、こうした利用規約はできれば読みたくないというユーザーの心理を理解しています。しかし、ユーザーが Google サービスを利用するうえで期待できること、および Google がユーザーに期待することを、利用規約に明記することは重要です。
:::

という、いやいやしいことを自然に受け入れてもらう文言はとても気に入っています。

これを参考に、前文は以下の通りにおおむねこうしています。

:::note info
こうした規約とかで厳しくしたくないし、今見ている貴方もこういう文面は読みたくないし、本当はこういうのを書くのもめんどくさいのですが、普通、こういうことしないよね、っていう前提をもとに、当社からこういう大事な文面を提示して、みなさまに期待することは大事と思われます。
:::

その直後に、とても軽い文面で、おおむね全文を掲載します。

- ブラウザは〇〇をおすすめするよ
- 〇〇は保証しないよ
- 〇〇しちゃだめだよ

などなど

## 正式な文書

### 著作権

- 一般的に、個人、法人問わず事前の許可なく複写禁止を置いておくと、利用規約の制定が楽になります
- SNSでシェアされたときの為に、SNSに勝手に引用並びに画像1枚が掲載（複写）されても、それは問題ない、という文言は必須です
- 画像にいたずらされない、という権利を設けてます。そのため、画像直リンを利用規約上禁止をしてみました。
:::note info
別にソースコード解析されるのは構わないのですが、画像の不正利用からの保護を最優先しているので、view-sourceを除きシステムで可能な限り、ソース表示等の機能をデフォルトでブロックしています
:::
- サイト利用規約に同意したら、ソース表示とか使える
- できれば、平成30年改正著作権法第30条4項に従わない方法におけるリバースエンジニアリング、について加えておく

### チート

https://greasyfork.org/ja/

著作権法20条をもとに、あえて、このようなGreasy Forkで公開されているものや、類似のスクリプト類、ブラウザのソースコードからの改造行為を、実際は制限しないものの、利用規約上は禁止にしておきます。

ここには、利用規約上は、アドブロックもあえて禁止します。

:::note info
実際は、数少ないソフトウェアの課金に、うちはアドブロックにも行ってます
:::

しかし、基本的に減量表示（広告取り除く）、もしくは、ユーザーのアクセシビリティー向上において行われた場合は、利用規約の提示は意味するものの、一切法的に訴えても意味はありません。

### リンク

あえて、リンク元としてふさわしくないリンク元の提示はとても重要です。

これが法的に意味がなくても、阻止力はあります。


例えば

軽レベル
- 不倫、援助交際を助長するサイト
- ねずみ講、マルチ商法を助長するサイト

中レベル
- ポルノ、自殺を肯定、掲載、助長するサイト
- 戦争を肯定するサイト
- HTMLコンテンツ以外へのリンク

強レベル
- 未成年にとって有害と認められる情報を掲載するサイト
- 不正ソフトサイト
- 覚せい剤取締法、大麻取締法、あへん法、麻薬及び向精神薬取締法の違反を肯定・助長するサイト等
- その他法に違反するサイト

から、リンクされることを利用規約上で禁止することで、サイトのブランドイメージが少しあがるのではないかと考えられます

### 迷惑メール

迷惑メールを私のサイトに送るな、ってことも、利用規約に記載はできるのではないかと考えられます。

しかし、実際に法に訴えることができるのは、悪質なウイルスメールのみです。

ちなみに、迷惑メールはこう定義しています
- お問い合わせフォームも含む
- 承諾を得ず連続たる営業行為
- 承諾を得ず人間が入力しない機械的な入力
- 虚偽で他人もしくは他社等を名乗る行為
- メールのMIMEの仕様に定義されているRFC 2045、RFC 2046、RFC 2047、RFC 4288、RFC 4289、RFC 2049を悪用する行為　→いわゆるHTMLメールのことで、悪用してだまされた方もそれなりにいらっしゃるかと思います


### cookieとか

cookieのことを気にする人が多いので、cookieのことだけを記載すべきではあるのですが、
それだけでは物足りないと感じられます。

ローカルストレージの利用やセッションの利用も記載すると、ユーザーに何をどのように使用できているのかオープンになって、良いのではないかと思われます


# ってことで、こんな細かいことまでぐだぐだ利用規約にかかれたら

読むのめんどくさくなって、悪用したくなくなる心理に持ち込んでみようという作戦です

特に、〇〇法何条っていくつか記述すると、更に読むのがめんどくさくなって、悪用するのが面倒だなぁって考える人が増えると思われます。
Rails DM機能その２（DM投稿画面）

[Rails DM機能その１](https://qiita.com/drafts/3a16559bedad01be8fe4/edit)の続きです。
## 開発環境
Mac
Ruby 2.6.5
Rails 6.1.3.2

```rooms/show.html.erb

<%= form_for @message do |f| %>
<%= f.text_field :content, :placeholder => "メッセージを入力して下さい" , :size => 70 %>
<%= f.hidden_field :room_id, :value => @room.id %>
<%= f.submit "投稿する" %>
<% end %>
```
## コードの解説
フォームに投稿された内容をテーブルに保存したいのでform_forを使います。
[form系ヘルパーメソッドの使い分け](https://pikawaka.com/rails/form_for#form%E7%B3%BB%E3%83%98%E3%83%AB%E3%83%91%E3%83%BC%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E4%BD%BF%E3%81%84%E5%88%86%E3%81%91)
ユーザーが入力したメッセージの内容を取得したいため、f.text_field :contentの記述が必要です。
どのチャットルームのメッセージが判断するために、f.hidden_fieldで：room_idのバリューとして、チャットルームでのidを取得しています。
[f.hidden_fieldとhidden_field_tagの使い方【Ruby on Rails】](https://sakurawi.hateblo.jp/entry/hidden_field)
f.submitとすることで、投稿した時に、messages_controllerにパラメータが飛ぶようになっています。


# 参考記事
[RailsでややこしいDM機能を1万字でくわしく解説してみた](https://qiita.com/bindingpry/items/6790c91f374acc25bea2)
[RailsでDM(ダイレクトメッセージ)を送れるようにしよう](https://qiita.com/ki_87/items/a344ea566c88b10b950c)
デシジョンテーブル(decision table) の使い方

# 概要
業務でテスト仕様書を作成し、単体テストや結合テストを行うことがあると思います。
そのテストケースの選定に役立つ、```デシジョンテーブルを用いた技法```をまとめます。


# デシジョンテーブル とは？
テストをする対象の```条件```と```結果```の```組み合わせ```を洗い出した上で、マトリクスとして一覧化した表のこと。ブラックボックステストに分類される。
（イメージは、Excelの表みたいな感じ）

# メリット
* 条件と結果を網羅し、可視化することができる
* 網羅率を簡単に計算することが可能
* テストパターンの抜け漏れを防げる
* どのテストレベルでも使用できる
* 条件の組み合わせ次第では、ありえない組み合わせをあぶり出すことで、不要なテストケースを除くことができる

# デメリット
* 条件の組み合わせ次第では、膨大なケース数になる
* わざわざデシジョンテーブルを作成する手間がかかる
* 条件や分岐を明確にしていないと、テストケースが漏れる

# 作り方
1. 設計書を見つつ仕様を確認することで```条件・分岐```を洗い出す
2. 1.の条件・分岐を実施後の```想定される処理結果```を洗い出す
3. 条件と結果をマトリックスに網羅する
4. ありえない条件の組み合わせを除外する

# 例題
簡単な仕様を決めた上で、実際にデシジョンテーブルを作成してみる。

## 仕様
映画館の料金割引を決める。
* 1日の場合`-100円`
* 20:00以降`-200円`
* 65歳以上`-300円`
* 条件が複数ある場合は、全て適応される
  * 1日かつ20:00以降の場合は`-300円`

## デシジョンテーブルの作成結果
![デシジョンテーブル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2226343/c3ad1eaf-4550-1bf2-4458-47090b51f918.png)


## 参考にさせていただいたサイト
* [デシジョンテーブル（決定表）とは｜その概要から作り方までわかりやすくご紹介](https://shiftasia.com/ja/column/%E3%83%87%E3%82%B7%E3%82%B8%E3%83%A7%E3%83%B3%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%EF%BC%88%E6%B1%BA%E5%AE%9A%E8%A1%A8%EF%BC%89%E3%81%A8%E3%81%AF/)
* [デシジョンテーブルテスト 道具を駆使して魔王を倒すぞ！](https://www.jasst.jp/symposium/jasst19tohoku/pdf/S5-1.pdf)
* [【ゲームを例に解説】デシジョンテーブルテストとは？](https://blog.aiqveone.co.jp/decision_table_testing/)
マークダウンの基本的な書き方

# 概要
マークダウンでREDMINEなど記載するためのメモ。
slackなどでも特によく使うものを記載する。

# 見出し

```
# 見出し1
## 見出し2
### 見出し3
#### 見出し4
##### 見出し5
###### 見出し6
```

# 見出し1
## 見出し2
### 見出し3
#### 見出し4
##### 見出し5
###### 見出し6

# 箇条書きリスト

```
* リスト1
  * リスト1-1
  * リスト1-2
    * リスト1-2-1
    * リスト1-2-2
* リスト2
* リスト3
```

* リスト1
  * リスト1-1
  * リスト1-2
    * リスト1-2-1
    * リスト1-2-2
* リスト2
* リスト3

# 太字・斜体・訂正線

```
これは **太字** です。
これは __太字__ です。
これは *斜体* です。
これは _斜体_ です。
これは ~~訂正線~~です。
```

これは **太字** です。
これは __太字__ です。
これは *斜体* です。
これは _斜体_ です。
これは ~~訂正線~~です。

# 引用

```
> これは引用です。
> これは引用です。これは引用です。
```

> これは引用です。
> これは引用です。これは引用です。
信州割の観光クーポン対象店舗をPDFから抽出してGoogle Mapに表示する実験

[信州割](https://tabi-susume.com/)（長野県の県民割）でもらえる観光クーポンがどこで使えるのか分かりにくいんじゃい！
ということでGoogle My Mapsに表示させてみました。

## 結果
https://diontools.github.io/ShinshuCouponMap/

## やったこと
1. [クーポン対象観光施設の一覧PDF](https://tabi-susume.com/wp/wp-content/uploads/2021/05/list-k-adv.pdf)から[pdfplumber](https://github.com/jsvine/pdfplumber)を使用してテーブルデータを抽出
1. Google Maps APIの[Find Place]()で施設名の緯度経度を取得
1. KMLに出力
1. Google My Mapsにインポート

リポジトリ: https://github.com/diontools/ShinshuCouponMap

## PDFからテーブルデータ抽出
意外とこれは簡単でした。

```python
import pdfplumber

with pdfplumber.open('./list-k-adv.pdf') as pdf:
  data: list[list[str | None]] = []
  for page in pdf.pages:
    table = page.extract_table()
    data.extend(table[2:])
```

テーブル内のハイパーリンクは抽出されないので、何とかする必要があります。
今回はテーブル内の"HP"のみにリンクがあるので、順番に割り当てました。

```python
links = [x['uri'] for x in pdf.hyperlinks]
linkIndex = 0
for values in data:
  if values[7] == 'HP':
    values[7] = links[linkIndex]
    linkIndex += 1
```

## 施設名から緯度経度の取得
最初はGoogle Maps APIのGeocodingを試しましたが、曖昧なデータだとあまり精度が良くないようなので、Find Placeを使用しました。
市町村別に同名の施設（チェーン店など）があるため、市町村名＋施設名で検索しました。

```python
import googlemaps

gmaps = googlemaps.Client(key='APIKEY')
result = gmaps.find_place(
  text,
  input_type='textquery',
  fields=['business_status','formatted_address','geometry','icon','name','photos','place_id','plus_code','types'],
  language='ja',
  location_bias=f'rectangle:35.1598723715222,138.82338748509508|37.16621915151721,137.27180990577534'
)
```

## おわり
体感8,9割ぐらい上手くいきましたが、元データ（施設名）がGoogle Mapの表記と違ったり、似たような名称だったりしてミスマッチが起きています。
検索結果がない・住所が長野県ではない・市町村名が一致しない場合のみ手動で修正をしましたが、これを修正していくのはちょっと辛いと思いました。（小並感）
あとFind Placeは1000リクエストで$17するので結構お高い。7000リクエストぐらいしたので1.5万円分。
MacOS Montereyでdockerの5000番ポートがalreadyになる

## 発生した問題

MacOSをMontereyにアップグレードして、docker-compose up -dをすると

🟩 追記：2024年10月発表のMacOS Sequoiaにアップグレードすると一度オフにしていたAirPlayレシーバが再度有効になり、同様の問題が発生した。
設定の画面などは変わっているが、Macの設定画面からAirPlayを検索でAirPlayレシーバーをオフにすれば良いので同様の手順で5000番ポートを使用できるようになった。

```
Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:5000 -> 0.0.0.0:0: listen tcp 0.0.0.0:5000: bind: address already in use
```
5000番ポートは既に使用しているという旨のエラーが発生した。

docker desktop for macを使用していたのでguiで5000番のポートが他のコンテナで使用されていないことを確認して再起動やdocker-compose down -v なども試してみたが改善されず。

```
$ sudo lsof -i -P | grep "LISTEN"
```
を試してみるとControlCeという機能が5000番ポートで起動していた。
これはMonterey以降実装された機能でAirPlayレシーバーというものらしいが、とりあえず使わなそうなのでオフにしてみる。
設定でAirPlayレシーバーと検索するとヒット
![スクリーンショット 2022-05-22 23.12.44 2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1055872/c873271b-e824-9dc2-4f1d-09438ae857b5.jpeg)
※画像は既にオフにした状態

これでdocker-compose up -dしてもポートが重複しないので5000番のコンテナも起動できるようになった👍
AWSのEC2インスタンス(AmazonLinux2)にJava8 + Tomcat8.5 + Apacheの環境を整える。 

今回、仕事で同環境が必要になったので、忘備録がてらに書きます。

# 1. AWS側でEC2インスタンスを作成・起動する。
まずは、AWS側でEC2インスタンスを作成・起動します。
ステータスが2/2チェックでOKになれば、接続の準備OKです。
WindowsPCからの接続であれば、TeratermやPuttyなどのツールでSSHで接続します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304058/2ddd1c8b-301d-b8cb-60fe-78ab7518cde3.png)


AWS側でEC2インスタンスを作成し、OSには、デフォルトで設定されているAmazonLinux2を選択し起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304058/b9a1c443-99a7-d4ad-f1fe-7f0b37097328.png)


:::note warn
注意！
↓ここからSSH接続をしているツール上でCUIのみでの操作となります。
:::

# 2. Java8のインストール
### Java8をインストールします。
```
sudo yum install java-1.8.0-openjdk-devel
java -version
javac -version
```

# 3. Tomcat8.5のインストール
### javaのインストールが完了したら、Tomcat8.5をインストールします。
```
sudo amazon-linux-extras install tomcat8.5
sudo yum install tomcat-webapps tomcat-admin-webapps
```
### Tomcatをインストールしたら起動します。
```
tomcat version
sudo systemctl start tomcat
systemctl status tomcat
```

# 4. Apacheのインストール

:::note warn
確認
まずは、Apacheが既にインストールされているか確認する。
:::
```
yum list installed | grep httpd
```

## Apacheをインストールします。
```
sudo yum install httpd
```


## Apacheの操作
### Apacheの起動ステータスを確認する。
```
sudo service httpd status
```
### Apacheを起動する。
```
sudo service httpd start
```
### Apacheを停止する。
```
sudo service httpd stop
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304058/bfb69da3-6e20-77bf-1576-38a4109d97bf.png)


以上となります。
お疲れ様でした。
ラズパイでキーエンスのデバイスの値をロギング

# KEYENCEの上位リンクコマンドを使ってPLCのメモリをロギング

## 環境
Python3.9  

https://github.com/OkitaSystemDesign/memlog/tree/main/KEYENCE


#### ディレクトリ構成
```
.
├─ kvhostlink.py
├─ memlogKV.py
└─ log
   └ ログファイル
```

## 実行例
```
memlogKV(0.1, 100, 'log')
```
## ファンクション
### memlogKV(interval, recnum, logpath)
定周期でlogwriteを実行します

#### interval : 収集周期
収集する周期を入力します 単位は秒  
例：0.1

#### recnum : 収集個数
設定した個数を収集するとプログラムを終了してファイルを閉じます

#### logpath : 収集ファイル出力ディレクトリ
ロギングするファイルの出力先です
ここで指定したディレクトリの下にファイル名は"logYYYYMMDD_HHSS.csv"で出力します

### logwrite(filename)
デバイスの値を読み出してcsvファイルに出力します

#### filename
出力先のファイル名

## 収集するデバイスの設定方法
### まずPLCのD0にビットデータとD1-19にワードデータがセットされてると想定します

| D0 | 1 - 19 |  
| --- | --- |  
| BitData | WORD |


### 読み出すデバイスはプログラムの先頭で指定します
```
# PLCのIPアドレス
host = '192.168.0.31'

# モニタ登録
# False: 連続アドレスで読み出す場合
# True: モニタ登録して読み出す場合
monitor = False

# 連続アドレスで読み出すデバイスアドレス
deviceAddress = 'D0.H'
readSize = 20
# モニタ登録して読み出すデバイスアドレス
monitorAddresses = 'DM0.H DM1.S DM2.L DM4.U DM5.D'
```

### 次に書き込むデータを順番に作っていきます  
まず読み出したデータはスペースで区切られているのでリストに変換します
```
datastr = data.decode().replace('\r\n', '')
datas = datastr.split(" ")
```
次から書込みデータを作成します  
最初の列には読み出した日付時刻 (ここはこのまま使います)
```
now = datetime.now()
f.write(now.strftime('%Y/%m/%d %H:%M:%S.') + "%03d" % (now.microsecond // 1000) + ',')
```
次は1つ目の文字列を16ビットに変換して出力します
```
dataB = bin(int(datas[0],16))[2:].zfill(16)
f.write(','.join(list(dataB)) + ',')
```

残りはそのままカンマ(,)でつないで出力します
```
f.write(','.join(datas[1:]) + '\n')
```

### 出力データ例
```
2022/05/22 14:19:03.987,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0001,0002,0003,0004,0001,0002,0003,0004,0009,4E20,0000,000C,000D,000E,000F,0010,0011,0012,0013
2022/05/22 14:19:04.089,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0001,0002,0003,0004,0001,0002,0003,0004,0009,4E20,0000,000C,000D,000E,000F,0010,0011,0012,0013
2022/05/22 14:19:04.190,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0001,0002,0003,0004,0001,0002,0003,0004,0009,4E20,0000,000C,000D,000E,000F,0010,0011,0012,0013
2022/05/22 14:19:04.287,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0001,0002,0003,0004,0001,0002,0003,0004,0009,4E20,0000,000C,000D,000E,000F,0010,0011,0012,0013
```
Dependabot を導入する

# 前提
- パッケージマネージャーは、Bundler
    - Rails 等を想定

# サンプル
毎週、水曜日の11時（JST）に、Gemのバージョン更新（`bundle update` して `Gemfile.lock` の差分）を通知する

```yml:.github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "bundler"
    directory: "/"
    target-branch: "develop"
    insecure-external-code-execution: deny # セキュリティ対策として、外部コードを実行を許可しない
    open-pull-requests-limit: 10
    versioning-strategy: lockfile-only # バージョン固定を反映するために、Gemfile.lockのみ更新する
    schedule:
      interval: "weekly"
      timezone: "Asia/Tokyo"
      day: "wednesday"
      time: "11:00"
```

# 補足
- [ドキュメント](https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically)を読みましょう
- デフォルトブランチにマージされて有効になる
    - 検証する際は要チェック（何で動かないの？となるので）
- 実際に設定する中でよく見たページ
    - [バージョンアップデートの設定](https://docs.github.com/ja/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/enabling-and-disabling-version-updates)
    - [dependabot.ymlの設定](https://docs.github.com/ja/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates#configuration-options-for-updates)
    - [設定の文法チェック](https://dependabot.com/docs/config-file/validator/)
- Dependabot commands and options
    - 該当PRにコメント投稿すると仕事してくれる
    - PR の description から詳細を確認できる
        -  一通り内容を把握しておいた方がいい
        -  例えば、後から versioning-strategy を設定した場合
            * 一度、バージョン固定を無視した PR を作ると、クローズしてもバージョン固定適用の PR が作られない
            * `@dependabot recreate` で PR を作り直せば OK
DeepL APIを使って英 ↔ 日翻訳してくれるdiscord botをつくった

# はじめに
discord.pyにて、「DeepL APIを使って英 ↔ 日翻訳してくれるdiscord bot」を作りました。
記事を書くのもコードを書くのもほぼ初心者ですが
温かい目で見ていただけると幸いです。

:::note warn
既にbotがある前提で進めていきます。
そのため、discord botの具体的な説明や導入は省略させていただきます。
:::
## deeplライブラリのインストール

```python3
pip install deepl
```
エラーが出るかもしれませんが何ら問題ないと思います。たぶん。

## コード全文
```python3:python3.8.3
import discord
import re
import deepl

TOKEN = "Your bot's token"

client = discord.Client()

def is_japanese(str):
    return True if re.search(r'[ぁ-んァ-ン]', str) else False

@client.event
async def on_ready():
    print('start.')

@client.event
async def on_message(message):

    @client.event
    async def on_reaction_add(reaction, user):

        if reaction.emoji == "❓":
            msg = message.content

            if is_japanese(msg) == True:
                lang = "EN-US"

            else:
                lang = "JA"

            translator = deepl.Translator("Your DeepL API key") 
            result = translator.translate_text(msg, target_lang=lang)
            result_txt = result.text 

            embed = discord.Embed(description=result,color=discord.Colour.from_rgb(130, 219, 216))

            await message.channel.send(embed=embed)

client.run(TOKEN)
```
出力例 ↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396148/cb2373ea-1d15-a04b-75ef-44e5246aacec.png)


ということで、いくつか解説していきます。
## １つ目
```python3:ここについて
def is_japanese(str):
    return True if re.search(r'[ぁ-んァ-ン]', str) else False
```
ここで文章が日本語かそうでないかという判定をしてくれる関数の定義をしています。
このコードはこちらの記事を参考にさせていただきました。
尚、漢字だけでは英語扱いされます。詳しい説明はこちらの記事を参照ください。

https://qiita.com/MichiHosokawa/items/83da0ad58905c79c867d

## ２つ目
```python3:ここについて
@client.event
async def on_message(message):

    @client.event
    async def on_reaction_add(reaction, user):

        if reaction.emoji == "❓":
            msg = message.content
```
メッセージを受け取った上で、特定のリアクションが付き、かつ特定のリアクションであった場合。。。
という風にしています。
今回は"❓"というリアクションが付けられた場合、という風にしました。
```python3:ここについて
if is_japanese(msg) == True:
    lang = "EN-US"

else:
    lang = "JA"

translator = deepl.Translator("Your DeepL API key") 
result = translator.translate_text(msg, target_lang=lang)
result_txt = result.text
```
下から２行目の引数に翻訳する文と翻訳先の言語を指定することで翻訳してくれます。
そのため、１つ目の関数で文字列を判断してもらい、翻訳先の言語を決めています。
細かい仕様等はこちらをご参照ください。

https://www.deepl.com/ja/blog/announcing-python-client-library-for-deepl-api

<details><summary>ちょっとした余談</summary>

ここで余談ですが、実はもともと最後の行を書いていなかったんです。
この記事を書いていて公式のページをもう一度読むとこの行が記述されていたことに気づいたので
焦って追加したんですけど、一応確認してみたら
```python3:型の確認
print(type(result), result)
print(type(result.text), result.text)
```
```:出力
<class 'deepl.translator.TextResult'> ハローワールド
<class 'str'> ハローワールド
```
と、独自の型になってました。
なぜエラーが出ないんだろう。。。（オチなし）
</details>

## おまけ
```python3:ここについて
embed = discord.Embed(description=result,color=discord.Colour.from_rgb(130, 219, 216))
```
こちらはembed(エンベッド)といって、
出力例のように左に線をいれて見やすくしてくれるDiscordの機能です。
こちらの記事を参考にさせていただきました。

https://qiita.com/Poteto143/items/bbc61d3adf9b6b72f75f

# さいごに
ここまで読んでいただきありがとうございました。
Python勉強中のひよっこ灰coderですが、
自分で色々調べてこうして形にできたのはすごく達成感がありました。
また新しいものを作ったら投稿してみます。
それでは。
AHK(AutoHotkey)を使いウインドウズキー左側（【LWin】）を0.5秒以内に２回連打すると現在選択している文字を検索文字として使い複数のサイトを同時に検索する

「Windows10で、AHKの実行キーを　左下隅の【Winキー】に割り当て、複数のWebサイトを一度に検索する」

というAHKスクリプトを作成してみました。
検索サイトは、Google、weblio、英辞郎　
それと【サイト指定検索】として
www.fda.gov、pmda.go.jp　も検索出来るようにしています。
【Winキー】を１度押すと、スタートメニューが出ますが、もう一度【Winキーを押せば】スタートメニューはまた非表示に戻ります。この動作を利用して、
【Winキーを0.5秒以内に２回連打したらAHKを実行する】というのがこのコードになります。
このスクリプトのメリットは、Winキーを１回押すだけなら、通常動作をすることです。
どうしてもファンクションキーや、Ctrl、Altキー等にAHKを割り当てたくは無い時に便利かもしれません。
＃【LWin】を利用してますが【RWin】にも別スクリプトを割り当てられますね

以下が　↓　↓　↓　↓　コードです。
;------------------------------------------------
; 検索対象文字をマウスで選択　＞　LWinを２回連打
; By skojima@kuhen.org
; Ver.2022-05-11
;

MsgBox,,, 【 検索対象をマウスで選択　＞　LWinを２回連打  】,2


LWin::
   Keywait, LWin, U ;ここで1回目のキーが押し上げられるのを待ちます
   Keywait, LWin, D T0.5 ;0.5秒待ちます．この間にLWinが押されれば成功＝0，失敗なら1がErrorLevelにはいります．
   If (ErrorLevel=1) ;直前のコマンド＝Keywaitがタイムアウトで失敗＝1なら
      {


      Send,{LWin}

      }
   else
      {

;MsgBox,,, 【 %clipboard% 】,1

send,^c
sleep , 300

Run,https://www.google.co.jp/search?q="""%Clipboard%"""
sleep , 300

Run,https://ejje.weblio.jp/content/%Clipboard%
sleep , 300

Run,https://eow.alc.co.jp/search?q=%Clipboard%
sleep , 300

Run,https://www.google.co.jp/search?q=site:www.fda.gov """%Clipboard%"""
sleep , 300


Run,https://www.google.co.jp/search?q=site:pmda.go.jp """%Clipboard%"""
sleep , 300

      }
   return
マークダウンで表を作るためのメモ

# 概要
マークダウンで表を作る時のメモを記載する。

# 2~5行の表

* 2行の表
```
|  TH  |  TH  |
| ---- | ---- |
|  TD  |  TD  |
|  TD  |  TD  |
```
|  TH  |  TH  |
| ---- | ---- |
|  TD  |  TD  |
|  TD  |  TD  |

* 3行の表
```
|  TH  |  TH  |  TH  |
| ---- | ---- | ---- |
|  TD  |  TD  |  TD  |
|  TD  |  TD  |  TD  |
```
|  TH  |  TH  |  TH  |
| ---- | ---- | ---- |
|  TD  |  TD  |  TD  |
|  TD  |  TD  |  TD  |

* 4行の表
```
|  TH  |  TH  |  TH  |  TH  |
| ---- | ---- | ---- | ---- |
|  TD  |  TD  |  TD  |  TD  |
|  TD  |  TD  |  TD  |  TD  |
```
|  TH  |  TH  |  TH  |  TH  |
| ---- | ---- | ---- | ---- |
|  TD  |  TD  |  TD  |  TD  |
|  TD  |  TD  |  TD  |  TD  |

* 5行の表
```
|  TH  |  TH  |  TH  |  TH  |  TH  |
| ---- | ---- | ---- | ---- | ---- |
|  TD  |  TD  |  TD  |  TD  |  TD  |
|  TD  |  TD  |  TD  |  TD  |  TD  |
```
|  TH  |  TH  |  TH  |  TH  |  TH  |
| ---- | ---- | ---- | ---- | ---- |
|  TD  |  TD  |  TD  |  TD  |  TD  |
|  TD  |  TD  |  TD  |  TD  |  TD  |


# 右寄せ・中央寄せ・左寄せ

* 右寄せ(:---)・中央寄せ(:---:)・左寄せ(---:)

```
| TH 左寄せ | TH 中央寄せ | TH 右寄せ |
| :--- | :---: | ---: |
| TD | TD | TD |
| TD | TD | TD |
```

| TH 左寄せ | TH 中央寄せ | TH 右寄せ |
| :--- | :---: | ---: |
| TD | TD | TD |
| TD | TD | TD |
【Wordpress】自宅にWordpress環境を構築してみた①

## 背景
　こんにちは、某キャリアのネットワークエンジニアとして働いている「**だいまる**」です。今回の記事では、友人に頼まれていた自宅のサーバ（ラズパイ）を利用したWordpress環境構築の実施した内容について、掲載致します。　そもそも、「**Wordpress環境を構築して！**」となったのは、友人が最近はやりのアフェリエイトやアドセンスを始めることで収入源を多様化したいといった動機がきっかけとなりました。まずはじめに、その構築環境について説明致します。

## 今回利用した構築環境
　今回、Wordpress環境を構築した構成図は、以下の図の通りとなります。


![Screen Shot 2022-05-22 at 23.25.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2658685/a1915b79-b11c-3284-e360-ddf4e939341a.png)



　サーバ（ラズパイ）が置いてある自宅は、ネット回線がマンションタイプとなっているため、**グローバルIPアドレス（世間向けの住所みたいなもの）**がもらえず、直接サーバ（ラズパイ）には届きません。そのため、今回は、AWS（Amazon Web Service）のEC2サーバを借り、リバースプロキシとして利用することで対応しました。

## 自宅での環境構築を実施するメリット・デメリット

・**メリット**
　・費用を安く抑えることが可能（EC2などのサーバを借りなければより効果大）
　・自己学習ができ、サーバの知識やWordpressの知識が身に付く

・**デメリット**
　・手間と時間がかかる
　・セキュリティ面で不安がある
　・EC2の場合、データ転送量が膨大にかかる可能性あり

今回の記事では、序章中の序章のため、初めのさわりの部分しか記載しませんでしたが、次回以降、やったことと困ったことについて記事にしていきたいと思います。