先生ぇー！Pythonista3 のディレクトリ探索にwebbrowser モジュールは使えますかー？ 〜 使えます 〜

この記事は、[Pythonista3 Advent Calendar 2022](https://qiita.com/advent-calendar/2022/pythonista3) の11日目の記事です。

https://qiita.com/advent-calendar/2022/pythonista3

一方的な偏った目線で、Pythonista3 を紹介していきます。

ほぼ毎日iPhone（Pythonista3）で、コーディングをしている者です。よろしくお願いします。

以下、私の2022年12月時点の環境です。

```sysInfo.log
--- SYSTEM INFORMATION ---
* Pythonista 3.3 (330025), Default interpreter 3.6.1
* iOS 16.1.1, model iPhone12,1, resolution (portrait) 828.0 x 1792.0 @ 2.0) 828.0 x 1792.0 @ 2.0
```

他の環境(iPad や端末の種類、iOS のバージョン違い)では、意図としない挙動(エラーになる)なる場合もあります。ご了承ください。

ちなみに、`model iPhone12,1` は、iPhone11 です。

## 探訪に最適な探訪ツールを使う（作る）

漫画ブラック・ジャックにて、ブラック・ジャックが自分自身でエキノコックスを抽出した開腹手術のエピソードがありました。

我々もPythonista3 で、Pythonista3 の中身を覗いてみましょう！！（？）

また、ブラック・ジャックですら自身を見るための鏡やメスなどを使っていたので、我々も`print` 確認だけでは、辛い部分があるので、佳きツールを呼び出します。

### 注意事項

:::note warn
免責事項として書いています。
:::

アプリやOS 内部構造を操作することになります。

変更や削除で、アプリやOS が起動不可になることもありえます。

不具合に関しては自己責任でお願い致します。

### `webbrowser` モジュール

[21.1. webbrowser — Convenient Web-browser controller — Python 3.6.1 documentation](https://omz-software.com/pythonista/docs/library/webbrowser.html#module-webbrowser)

https://omz-software.com/pythonista/docs/library/webbrowser.html#module-webbrowser

標準的な使用方法については割愛しますが、Pythonista3 用に`webbrowser` モジュールはカスタムされています。

そのカスタムされている機能をつかいます。

[pythonista-url | App Extensions and Shortcuts — Python 3.6.1 documentation](https://omz-software.com/pythonista/docs/ios/pythonista_shortcuts.html#pythonista-url)

https://omz-software.com/pythonista/docs/ios/pythonista_shortcuts.html#pythonista-url

#### 挙動確認

```py
from pathlib import Path
import webbrowser

origin_path = Path()

url = 'pythonista3://' + str(origin_path)

webbrowser.open(url)

```

![img221127_174434.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953777/a977c40d-b9d3-b0e0-02ab-5b8b97f4db27.gif)

なんということでしょう！

ディレクトリがGUI で表示されています。

GUI で見るScriptLibrary のようにも見えますが、前回探訪した際と同様に、GUI のScriptLibrary では確認できなかったフォルダも確認できます。

`webbrowser.open` の`url` に`pythonista3://` をつけることで、GUI でよびだせるようになるのです。

![img221127_175134.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953777/310ef81e-d484-0e1f-07ce-c37a7cfe5d98.png)

左上の`<` で、戻ることもできます。

が、挙動が幾分バギーなので私はアプリを一度落として再起動させています。

![img221127_174633.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953777/87040ef6-bf37-ba5e-8b04-74fbddaefc68.gif)

#### もっと探訪を

GUI で確認できることにより、ディレクトリの行き来が容易になりました。

階層を指定できるようにすれば、さらに素敵ですね:

```py
from pathlib import Path
import webbrowser


def parent_level(level_int):
  return '../' * level_int


origin_path = Path()

level = 2
target_url = origin_path / parent_level(level)

webbrowser.open(f'pythonista3://{target_url}')

```

`level` で上の階層を指定できるようにしました。

- `0`: `Documents`
- `1`: `Pythonista3`
- `2`: `ID`（`それぞれ-違う-英数字`）

と、登っていけます。

しかし、

- `3`: `AppGroup`

より上から、内容が見えなくなります。

![img221127_232605.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953777/8a6c42cb-145b-6616-7c17-270b4c779a28.png)

> ディレクトリの内容を読み取れませんでした。
> このディレクトリを表示する権限がない可能性があります（例：別のアプリに属しているため）。

iOS 側の権限でアクセスできない場所もあるのです。

とこが、

- `8`: `private`
- `9`: `(root)`

は、アクセスが可能になっています。面白いですね。

`9` がroot となるので、それ以降の数値を入れても`9` と同じ結果となります。

##### 補足

`webbrowser.open` で使用している`origin_path = Path()` は、「現在のpath」を示しているのではありません。

`PosixPath` の`.` を便宜的に呼び出ししています。

![img221127_234635.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953777/da22079d-a509-eddf-674e-4360ada63b20.png)

つまり、以下コードと呼び出しは同意義となります:

```py
import webbrowser

webbrowser.open('pythonista3://.')

```

`pythonista3://.` は、`Documents` 直下となります。

階層指定:

```py
import webbrowser


def parent_level(level_int):
  return '../' * level_int


origin_path = './'

level = 9
target_url = origin_path + parent_level(level)

webbrowser.open(f'pythonista3://{target_url}')

```

`pathlib` でのメリットとしては、`print` 時に`.resolve()` すれば絶対パスが確認できるところでしょうか。

```py
from pathlib import Path
import webbrowser


def parent_level(level_int):
  return '../' * level_int


origin_path = Path()

level = 2
target_url = origin_path / parent_level(level)
resolve_path = target_url.resolve()

#webbrowser.open(f'pythonista3://{target_url}')

```

![img221127_235939.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953777/f1716562-32f9-9376-f06d-2b1a7141845f.png)

### 階層の整理を

一度ディレクトリ階層の整理をましょう。アクセス不可だったフォルダもあるので、網羅的に確認します。

```
🙆‍♂️(9: root
  └ 🙆‍♂️(8: private
   └ 🙅‍♀️(7: var
    └ 🙅‍♀️(6: mobile
     └ 🙅‍♀️(5: Containers
      └ 🙅‍♀️(4: Shared
       └ 🙅‍♀️(3: AppGroup
        └ 🙆‍♂️(2: それぞれ-違う-英数字
         └ 🙆‍♂️(1: Pythonista3
          └ 🙆‍♂️(0: Documents

```

アクセス可能が`🙆‍♂️` で、不可を`🙅‍♀️` としています。

表示用としてのコードは:

```py
from pathlib import Path


def parent_level(level_int):
  return '../' * level_int


def is_access(index):
  _level = index + 1
  _path = cwd_path / parent_level(deep - _level)
  return True if list(_path.glob('*')) else False


cwd_path = Path().cwd()

directly_list = str(cwd_path).split('/')
deep = len(directly_list)

for n, d in enumerate(directly_list):
  access = '🙆‍♂️' if is_access(n) else '🙅‍♀️'
  if not d:
    d = 'root'
  output = f'{access}({deep - n -1}: {d}'
  if n:
    tab = ' ' * n
    print(f'{tab} └ {output}')
  else:
    print(f'{output}')

```

このような構成になっているのですね。普段意識することがなくとも内部が見れるのは面白いですね。

### え、待って。え、他にもパスが見えたんだけど？

ディレクトリ構造を意識して見ていると、普段見ている情報でも、気になる情報が出てきます。

先ほどの絶対パスを確認したinspector 情報です

![img221127_235939.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953777/f1716562-32f9-9376-f06d-2b1a7141845f.png)

絶対パスに気を取られていましたが、`webbrowser` に`var/` とかありますね😎

![img221128_174536.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953777/3834caad-ccdf-3a1d-60d2-843edfd572e1.png)

詳細を見てみると、、、

![img221128_174941.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953777/4f6e3084-8c56-f051-7614-4cf1c561a1ee.png)

```
/var/containers/Bundle/Application/それぞれ-違う-英数字/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/webbrowser.py
```

これは、探索しに行かなければならないですねぇ😎

#### （ちなみに）`var` フォルダについて

いままでアクセスしていた`var` と、`webbrowser` から見つけた`var` はフォルダ名は同じですが、違う階層にあるものです。

```
/(root)
  ├ .ba
  ├ .file
  ├ .mb
  ├ Applications
  ├ Developer
  ├ Library
  ├ System
  ├ bin
  ├ cores
  ├ dev
  ├ etc
  ├ private
    ├ etc
    ├ preboot
    ├ system_data
    ├ var          ← 今までの「var」
    └ xarts
  ├ sbin
  ├ tmp
  ├ usr
  └ var            ← これから探索する「var」

```

「あれ？どこの何をアクセスしようとしてたんだっけ？」という時に参照ください。

### `Pythonista3.app` ディレクトリへ行く

探索ルートとしては

- `.`: `Documents` （便宜上のスタート位置）
  - `(root)/private/var/mobile/Containers/Shared/AppGroup/それぞれ-違う-英数字1/Pythonista3/Documents`
    - `(root)` は私が示しやすいように、追記してます
- `(root)` まで、階層を登る
- `(root)/var/containers/Bundle/Application/それぞれ-違う-英数字2/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/webbrowser.py` を目指し降りてゆく
  - `それぞれ-違う-英数字2` は、`それぞれ-違う-英数字1` とは違う値
- `Pythonista3.app` で止まる
- `webbrowser.open` で覗き見る

そのコードが:

```py
from pathlib import Path
import webbrowser


def parent_level(level_int):
  return '../' * level_int


# todo: 今回行きたいディレクトリパスを取得する処理
wbb_file_path = webbrowser.__file__  # webbrowser モジュールのファイルパス
wbb_path_list = wbb_file_path.split('/')  # インデックスでアクセスできるように、ファイルパスをlist化
app_index = wbb_path_list.index(
  'Pythonista3.app')  # webbrowser 格納先の手前のPythonista3.app のインデックスを取得
app_path_list = wbb_path_list[:app_index + 1]  # Pythonista3.app 以降の情報を排除
app_file_path = '.' + '/'.join(app_path_list)  # Pythonista3.app までのディレクトリパスを作成

# todo: ここから、いつもの処理
origin_path = Path()
level = 9

hub_path = origin_path / parent_level(level)

target_path = hub_path / app_file_path

webbrowser.open(f'pythonista3:/{target_path}')

resolve_origin_path = origin_path.resolve()
resolve_target_path = target_path.resolve()


```

![img221128_223837.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953777/19c915fb-a976-e36f-4d80-756ca03e9d56.gif)

![img221128_223922.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953777/943e5372-081b-cfcd-1ff3-b0dc9faf2df7.gif)

ほぼ、変数を外出ししているので、inspector より値確認ができます。

試しに、スタート位置のパスと、ゴール位置のパスを絶対パスとしているので確認してみてください。

```py
resolve_origin_path = origin_path.resolve()
resolve_target_path = target_path.resolve()
```

![img221128_223538.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953777/eb19d446-bced-0e28-d02d-8a6d2f8d52da.png)

### アプリケーションの情報が入ってる

`Documents` 内の`site-packages` は、我々が追加できるモジュールが格納されるディレクトリでした。`Pythonista3.app` 内には、標準モジュールやPythonista3 アプリで事前に入っているモジュールが格納されていましたね。

`Pythonista3.app` は、`Pythonista3/Documents/` の`Pythonista3` ではありません。わかり辛いですが、、、

直下には、Pythonista3 のアイコン画像（それの色違い）やフォントなとなど様々なファイルがありました

他にも、画像や音のファイルが格納されている`Media`:

```
/private/var/containers/Bundle/Application/それぞれ-違う-英数字2/Pythonista3.app/Media/
```

Theme のjson ファイルが格納されているフォルダまで:

```
/private/var/containers/Bundle/Application/それぞれ-違う-英数字2/Pythonista3.app/Media/
```

Pythonista3 アプリを触っていればいるだけ「こんな所に、こんなファイルがあるのか」と、探訪が止まりませんね。

冒頭にもお伝えしましたが、これらのファイルの扱いは丁重におこなってくださいね。

何をどうしたら動かなくなる。など、判断がつかないものは自己責任でお願いします。

## 次回は

自分（Pythonista3）で自分の中身（Pythonista3）を見るなんて、なかなかスリリングな探訪でしたね。

今回は闇雲に、手当たり次第・見つけ次第で確認行きましたが、以下のようにiOS, iPadOS のベースの部分を理解すると目的に合わせた探し方もできるかもしれません。

[iOSアプリケーションのフォルダ構成|【Swift】ARシューティングアプリを作ってみよう！|Techpit](https://www.techpit.jp/courses/80/curriculums/83/sections/627/parts/2182)

https://www.techpit.jp/courses/80/curriculums/83/sections/627/parts/2182

[今こそ復習したい、iOSアプリのディレクトリ構成 - Qiita](https://qiita.com/tototti/items/8646405f47cc56a59722)

https://qiita.com/tototti/items/8646405f47cc56a59722

私自身はiOS アプリ開発の経験もないので、Pythonista3 をいじりながら「なるほどねー」と雰囲気で把握している感じです。ですので、理解違いで説明が違っている所が多いかもしれません。

また「ディレクトリ」「フォルダ」の記載ゆらぎが多々あったかと思われます。こちらも雰囲気で書いており重ねてお詫び申し上げます。。。

次回は、iOS 自体の探訪を中心に紹介したいと思います。

ここまで、読んでいただきありがとうございました。

## せんでん

### Discord

Pythonista3 の日本語コミュニティーがあります。みなさん優しくて、わからないところも親身に教えてくれるのでこの機会に覗いてみてください。

https://t.co/0IOW2hn1VC

### 書籍

iPhone/iPad でプログラミングする最強の本。

- [Python や Jupyter で iPhone/iPad 先端機能を簡単･自由にプログラミング！「活用篇」：hirax](https://techbookfest.org/product/1WiyV4LEev3f4YrzesRKWV?productVariantID=axX32srsSgtLtZWUtjkj99)

https://techbookfest.org/product/1WiyV4LEev3f4YrzesRKWV?productVariantID=axX32srsSgtLtZWUtjkj99

- [Python や Jupyter で iPhone/iPad 先端機能を簡単･自由にプログラミング！「土台篇」：hirax](https://techbookfest.org/product/wTZTyeibm5GQ5XgdfMrEBV?productVariantID=kRDmN1udbEYZUWbETdwL8r)

https://techbookfest.org/product/wTZTyeibm5GQ5XgdfMrEBV?productVariantID=kRDmN1udbEYZUWbETdwL8r

### その他

- サンプルコード

[Pythonista3 Advent Calendar 2022](https://qiita.com/advent-calendar/2022/pythonista3) でのコードをまとめているリポジトリがあります。

コードのエラーや変なところや改善点など。ご指摘やPR お待ちしておりますー

https://github.com/pome-ta/Pythonista3AdventCalendar2022sampleCode

- Twitter

なんしかガチャガチャしていますが、お気兼ねなくお声がけくださいませー

https://twitter.com/pome_ta93

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">やれるか、やれないか。ではなく、やるんだけども、紹介説明することは尽きないと思うけど、締め切り守れるか？って話よ！（クズ）<br><br>Pythonista3 Advent Calendar 2022 <a href="https://t.co/JKUxA525Pt">https://t.co/JKUxA525Pt</a> <a href="https://twitter.com/hashtag/Qiita?src=hash&amp;ref_src=twsrc%5Etfw">#Qiita</a></p>&mdash; pome-ta (@pome_ta93) <a href="https://twitter.com/pome_ta93/status/1588570697777152006?ref_src=twsrc%5Etfw">November 4, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

- GitHub

基本的にGitHub にコードをあげているので、何にハマって何を実装しているのか観測できると思います。

https://github.com/pome-ta