【Python】コメントの割合を分析しPDFで保存するプログラムを実装する

こんにちは、しゃねろんです。
今回はプログラム内のコメントの割合を分析し、PDFで保存するプログラムを実装したのでそれを記事にしていきます。
今回作成したコードはGitHubに投稿しているので、よければそちらもご覧ください!
今回実装したもの
今回のプログラムで実装したものは大きく4つ
- Pythonファイルの探索
- コメントの判定
- 円グラフ描画
- PDF保存
になります。
それぞれ詳しく説明していきます。
GitHubの方にコードは乗っていますが、一応このページにも載せておきます。
今回用いたコードはこちら↓
import os
import glob
import matplotlib.pyplot as matplot
import japanize_matplotlib
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4, portrait
file_count = 0
total_count = 0
comment_count = 0
use_files = []
png_name = "circle.png"
pdf_name = "circle.pdf"
#下の階層用
for files in glob.glob("*/**/*"):
name, extension = os.path.splitext(files)
if extension == ".py":
use_files.append(name + extension)
file_count += 1
pyfile = open(files)
lines = pyfile.readlines()
pyfile.close()
replace_line = [linedata.replace(' ','') for linedata in lines]
replace_and_strip_line = [replacedata.replace('\n','') for replacedata in replace_line]
#コメント判定
for pyline in replace_and_strip_line:
total_count += len(pyline)
if pyline.startswith("#", 0):
comment_count += len(pyline)
#同じ階層用
for files in glob.glob("*"):
name, extension = os.path.splitext(files)
if extension == ".py":
use_files.append(name + extension)
file_count += 1
pyfile = open(files)
lines = pyfile.readlines()
pyfile.close()
replace_line = [linedata.replace(' ','') for linedata in lines]
replace_and_strip_line = [replacedata.replace('\n','') for replacedata in replace_line]
for pyline in replace_and_strip_line:
total_count += len(pyline)
if pyline.startswith("#", 0):
comment_count += len(pyline)
#円グラフ描画
not_comment_count = total_count - comment_count
circledata = [not_comment_count, comment_count]
labels = ["プログラム文", "コメント"]
a, ax = matplot.subplots()
ax.pie(circledata, labels = labels, startangle = 90, autopct="%1.1f%%")
matplot.title("コメント比率", fontsize = 24)
matplot.savefig(png_name)
#PDF作成
pdf = canvas.Canvas(pdf_name, pagesize=portrait(A4))
pdf.drawImage(png_name, 0, 500, 450, 300)
pdf.drawCentredString(107, 500, "Total: " + "{:10}".format(str(total_count)))
pdf.drawCentredString(110, 480, "Comment: " + "{:10}".format(str(comment_count)))
pdf.drawCentredString(113, 460, "Files: " + "{:10}".format(str(file_count)))
pdf.drawCentredString(107, 400, "FileName_and_Path")
x, y = 215, 375
for file_name in use_files:
pdf.drawCentredString(x, y, file_name)
y -= 15
pdf.save()
#画像ファイル削除(circle.png)
os.remove("circle.png")
Pythonファイルの探索とコメントの判定
まずはPythonファイルの探索とコメントの判定について説明していきます。
2つまとめて説明しているのは、プログラム的に分けて説明がしづらかったためです。
コードだと以下の部分に該当します。
import os
import glob
file_count = 0
total_count = 0
comment_count = 0
use_files = []
#下の階層用
for files in glob.glob("*/**/*"):
name, extension = os.path.splitext(files)
if extension == ".py":
use_files.append(name + extension)
file_count += 1
pyfile = open(files)
lines = pyfile.readlines()
pyfile.close()
replace_line = [linedata.replace(' ','') for linedata in lines]
replace_and_strip_line = [replacedata.replace('\n','') for replacedata in replace_line]
#コメント判定
for pyline in replace_and_strip_line:
total_count += len(pyline)
if pyline.startswith("#", 0):
comment_count += len(pyline)
Pythonファイルの探索
ファイル探索に用いるのは「os」「glob」モジュールになります。
今回出力に用いた変数は
変数名 | 格納するデータ |
file_count | 処理に用いたファイル数 |
total_count | プログラム総文字数 |
comment_count | コメント総文字数 |
use_files | 処理に用いたファイル名 |
となります。
まずはこのプログラムが保存してある場所より下の階層にあるpythonファイルの探索を行います。
for files in glob.glob("*/**/*"):
にて下の階層にある全てのファイルを探索します。
次に全てのファイルの中から拡張子が「.py」の物のみ抽出します。
name, extension = os.path.splitext(files)
では、ファイル名と拡張子に分ける処理を行っています。
第1引数にはファイル名、第1引数には拡張子名が格納されます。
そして
if extension == ".py":
の処理でその中でも拡張子が「.py」のPythonファイルのみを抽出することでPythonファイルの分析を行うことができるようになりました。
※GitHubのコードには上記のプログラムに加え、「#同じ階層用」というものが用意されていますが、「#下の階層用」との違いは探索範囲が違うだけで、
for files in glob.glob("*/**/*"):
が
for files in glob.glob("*"):
になっているだけなので今回は省略します。
コメントの判定
次にコメントの判定です。
ここでは「Pythonファイルの探索」にて探索されたPythonファイルの中からコメントを探していく行程になります。
file_count += 1
にてファイル数を増やした後、
pyfile = open(files)
lines = pyfile.readlines()
pyfile.close()
ファイルの中身を抽出する処理を行います。この場所はwith asでも勿論代用できます。
次にファイルを読み込んだ変数「lines」に細工をしていきます。
空白や改行を削除することでより正確な文字数をカウントするためです。(字下げ等の空白も文字数としてカウントされるため。)
今回は内包表記によって処理を書きました。
replace_line = [linedata.replace(' ','') for linedata in lines]
replace_and_strip_line = [replacedata.replace('\n','') for replacedata in replace_line]
〇〇.replaceにてもじれるの置換を行い、空白と改行(\n)をこれらの処理によって削除しています。
さていよいよコメントの判定です。
今回のコメントの判定では行の一番最初に「#」がついている行を対象とします。
for pyline in replace_and_strip_line:
total_count += len(pyline)
if pyline.startswith("#", 0):
comment_count += len(pyline)
先ほどの細工を行ったプログラムを1行ずつ取り出していく処理になりますが、if文で文字検索をすることで判定していきます。
今回は〇〇.startswithを用いて判定します。
〇〇.startswithの第1引数には判定したい文字列、第2引数には探索場所が入ります。
探索場所を「0」からにしているのは、〇〇.replaceの処理で最初の空白も削除されているため、「#」が付く行は必然的にコメントと判定できるためです。
あとは当てはまった行の文字数をカウントする処理を入れてコメントの判定は終了です。
円グラフ描画
次に円グラフ描画について説明していきます。
コードだと以下の部分に該当します。
import matplotlib.pyplot as matplot
import japanize_matplotlib
png_name = "circle.png"
#円グラフ描画
not_comment_count = total_count - comment_count
circledata = [not_comment_count, comment_count]
labels = ["プログラム文", "コメント"]
a, ax = matplot.subplots()
ax.pie(circledata, labels = labels, startangle = 90, autopct="%1.1f%%")
matplot.title("コメント比率", fontsize = 24)
matplot.savefig(png_name)
ざっくりとこの処理を説明すると、コメントとそれ以外の文字数をカウントし、円グラフに描画、画像ファイル(今回は「.png」)に保存する。といった処理になります。
円グラフ描画のために、matplotlibをpip等でインストールしておきます。
pip install matplotlib
でインストールができると思います。
japanize_matplotlib というパッケージもついでにインストールしていますが、matplotlib 自体が日本語対応しておらず、日本語を入力すると文字化けしてしまいます。
それを解決するために本プログラムではインストールしています。
本題の円グラフの描画はこの行で指定しています。
ax.pie(circledata, labels = labels, startangle = 90, autopct="%1.1f%%")
今回は startangle (角度設定)や autopct (%表記)を用いましたがこの他にも影をつけたり半径の大きさを変更することはできますが、今回は省略します。
最後に
matplot.title("コメント比率", fontsize = 24)
でタイトルをつけ
matplot.savefig(png_name)
で画像として保存しています。
PDF保存
最後にPDF保存について説明していきます。
コードだと以下の部分に該当します。
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4, portrait
pdf_name = "circle.pdf"
#PDF作成
pdf = canvas.Canvas(pdf_name, pagesize=portrait(A4))
pdf.drawImage(png_name, 0, 500, 450, 300)
pdf.drawCentredString(107, 500, "Total: " + "{:10}".format(str(total_count)))
pdf.drawCentredString(110, 480, "Comment: " + "{:10}".format(str(comment_count)))
pdf.drawCentredString(113, 460, "Files: " + "{:10}".format(str(file_count)))
pdf.drawCentredString(107, 400, "FileName_and_Path")
x, y = 215, 375
for file_name in use_files:
pdf.drawCentredString(x, y, file_name)
y -= 15
pdf.save()
pdfにデータを挿入して保存するため、PDFに描画するモジュールが必要になります。
そのため、事前にpip等で reportlab をインストールしておく必要があります。
pip install reportlab
でインストールができると思います。
次にpdfファイルを実際に作成していきます。
pdf = canvas.Canvas(pdf_name, pagesize=portrait(A4))
にてA4サイズのpdfを作成し、pdf_name が保存される名前が入ります。
次に画像の挿入についてです。
pdf.drawImage(png_name, 0, 500, 450, 300)
○○.drawImage にて画像の挿入を行います。
第1引数に画像ファイル名、第2,第3引数にx,y座標、第4,第5引数に画像サイズ(width,height)が入ります。
次に文字列の挿入です。
pdf.drawCentredString(107, 500, "Total: " + "{:10}".format(str(total_count)))
pdf.drawCentredString(110, 480, "Comment: " + "{:10}".format(str(comment_count)))
pdf.drawCentredString(113, 460, "Files: " + "{:10}".format(str(file_count)))
pdf.drawCentredString(107, 400, "FileName_and_Path")
x, y = 215, 375
for file_name in use_files:
pdf.drawCentredString(x, y, file_name)
y -= 15
ややコードが多めですが違いは座標ぐらいです。
○○.drawCentredString にて文字列の挿入を行います。
第1,第2引数にx,y座標、第3引数に挿入する文字列が入ります。
最後のfor文では複数の文字列をずらしながら挿入しています。
こちらのリンクのpdfファイルの中の「FileName_and_Path」より下の文字列がfor文にて挿入している文字列です。
最後に、
pdf.save()
でpdfを保存し、いらなくなった画像ファイルもos.remove() にて削除しています。
まとめ
今回のプログラムでは1行ずつ判定をしているため複数行にわたるコメントは反映させていません。
勿論拡張子を変えたり条件を変えることで多言語バージョンも作成することができるのでぜひ試してみてください!