Python,PyQtで簡単にGUIを作る ② Splitterを使った配置とcsvのPlotter
目標
PythonでちゃっちゃとGUIを作ることを目標にしています。
前回に引き続き下記の2つを満たすようなものを作成していきます。
- 複数ある要素を選択
- 選択した要素をグラフに随時プロット
今回やること
ざっくりと下記のことをやっていきます。
- Splitterを用いた配置
- pandasと組み合わせて数値をPlot
Splitterを用いた配置
SetGeometryなどを用いずにSplitterという仕切りを使ってWidgetを配置する方法を用います。
下記のチュートリアルを見てみましょう。
使い方はVboxたちと似通っていて定義後はaddWidgetでWidgetを追加できます。
#from PyQt5.QtWidgets import * splitter1 = QSplitter(Qt.Horizontal) #横向きにStackする textedit = QTextEdit() textedit2 = QTextEdit() splitter1.addWidget(textedit2) splitter1.addWidget(textedit) # 中略 # 最後に予め作成したQVboxlayoutに登録 vbox.addWidget(splitter1)
また,SplitterにSplitterを追加できるため下記のようにプログラムを書けばいろいろ追加できます。
#!/usr/bin/python3 # -*- coding: utf-8 -*- import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * """ Qt splitter のチュートリアル https://www.finddevguides.com/Pyqt-qsplitter-widget Example:Qwidgetから継承 SplitterはaddWidgetで部品を追加可能。Splitter自身も追加可能 基本的に一つの領域に一つの部品を配置できる。 """ class Example(QWidget): def __init__(self): super(Example, self).__init__() self.initUI() def initUI(self): hbox = QHBoxLayout(self) # フレームを作る topleft = QFrame() topleft.setFrameShape(QFrame.StyledPanel) bottom = QFrame() bottom.setFrameShape(QFrame.StyledPanel) # 水平に分割 splitter1 = QSplitter(Qt.Horizontal) textedit = QTextEdit() textedit2 = QTextEdit() textedit3 = QTextEdit() splitter1.addWidget(textedit2) splitter1.addWidget(textedit) splitter1.setSizes([100,200]) splitter2 = QSplitter(Qt.Vertical) splitter2.addWidget(splitter1) splitter2.addWidget(textedit3) # Widgetを追加 hbox.addWidget(splitter2) # レイアウト適用 self.setLayout(hbox) QApplication.setStyle(QStyleFactory.create('Cleanlooks')) # Windowサイズを規定 self.setGeometry(300, 300, 300, 200) self.setWindowTitle('QSplitter demo') self.show() def main(): app = QApplication(sys.argv) ex = Example() sys.exit(app.exec_()) if __name__ == '__main__': main()
csvファイルを読んでPlotする
前回の記事と合わせると冒頭で示したようなcsvファイルから数値列を読んでPlotするGUIが作れます。
流れは下記のとおりです。
データとして使うCSVはAtushi Sakai大先生のリポジトリから拝借してきました。
pandasで非数値列を弾く方法
CSVを読んでPlotするのは良いですがその際,非数値が混じってエラー終了するのは避けたいです。
下記のようにselect_dtypesを使えば非数値列を排除できて安全です。
# dfの非数値の列のみ選択 self.df = df.select_dtypes(include='number')
コード
#!/usr/bin/python3 # -*- coding: utf-8 -*- import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * import pandas as pd import matplotlib.pyplot as plt from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas """ Qt splitter のチュートリアル https://www.finddevguides.com/Pyqt-qsplitter-widget Example:Qwidgetから継承 SplitterはaddWidgetで部品を追加可能。Splitter自身も追加可能 基本的に一つの領域に一つの部品を配置できる。 """ class Example(QWidget): def __init__(self,filename=None): super(Example, self).__init__() self.loadcsv(filename) self.initUI() def loadcsv(self,filename): df = pd.read_csv(filename) # 非数値の列のみ選択 self.df = df.select_dtypes(include='number') print(self.df) self.columnlist = self.df.columns # list def initUI(self): hbox = QHBoxLayout(self) # list/figure作成 self.create_listwidget() self.create_figure() # 左側作成 splitter_l = QSplitter(Qt.Vertical) splitter_l.addWidget(self.add_label("Plotする要素を選択")) splitter_l.addWidget(self.listWidget) # 右側作成 splitter = QSplitter(Qt.Horizontal) splitter.addWidget(splitter_l) splitter.addWidget(self.FigureCanvas) #splitter.setSizes([100,200]) # Widgetを追加 hbox.addWidget(splitter) # レイアウト適用 self.setLayout(hbox) QApplication.setStyle(QStyleFactory.create('Cleanlooks')) # Windowサイズを規定 #self.setGeometry(300, 300, 300, 200) self.setWindowTitle('CSV plotter demo') self.show() def create_listwidget(self): # リスト作成 self.listWidget = QListWidget() self.listWidget.setSelectionMode( #複数選択可能にする QAbstractItemView.MultiSelection ) for col in self.columnlist: item = QListWidgetItem(col) self.listWidget.addItem(item) self.listWidget.itemClicked.connect(self.printItemText) def create_figure(self): # Figureを作成 self.Figure = plt.figure() self.FigureCanvas = FigureCanvas(self.Figure) # FigureをFigureCanvasに追加 self.axis = self.Figure.add_subplot(1,1,1) # axを持っておく def add_label(self,text): # ラベルを作る label=QLabel(self) label.setText(text) label.adjustSize() return label def printItemText(self): # 選択したアイテムを表示 items = self.listWidget.selectedItems() self.selected_columns = [] for i in range(len(items)): self.selected_columns.append(self.listWidget.selectedItems()[i].text()) self.plot_selected_columns() # Plotをアップデート def plot_selected_columns(self): self.axis.cla() # リセットを掛ける必要がある。 df = self.df[self.selected_columns] df.plot(ax=self.axis) plt.grid(); self.FigureCanvas.draw() def main(): app = QApplication(sys.argv) ex = Example("samplecsv.csv") sys.exit(app.exec_()) if __name__ == '__main__': main()
余談:Checkbox形式への変更
Listwidgetは結構見た目があれなので自分的にはCheckboxのほうが好きだったりします。 下記のように設定すればCheckboxを導入できます。
# Checkbox Widgetの作成 def add_checkbox(self,name): chxlist = QCheckBox(name, self) chxlist.stateChanged.connect(self.checklist_clicked) return chxlist # クリックされたCheckboxをみてリストに追加 or 削除 def checklist_clicked(self, state): checked_name = str(self.sender().text()) if state == Qt.Checked: # if checked # add to the list self.clicked_list.append(checked_name) else: # if unchecked # remove from list self.clicked_list = [x for x in self.clicked_list if not x == checked_name]
余談2: Checkboxをまとめて導入
Checkboxをまとめて導入したいときは,上記のように一個一個Widgetを作るのではなく,VboxLayoutを用意してその中に入れていくのが良いです。
ちょっと長めの記述になります。
def create_checkboxes(self): vbox = QVBoxLayout() for col in self.columnlist: vbox.addWidget(self.add_checkbox(col)) # 箱を用意する frame = QFrame(self) # Frame定義 frame.setFrameShape(QFrame.StyledPanel) # 形状決定。パネル。 vbox.addStretch(1) # 行間を定義 frame.setLayout(vbox) return frame
補足:Checkboxに変更した版のGUI
以上をまとめると下記のような感じになります。
#!/usr/bin/python3 # -*- coding: utf-8 -*- import sys from PyQt5.QtWidgets import * from PyQt5.QtCore import * import pandas as pd import matplotlib.pyplot as plt from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas """ Qt splitter のチュートリアル https://www.finddevguides.com/Pyqt-qsplitter-widget Example:Qwidgetから継承 SplitterはaddWidgetで部品を追加可能。Splitter自身も追加可能 基本的に一つの領域に一つの部品を配置できる。 """ class Example(QWidget): def __init__(self,filename=None): super(Example, self).__init__() self.loadcsv(filename) self.initUI() def loadcsv(self,filename): df = pd.read_csv(filename) # 非数値の列のみ選択 self.df = df.select_dtypes(include='number') print(self.df) self.columnlist = self.df.columns # list def initUI(self): hbox = QHBoxLayout(self) # list/figure作成 #self.create_listwidget() self.create_figure() # 左側作成 splitter_l = QSplitter(Qt.Vertical) splitter_l.addWidget(self.add_label("Plotする要素を選択")) #splitter_l.addWidget(self.listWidget) splitter_l.addWidget(self.create_checkboxes()) # 右側作成 splitter = QSplitter(Qt.Horizontal) splitter.addWidget(splitter_l) splitter.addWidget(self.FigureCanvas) #splitter.setSizes([100,200]) # Widgetを追加 hbox.addWidget(splitter) # レイアウト適用 self.setLayout(hbox) QApplication.setStyle(QStyleFactory.create('Cleanlooks')) # Windowサイズを規定 #self.setGeometry(300, 300, 300, 200) self.setWindowTitle('CSV plotter demo') self.show() def create_listwidget(self): # リスト作成 self.listWidget = QListWidget() self.listWidget.setSelectionMode( #複数選択可能にする QAbstractItemView.MultiSelection ) for col in self.columnlist: item = QListWidgetItem(col) self.listWidget.addItem(item) self.listWidget.itemClicked.connect(self.printItemText) def create_figure(self): # Figureを作成 self.Figure = plt.figure() self.FigureCanvas = FigureCanvas(self.Figure) # FigureをFigureCanvasに追加 self.axis = self.Figure.add_subplot(1,1,1) # axを持っておく def create_checkboxes(self): vbox = QVBoxLayout() for col in self.columnlist: vbox.addWidget(self.add_checkbox(col)) # 箱を用意してはる frame = QFrame(self) # Frame定義 frame.setFrameShape(QFrame.StyledPanel) # 形状決定。パネル。 vbox.addStretch(1) # 行間を定義 frame.setLayout(vbox) return frame def add_checkbox(self,name): chxlist = QCheckBox(name, self) chxlist.stateChanged.connect(self.checklist_clicked) return chxlist # クリックされたCheckboxをみてリストに追加 or 削除 def checklist_clicked(self, state): checked_name = str(self.sender().text()) try: self.clicked_list except: self.clicked_list = [] if state == Qt.Checked: # if checked # add to the list self.clicked_list.append(checked_name) else: # if unchecked # remove from list self.clicked_list = [x for x in self.clicked_list if not x == checked_name] self.selected_columns = self.clicked_list self.plot_selected_columns() def add_label(self,text): # ラベルを作る label=QLabel(self) label.setText(text) label.adjustSize() return label def printItemText(self): # 選択したアイテムを表示 items = self.listWidget.selectedItems() self.selected_columns = [] for i in range(len(items)): self.selected_columns.append(self.listWidget.selectedItems()[i].text()) self.plot_selected_columns() # Plotをアップデート def plot_selected_columns(self): self.axis.cla() # リセットを掛ける必要がある。 df = self.df[self.selected_columns] df.plot(ax=self.axis) plt.grid(); self.FigureCanvas.draw() def main(): app = QApplication(sys.argv) ex = Example("samplecsv.csv") sys.exit(app.exec_()) if __name__ == '__main__': main()
TODO
いくつか目処は立っていますが下記の要素を徐々に追加していくとそれっぽくなるものと思われます。
- ファイル選択ダイアログの作成:【PythonでGUI】PyQt5 -ダイアログ- - Qiita
- Plotした関数のマウスGUI実装
- Saveボタンを押した時点で画像をSaveなどグラフ操作に関するボタン
Python,PyQtで簡単にGUIを作る ① リスト表示とグラフPlot
モチベーション
身の回りのありとあらゆる便利パッケージにGUIがあったらな,と思うことが多いためやり方をメモします。
初回はひとまず
- リストの複数を選択
- matplotlibの図をリストの選択に応じて表示
ができるようにします。
サンプルを通した理解
わかりやすいサンプルコードを介して理解を深めるのが手っ取り早いです。 今回の目的には下記の内容が大部分で合致しているので参考にしました。
配置
構造としてはまずはQwidgetというキャンバスを用意して,QVboxlayoutというコンテナの中にグラフのプロットであったり,チェックボックスであったり具体的な要素を詰めていくという構造をとります。(理解がUpdateされ次第Updateします。)
超最低限に書くと下記のようになります。
# 初期化のサンプル class Test(QtWidgets): # QWidgetを継承 def __init__(self): super().__init__() # 継承元のInit vbox = QtWidgets.QVBoxLayout(self) #Vboxlayoutの定義。縦にオブジェクトを並べる。(QHboxなら横に) vbox.addWidget( QtWidgets.QListWidget() ) # リスト表示Widgetを追加。 vbox.addWidget( QtWidgets.QLabel(self) ) # ラベルWidgetを追加。(テキスト設定していないからこの段階では無) # ...このように追加していって self.setLayout(vbox) # VboxlayoutをWidgetにはめ込む
他にオブジェクトを配置する手法としては最後の部分はsetGeometryなどでゴリゴリ配置しても良さそうですし,
# 配置 self.setGeometry(0,0,900,600) self.FigureWidget.setGeometry(200,0,700,600) self.FileList.setGeometry(0,0,200,600)
配置をこりたい人は,Qt Designer というGUIがあるのでそれでレイアウトを作ったほうがいいと思います。
自分は今はSplitterという機能を使って画面を分割して配置するようにしています。また次回にメモします。
イベントの設定
配置したオブジェクトたちにはイベントが起きたときにどうするかという設定をすることができます。
# インデント略 self.listWidget = QtWidgets.QListWidget() self.listWidget.itemClicked.connect(self.printItemText) #クリックされたらprintItemText関数を呼ぶ # 呼ばれる側の関数 def printItemText(self): # 選択したアイテムを表示 items = self.listWidget.selectedItems() x = [] for i in range(len(items)): x.append(str(self.listWidget.selectedItems()[i].text())) print (x)
実行
Qtにおいては、QApplicationというクラスがメインのアプリケーションを定義するので, 最後に作ったWidgetクラスを表示する際には下記のようにします。
#アプリケーション本体のインスタンスを作る app = QtWidgets.QApplication(sys.argv) #アプリケーションのインスタンスを作る Test.show() #作ったWidgetを表示する app.exec() #アプリケーションのメッセージループを開始する
小ネタ
大体のオブジェクトは配置してから初期設定などをする必要があります。 そうするとaddWidgetでの配置が見づらくなるので関数化して一行で表せるようにしたほうがはかどります。
ラベルテキスト追加
ラベルオブジェクトを配置するならこうします。
def add_label(self,text): # ラベルを作る label1=QtWidgets.QLabel(self) label1.setText(text) label1.adjustSize() return label1
リストでの複数選択
QListWidgetでSelectionModeを設定すると複数選択可能にできます。
毎回リセットするなら ExtendedSelection,トグルするなら MultiSelectionがおすすめです。 下記のように書きます。
self.listWidget = QtWidgets.QListWidget() self.listWidget.setSelectionMode( #複数選択可能にする https://doc.qt.io/qt-5/qabstractitemview.html QtWidgets.QAbstractItemView.MultiSelection # QtWidgets.QAbstractItemView.ExtendedSelection )
参考: QAbstractItemView Class | Qt Widgets 5.15.5
リストで番号を選択してPlotするサンプル
ということでリストで番号を選択してPlotするサンプルを下記に載せます。
# -*- coding: utf-8 -*- """ Stackoverflowの文章より原型を拝借 QtのListwidgetで複数選択をしてPlotする。 """ from PyQt5 import QtWidgets, QtCore import matplotlib.pyplot as plt from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas class Test(QtWidgets.QDialog): def __init__(self, parent=None): super(Test, self).__init__(parent) self.layout = QtWidgets.QVBoxLayout() # リスト作成 self.listWidget = QtWidgets.QListWidget() self.listWidget.setSelectionMode( #複数選択可能にする QtWidgets.QAbstractItemView.MultiSelection ) self.listWidget.setGeometry(QtCore.QRect(10, 10, 211, 291)) for i in range(10): item = QtWidgets.QListWidgetItem("%i" % i) self.listWidget.addItem(item) self.listWidget.itemClicked.connect(self.printItemText) # Figureを作成 self.Figure = plt.figure() self.FigureCanvas = FigureCanvas(self.Figure) # FigureをFigureCanvasに追加 self.axis = self.Figure.add_subplot(1,1,1) # axを持っておく self.layout.addWidget(self.add_label("Ctrlを押しながらで複数選択")) self.layout.addWidget(self.listWidget) self.layout.addWidget(self.FigureCanvas) self.setLayout(self.layout) def add_label(self,text): # ラベルを作る label1=QtWidgets.QLabel(self) label1.setText(text) label1.adjustSize() return label1 def printItemText(self): # 選択したアイテムを表示 items = self.listWidget.selectedItems() x = [] for i in range(len(items)): x.append(int(self.listWidget.selectedItems()[i].text())) print(x) self.update_Figure(x) # Figure def update_Figure(self,x): self.axis.cla() # リセットを掛ける必要がある。 self.axis.plot(x,'-o') plt.grid(); plt.legend(["selected number"]) self.FigureCanvas.draw() if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) form = Test() form.show() sys.exit(app.exec_())
m1 mac 環境構築メモ① 入力の設定やRemap、基本操作など
3月末にM1 macを買ってウキウキだったものの、なんだかんだWSL2にハマってしまって放置していました。 とりあえずまともにタイピングできる環境を整えたのでメモします。
機体と環境について
- 2021年度版 m1 macbook air
- 16GB, 512GB SSD
- JIS配列キーボード
電池の持ちが一昔前のガラケーレベルなのが最も顕著に恩恵を感じます。 今はGoogle Colabなどで作業環境共同化もできるので回線さえあれば外出時でもそれなりに色々できます。
記事内での設定内容
- Terminalをショートカットで開くようにする
- キーボード設定
- ファンクションキーをFnなしで動かす
- JISキーボードをUS入力風に変更する
- 単語移動を変更
- 装飾キーまわりのメモ
ターミナルをショートカットで開く。
⌘スペースでWin+rのように呼び出しメニューを出せるのでそこでターミナルとうてばデフォルトでもキーボードだけでターミナルを起動できます。
ただ、やっぱCtrl+Alt+T的なので一発で開きたい場合はちょっと回り道をする必要があります。
Automatorというアプリでサービスというのを作成してそれにショートカットを割り振るというのがよく使われているようです。
https://ry0.github.io/blog/2015/10/21/open-terminal-shortcut/#gsc.tab=0
コンフリクトがない限りいくつでもショートカットは設定できます。
まっさらな環境だと普通にcontrol+command+T
を割り振れました。
キーボード設定
基本的なところ
基本的なところは箇条書きにて失礼します。
- capslock -> command
- keyboardの修飾から
- タップしてクリック
- トラックパッドから設定
- Enter一回で入力を確定するなどとか設定
- キーボード入力ソースからWindows風とする
- https://bl6.jp/dev/computer/shortcut-key-switch-desktop/
- FunctionキーをFnなしで起動する。
KarabinerでJISキーボードをUSキーボードに設定
下記の記事の通りにKarabinerをインストールします。
https://qiita.com/canonrock16/items/0c090276b2f1fd8eb0ab
初回はキーボードの識別に時間を取られてちょっとビビります。 その後、権限を求められるのでそのあたりをきちんと設定したらOKです。
私の設定は以下の通り。
変換前 | JISの該当キー | 変換後 | USの該当キー |
---|---|---|---|
international1 | ろ | grave_accent_and_tilde(`) | ‘ |
international3 | ¥ | backslash | \ |
caps_lock | 左下の奴 | left_command | 左⌘ |
right_command | 右⌘ | right_control | 右^ |
右のシフトも使わない気がしているので適当に変換しようかなとは思っています。
Karabinerでその他ショートカット作成
Karabinerでは複数キーの組み合わせもRemapすることができます。
これはComplex modifications
という箇所から設定できインターネット上からインポートしてくることができます。
https://ke-complex-modifications.pqrs.org/?q=option%20%2B%20arrows
例えばWindowsでやっていた操作を一通りMacで動かせるようにするには下記の設定検索からWindows shortcuts on macOS
というのを検索してImportすれば良いです。
自分の場合単語移動をcontrolでできるように下記を入れました。
Exchange control + arrows keys with option + arrows keys
装飾キーまわり
Macを使うにあたりちょっと混乱したのがWindowsであったCtrl/Alt/Winキーの挙動がMacではあまり対応していないことです。
⌘はAltとCtrlの機能を兼ねたような挙動を持っていますが肝心なときにどっちかわからなかったりします。
Pythonを雑に起動して終了しようと思ったら画面分割されたりとか、Chromeで新規タブは⌘Tで出すのにタブ移動は^Tabだったりするのでちょっと困ります。
単語移動関連
WindowsやLinudxではCtrlで単語移動できますが、macではOptionで移動する必要があります。(自分はKarabinerの設定で変更しました。)
とりあえずデフォルトでのvscodeでの挙動を下記メモします。
option | 上下 | 行を保持して移動 |
option | 左右 | 単語移動 |
command | 左右 | 行頭行末移動 |
command | 上下 | 文頭文末移動 |
control | 左右 | ワークスペース切り替え |
control | 上 | ウィンドウ選択 |
control | 下 | 最近開いたファイル表示? |
使いにくいと思った部分は混乱がない範囲でRemapして行けばいいと思います。
また、日本語の単語移動を実現するにはその都度設定が必要のようです。 VScodeの場合はJapanese Word Handerを入れれば良さそうでした。
スクリーンショット
スクリーンショット自分の場合、ファイルに保存する必要はなくてクリップボードに入れば良いことが多いです。 下記を参考にしました。
https://tamoc.com/mac-screenshot-window/#i-3
結論、特定領域・Windowをクリップボードにコピーするには下記のとおりやればいいです。
- control ⌃+command ⌘+shift ⇧+4 で自由領域Cropを起動
- Spaceを押してWindowの選択をする
controlを押さなければ普通にデスクトップに保存されるようです。
その他
プログラム関係 - ターミナルの名前を変更 - https://code-graffiti.com/how-to-change-the-prompt-display-on-the-mac-terminal/ - Xcodeをインストール - homebrewのインストール - VSCODE - https://stackoverflow.com/questions/30065227/run-open-vscode-from-mac-terminal
今後もなにかあれば追加していくと思います。
2021年4月のよかったもの
誘われたので。4月は特になにかしてないのであまり書くことはありません。 (以下からである調)
まんてん鮨
下記の記事にも紹介されている「まんてん鮨」の日暮里店に食べに行った。
6000円コースを頼んだのだが結論から言うと超腹一杯うまい鮨を食える。 おまかせなのでメニューは選べないが私が行ったときはこんな感じ。恥を忍んでメモをとった。
個人的にはキンメダイの炙りがうまかった。
## 6000円コース 28品目 *付きはシャリ付き しじみ汁 *サクラマス にぎり 宮城めかぶ ほたて こぶじめ かつお たまり醤油のくぐり 湯葉くず粉 あわび *コハダ にぎり *キンメダイ あぶり にぎり みずだこ煮 *しろえび にぎり *くるまえびにぎり うめ茶碗蒸し *赤みにぎり *トロにぎり 子持ち昆布 ほたるイカあぶり *いくら + 茎山葵(お椀) たらこ西洋わさびつけ *青森ムラサキウニ *北海道バフンウニ ひょうたん柴漬け *ねぎとろ巻 しじみ味噌汁 *穴子にぎり 卵焼き *かんぴょう巻 すいか
写真は控えていたがウニだけは初めてちゃんと食べたので写真を撮った。
注:予約しないと基本入れなさそう。カウンターで大将の握るのを待つ時間が長いので友達と行くべし。
葬送のフリーレン
サンデーで連載されている漫画。よくあるRPG世界観風ファンタジーかなと侮っていたが思っていた以上に良かった。
読むとすごく優しい気持ちになれる。
1話から魔王を倒した後のエピローグで始まる異色の構成だが,
今を生きる若者と旅しながらかつての旅の思い出を振り返る主人公の描写がアラサーには結構刺さる。これ少年向けか??
ジャンプの「チェンソーマン」「アンデッドアンラック」もそうだが,新しい世代の漫画はタメ回のようなものが少なく(特に序盤)ストーリー展開により無駄がない様に感じる。
感想として今の若手の子たち漫画うますぎない?となっている。
その他
書こうか迷っているが別に書くほどでもないなと思っているやつら。
- m1 macbook air
- 電池が持つだけでこんなにも使用感がいいのか!となる。現状開発はWSL2に寄ってるのでやや持て余し気味。
- vivy(2021年4月開始 SFアニメ)
- ターミネーター的な王道テーマをきれいな絵柄と丁寧な表情描写で描いているオリジナルアニメ。SFに飢えていたので今後に期待。
- 鎌倉 オクシロモン
- お洒落なカレー屋さん。うまい。以上。
- カレーつながりだと日吉のホアホアというアジアンダイニングのカレーが滅茶うまい。
- ふるさと納税全般
- 美味い。神。ありがとう我がふるさと。
- Oisix
- 献立を考えないというのがいかに楽かというのがわかる。でも高いのでやめた。
GitHub ActionsとMarkdownで書類作成 ~ Asciidoc編~
概要
下記記事のGitHub Actions版だと思えば良いです。
下記に出来上がった書類のサンプルを載せます。
https://yoshiri.github.io/MarkdownToAsciidocToHTML_CI/sample.html
Markdownからの変換手法
ここではMarkdownで書いた文をAsciidocに変換します。
MarkdownからAsciidocへの変換はPandocとKramdocの2択があります。
- Pandoc
- 言わずとしれた万能変換ツール
- 🙆数式などの変換がちゃんとしている
- 🙅タイトルへの変換
- Kramdoc
- Asciidoctorの開発側が開発した変換ツール
- 🙆章立てなどがきれいに変換される
- 🙅数式や一部書式が正しく変換されない
ここではPandocを採用します。(数式の書きにくいドキュメントはありえないので。)
ここで,Pandocで用いるコマンドを下記に示します。(DockerコマンドなのでPandoc環境がある人は適当に読み替えてください)
wsl docker run --rm -v $(pwd):/data pandoc/core: sample.md --to asciidoctor -o sample_pandoc.adoc --shift-heading-level-by=-1
引数の詳細などは下記を参照すると良いです。(本当は英語版サイトのほうがもっと良い。)
変換前提のMarkdownの書き方について
基本的な書き方はうまいこと変換されますが,気をつけなければ行けないのは章立てです。
通常の書き方をすると下記のようになると思います。
# 1章 ## 1-1節 ## 1-2 節 # 2章 ## 2-1節 ### 2-1-1節 ...
本記事で紹介する変換法を用いる場合文章は下記のように書くことを推奨します。 AsciidocのLevel1はHTMLのタイトル用であり,章がLevel2からスタートしていることに起因します。
# 文書タイトル ## 1章 ### 1-1節 ### 1-2 節 ## 2章 ... このように一つずつずらす。
または,マークダウンで見た時に不格好になるのが気にならなければ下記のようにかくのも良いでしょう。
= 文書タイトル ## 1章 ### 1-1節 ### 1-2 節 ## 2章 ... (上記は`--shift-heading-level-by=-1`のときの書き方)
理由は次で説明します。
pandocで下記のようなマークダウンを変換した場合,レベルを1段下げます。--shift-heading-level-by=-1
を用いた時の挙動について
# 文書タイトル ## 1章 ### 1-1節
下記のようにAsciidocでは=
が#
のような役割を持ちます。
== 文書タイトル === 1章 ==== 1-1節
これに対して--shift-heading-level-by=-1を引数に加えることでレベル下げをキャンセルできます。 できるのですがタイトルのブッキングを避けるために下記のような結果を生成します。
文書タイトル <- 本当は"="から始まってほしい == 1章 === 1-1節
これを避けるもっとも手っ取り早い方法は最初からタイトルの装飾を"#"から"="に書き換えて置くことです。
置換でタイトルの"#"を"="に変更
タイトルの装飾を"#"から"="に書き換えて置くことで上記の問題は解決しますが,Markdownでこれを書くのは面倒。
ということで,GithubなどのCIの時点でタイトルの装飾を変えれば良いです。
具体的には,sedコマンドを用いて 文章の最初に出てきた"# "のを"= "に変えます。(タイトルはほぼ文章の最初に来るはずなので)
コマンドはこちら。
sed -ie '0,/# / s/# /= /' sample.md
workflowの定義
サンプルリポジトリをおいておきます。
workflowは下記のような内容です。
# This is a basic workflow to help you get started with Actions name: CI # Controls when the action will run. on: # Triggers the workflow on push or pull request events but only for the master branch push: branches: [ master ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # asciidoc to html asciidoctor_job: # The type of runner that the job will run on runs-on: ubuntu-latest name: Build AsciiDoctor steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Check out code uses: actions/checkout@v2 - name: Fix Title Level Issue run: sed -ie '0,/# / s/# /= /' sample.md - name: Markdown To Asciidoc uses: docker://pandoc/core:2.11 with: args: -f markdown+east_asian_line_breaks sample.md --to asciidoctor -o sample.adoc --wrap=preserve --verbose --shift-heading-level-by=-1 # Output command using asciidoctor-action - name: Build AsciiDoc step id: documents uses: Analog-inc/asciidoctor-action@master with: shellcommand: "asciidoctor sample.adoc -r asciidoctor-diagram -a allow-uri-read -a data-uri -a toc=left" # Use the output from the documents step - name: move deploy files run: | mkdir build/ mv sample* build/ ls build - name: deploy uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./build publish_branch: gh-pages
以上!
GitHub ActionsとMarkdownで静的サイト作成 ~ mkdocs-material 編~
目的
Markdownで書いた文書を静的サイトとしてまとめ上げたい。 そしてそのままそれを本番の文章として提出しちゃいたい。
なお,前回の記事はこちら。
要求:
- 検索機能つきの静的なサイトが生成される
- 表現が豊か(数式・図表・Admonitionなど)
- PDFに出力可能
超参考になるサイト:
ここから,mdbook, (gitbook), mkdocsあたりを試していこうと思います。
mkdocs-material
Pythonで動く静的サイト作成手法mkdocsのmaterialというテーマ。
Pros and Cons
- 🙆Pros
- 表現力が豊か(数式,PlantUML,Admonitionなど)
- 日本語検索にデフォルトで対応
- cssのカスタムテンプレートなどの数が多い
- 🙅Cons
機能性・拡張性では今の所mdbookよりも良さそうです。
Dockerを用いた動作
多くの人が入れているであろうPython環境で動くので自前でも動かせますが,Dockerイメージもあります。
自分はWSLでやっていますが,Docker for windowsの場合は適宜`pwd`
を”%CD%”
にするなど置き換えてください。
とりあえず動かす分にはsquidfunk/mkdocs-material
が多く使われている感じがします。
- 初期化
wsl docker run --rm -v `pwd`:/book/ squidfunk/mkdocs-material init
- build
wsl docker run --rm -v `pwd`:/book/ squidfunk/mkdocs-material build
ざっくり生成されるファイル・フォルダを説明すると
- book.toml : 書式や署名などを指定する
- src/ : 文書のソースを置く場所
- SUMMARY.md : リスト形式で文章のネスト構造を指定する
- xxx.md: 本体の文章
- book/ : 生成されたサイトがここに格納される。文章へはindex.htmlにアクセス。
Github Actionsを用いたドキュメントのHost
ひとまずテストプロジェクトを立ててみました。
なお,拡張機能をいろいろ盛り込んだので動作検証用のossyaritoori/mkdocs-material
にてイメージを公開しました。
したがって,実行コマンドは下記のような感じになります。
docker run --rm -v `pwd`:/docs ossyaritoori/mkdocs-material build
ファイル構造
ファイル構成はこんなかんじです。
mkdocsTest ├── .github │ └── workflow │ └── mkdocs.yml │ ├── mkdocs.yml # book setting └── docs # Source folder ├── index.md # For top page (Not neccesary) └── < other .md files >
.github以下のActionsについて
Actionの流れは
- ファイルをCIサーバーにUpload
- build
- docs以下をgh-pagesに展開
としました。
name: deploy mkdocs on: push: branches: - main - master jobs: deploy: runs-on: ubuntu-latest steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Check out code uses: actions/checkout@v2 # Prepare Python package - uses: actions/setup-python@v2 with: python-version: 3.x - run: pip install mkdocs-material - run: pip install plantuml-markdown python-markdown-math mdx_truly_sane_lists mkdocs-git-revision-date-localized-plugin mkdocs-add-number-plugin # deploy - run: mkdocs gh-deploy --force
gh-pagesの設定
上記のFlowではgh-pagesブランチに生成物をUploadしています。
gh-pagesの設定は「設定」→「一般」から下記の項目で
TODO・その他
GithubActionについて
面倒でそのままpipを手打ちしていますがRequirement.txtとかを書いてそこからインストールしたほうが良さそうです。 Dockerfileもそれに準拠して書かないとなぁとは思っています。
PDF生成
インストール後,CSSファイルをコンパイルして作成することでそれっぽくできます。
ただ,日本語が文字化けしてしまったのでそのあたりはCSSを調整してみないと,といった感じです。
GitHub ActionsとMarkdownで静的サイト作成 ~ mdbook 編~
目的
Markdownで書いた文書を静的サイトとしてまとめ上げたい。 そしてそのままそれを本番の文章として提出しちゃいたい。
要求:
- 検索機能つきの静的なサイトが生成される
- 表現が豊か(数式・図表・Admonitionなど)
- PDFに出力可能
超参考になるサイト:
ここから,mdbook, (gitbook), mkdocsあたりを試していこうと思います。
mdbook
開発が終了したGitbook v1のRust版後継です。
Pros and Cons
- 🙆Pros
- 動作が軽量。コンパイルが早い。
- サーバを建てずともサイトの検索機能が動作する
- 🙅Cons
- 現状日本語検索に対応する気があまりなさそう…
- 発展途上なので現状表現力は他の手法に劣る。
Dockerを用いた最小動作
Rust環境を建てても良いですが,Dockerで開発するのが無難です。
自分はWSLでやっていますが,Docker for windowsの場合は適宜`pwd`
を”%CD%”
にするなど置き換えてください。
- 初期化
wsl docker run --rm -v `pwd`:/book/ peaceiris/mdbook init
- build
wsl docker run --rm -v `pwd`:/book/ peaceiris/mdbook build
ざっくり生成されるファイル・フォルダを説明すると
- book.toml : 書式や署名などを指定する
- src/ : 文書のソースを置く場所
- SUMMARY.md : リスト形式で文章のネスト構造を指定する
- xxx.md: 本体の文章
- book/ : 生成されたサイトがここに格納される。文章へはindex.htmlにアクセス。
Github Actionsを用いたドキュメントのHost
ひとまずテストプロジェクトを立ててみました。
ファイル構造
ファイル構成はこんなかんじです。
mymdBook_Test ├── .github │ └── workflow │ └── mdBook.yml │ ├── book.toml └── src ├── chapters │ ├── introduction.md │ └── <other .md files> └── SUMMARY.md
.github以下のActionsについて
Actionの流れは
- ファイルをCIサーバーにUpload
- build
- book以下をgh-pagesに展開
としました。
name: github pages on: push: branches: - main jobs: deploy: runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 - name: Setup mdBook uses: peaceiris/actions-mdbook@v1 with: mdbook-version: '0.4.6' # mdbook-version: 'latest' - run: mdbook build - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./book
gh-pagesの設定
上記のFlowではgh-pagesブランチに生成物をUploadしています。
gh-pagesの設定は「設定」→「一般」から下記の項目で
TODO・その他
- ちょっときになる点
- 左のTitleが折り畳めない
- PDF生成とか試しておきたい。