はじめに
tosaken1116 Advent Calendar 2024 2日目担当の土佐犬です
絶賛大学四年生でめちゃくちゃ忙しい日々を過ごしてる中記事を書く暇がありません
今回は卒論の環境構築をします
卒論の環境構築ってなんやって話ですが、大学生は4年生になると卒業するために卒業研究をし、その論文を書かなくてはなりません 理系は特に、
でその卒論というのは基本的にMicrosoft Word
or Latex
で書きます
今回の話は前者Microsoft Word
で書く人には当てはまりません
私が所属する研究室ももれなくLatexで書くようですが、去年の今頃typstなるものをチラリと見かけていて使ってみたいなぁと思い、教授に直談判したところ「いいんじゃない?」ということで今回はtypstを使った卒論環境構築を作ろうと思います
ちなみに私はLatexも書いたことがありません
のでtypstでもLatexでもハードルは同じくらいです
今回作成したディレクトリはこちら
この記事で扱う話 扱わない話
扱う話
環境構築したよって話
扱わない話
typstの文法の詳細
typstとは
Latexより早くて簡単なドキュメントカキコツールだぜ!!って書いてありました
要件定義
要件としては以下のようなものを考えています
- 手元でコンパイルできる
- コンパイル済みファイルを外部に共有できる
- バージョン管理ができる
バージョン管理に関してはgitでどうにでもなります
手元でコンパイルに関してもtypst cliでできそうです
コンパイル済みファイルを外部に共有することが今回の環境構築の主な内容になりそうです
環境構築
typstファイルを書く
typstはbrewで入れられます
brew install typst
typst --version
typst 0.12.0
まだベータ版なのかメジャーバージョンは0です
typstはinitコマンドを使ってテンプレートを呼び出して作成することができますが今回は1から構築していきます
ディレクトリ構成ですが以下のようになっています
.
├── .cz-config.js // コミットフォーマット
├── .czrc // コミットフォーマット
├── .github
│ └── workflows
│ └── build.yaml
├── .gitignore
├── README.md
├── Taskfile.yml
├── docs // ユーザーが書き込む部分
│ ├── abstract.typ
│ └── chapters
│ └── chapter1.typ
├── env.typ // 環境変数
├── init.sh // 環境構築スクリプト
├── lefthook.yml // lefthook用(pre commit)
├── libs // 文書のフォーマット定義
│ ├── abstract.typ
│ ├── config.typ
│ ├── cover.typ
│ ├── index.typ
│ └── template.typ
└── main.typ // main
まずはmain.typ
を作ります
#import "libs/cover.typ": cover
#import "libs/abstract.typ": abstract
#import "libs/config.typ": view
#import "libs/index.typ": index
#show: view.with()
// 表紙
#cover()
#counter(page).update(1)
#set page(numbering: "i")
// アブストラクト
#abstract()
// 目次
#index()
#set page(numbering: "1")
#counter(page).update(1)
#let chapters = ("docs/chapters/chapter1.typ",)
#for chapter in (chapters) {
pagebreak()
include (chapter)
}
typstは#import
を使って他のtypstファイルを参照できます
ここではlibsディレクトリのcover
, abstract
config
index
を呼び出しています
それぞれのファイルには以下が定義されています
- cover: 表紙テンプレート
- abstract: アブストラクトテンプレート
- config: 文書全体に適用する設定
- index: 目次テンプレート
#show
文は関数を受け取りその関数を用いて文書を表示します
ここではconfigを用いて続く文書を表示するようにしています
config.typ
は次のように書いています
#let view(doc) = {
set text(font: "IPAMincho", 12pt) // テキストの設定
set par( // 文章コンテンツの設定
first-line-indent: 1em,
spacing: 0.65em,
justify: true,
)
set heading(numbering: (..nums) => { // 章のフォーマット
let numbers = nums.pos()
if numbers.len() == 1 {
numbering("第1章 ", ..numbers)
} else {
numbering("1.1 ", ..numbers)
}
})
show heading.where(level: 1): set block(above: 40pt, below: 50pt) // 章のマージン
show heading.where(level: 2): set block(above: 30pt, below: 15pt)
show heading.where(level: 3): set block(above: 30pt, below: 15pt)
show heading.where(level: 1): set text(size: 24pt) // 章のテキストサイズ
show heading.where(level: 2): set text(size: 16pt)
show heading.where(level: 3): set text(size: 12pt)
set page( // 全ページ共通の設定
paper: "a4",
margin: (
top: 40mm,
bottom: 40mm,
left: 35mm,
right: 20mm,
),
)
doc
}
まだ中身は最低限のものしか書いていませんが今後必要になったら足していこうと思います
続いてcover関数の呼び出しを行っています
cover.typは次のように記述しています
#import "../env.typ": env
#let cover() = {
set align(center)
set text(18pt)
v(20mm)
[#text(size: 20pt)[卒~~業~~論~~文]]
v(15mm)
box(width: 145mm)[
#align(center)[#env.thesis_title]
]
v(40mm)
stack(
dir: ttb,
spacing: 3mm,
text(size: 18pt)[#env.college_name #env.faculty_name],
text(size: 18pt)[#env.department_name],
)
v(15mm)
[#env.student_name]
v(25mm)
[#env.year_of_grad 年度]
v(10mm)
[指導教員:#env.supervisor_name]
pagebreak()
}
env.typから特定の値を呼び出し表紙として表示する関数です
その次のページ数の表示をしています
表紙にはページ番号をつけたくないため表紙を表示した後に関数を実行し表示するようにしています
#counter(page).update(1)
#set page(numbering: "i")
またこの時のページ番号はローマ数字表記にしています
次にabstractとindexです
abstractとはその論文の概要を示すセクションです
// アブストラクト
#abstract()
// 目次
#index()
abstract.typではdocsディレクトリの中のabstract.typファイルから文章を読み出し、それを特定のフォーマットに基づいて表示するようにしています
とはいってもそこまで大きな設定はないので冗長といえば冗長です
include
を使ってtypstファイルから文書全体を取り出して表示しています
#let abstract() = {
align( //文書を真ん中寄せで表示
center,
text(size: 12pt)[概~要],
)
v(4mm)
[#include "../docs/abstract.typ"] // docs/abstract.typをinclude
pagebreak() // 次の文書が次のページに来るようにする
}
index.typではそれ以降に表示する文書の目次を表示する関数を定義しています
#let index() = {
show outline.entry.where(level: 1): it => { // 最も大きい章の表示設定
v(12pt)
text(size: 12pt, it)
}
show outline.entry.where(level: 2): it => { // 2番目に大きい章の表示設定
text(size: 12pt, it)
}
v(10mm)
outline( // 目次の表示
indent: 2em,
title: box([
#text(size: 24pt)[目~~次]
#v(10mm)
]),
)
}
目次を表示したらもう一度ページ番号をリセットします
#set page(numbering: "1")
#counter(page).update(1)
これは目次の次のページからページ番号を始めるためです
なおこの時の数字表記はアラビア数字を用いています
そして最後にchapterを表示しています
chapters配列にインポートしたいファイルを定義してそれをfor文を用いて表示しています
#let chapters = ("docs/chapters/chapter1.typ",)
#for chapter in (chapters) {
pagebreak()
include (chapter)
}
最後にユーザーが文書を書く部分ですがenv.typ
ではユーザーに関するデータなどを定義します
#let student_name = "著者の名前"
#let student_id = "00000000"
#let supervisor_name = "指導教員の名前"
#let thesis_title = "卒論のタイトル"
#let year_of_grad = "2023"
#let college_name = "大学名"
#let faculty_name = "学部名"
#let department_name = "学科名"
#let env = (
student_name: student_name,
student_id: student_id,
supervisor_name: supervisor_name,
thesis_title: thesis_title,
year_of_grad: year_of_grad,
college_name: college_name,
faculty_name: faculty_name,
department_name: department_name,
)
またdocsディレクトリ内のchapterやabstract.typでは次のように書くだけで文書が表示されます
アブストラクト書くとこ
= はじめに // typstでは = を用いて章を書きます
これはテスト用のドキュメントです
これで一通りのtypstファイルの定義を作ったのでコンパイルします
typst compile main.typ
また文書が変わったら動的にコンパイルするwatch
コマンドもあります
typst watch main.typ
そしてコンパイルされた文書が以下です
いい感じにできました
とはいっても私はまだ卒論を書いたことがないのでこれはサークルの先輩に見せていただいた論文を模したものです
周りのツールなど
typstコマンドを毎回全部打つのは面倒くさいのでtaskfileを作ります
taskfileはgo製のツールTask
のコマンド定義ファイルです
といってもここでは詳しくは触れません
公式サイト
https://taskfile.dev/
version: "3"
tasks:
default:
desc: Run the default task
cmds:
- task -l
compile:
desc: Builds the typst document
deps:
- fmt
cmds:
- mkdir -p exports && typst compile main.typ exports/main.pdf
compile:watch:
desc: Builds the typst document on change
cmds:
- mkdir -p exports && typst watch main.typ exports/main.pdf
commit:
cmds:
- cz
fmt:
desc: Format all typ files
cmds:
- typstyle format-all libs
- typstyle format-all main.typ
- typstyle format-all env.typ
- typstyle format-all docs
silent: true
今回はcompile
compile:watch
commit
fmt
の四つのコマンドを定義しました
commitにはcommitizen
という特定のフォーマットに基づいたコミットができるツールを使っています
またtypstにはデフォルトではフォーマットコマンドがないので有志のパッケージtypstyle
を用いています
typstyle
https://github.com/Enter-tainer/typstyle
github actionsの設定
最後にgithub actionsの設定です
一度最初に作った時に出てきたpdfが以下のようなものです
豆腐がたくさんありますね
github actionsで使用されるubuntuにはデフォルトでは日本語フォントが入っていないためこのような表記になります
ということでそれを加味したactionが以下です
name: Build Typst document
on:
push:
branches:
- main
paths:
- docs/**/*
- main.typ
- env.typ
env:
GOOGLE_DRIVE_UPLOAD_DIRECTORY: /research/report
TZ: Asia/Tokyo
jobs:
build_typst_documents:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Cache Typst binary
id: cache
uses: actions/cache@v3
with:
path: ~/.cargo/bin/typst
key: ${{ runner.os }}-typst-bin-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-typst-bin-
- name: Set up Typst
if: steps.cache.outputs.cache-hit != 'true'
run: |
sudo apt update -y
sudo apt install -y fonts-ipafont
cargo install --git https://github.com/typst/typst --locked typst-cli
- name: Verify Typst Installation
run: typst --version
- name: Build Typst Document
run: typst compile main.typ
- name: Get current date in JST
id: date
run: |
export TZ=$TZ
echo "DATE=$(date +%Y-%m-%d-%H:%M)" >> $GITHUB_ENV
- name: Rename file
run: mkdir exports && mv main.pdf exports/${{ env.DATE }}.pdf
- name: Upload to Google Drive
uses: adityak74/google-drive-upload-git-action@main
with:
credentials: ${{ secrets.GOOGLE_DRIVE_SECRET }}
filename: "exports/*.pdf"
folderId: ${{ secrets.GOOGLE_DRIVE_FOLDER_ID }}
ubuntuからtypstを入れるにはRust経由が良さそうだったのでRustをインストールしそこからtypstをインストールしています
またtypstでコンパイル後google driveにアップロードして他のユーザーからも参照できるようにします
なおgoogle driveにアップロードするためにはGoogle Cloud
でプロジェクトを作りgoogle drive apiにアクセス可能なサービスアカウントを作り、自分のフォルダにそのサービスアカウントを招待することが必要です
ちょっとめんどくさいです
ということでできました
リポジトリにpushするだけでpdfができるactionsの完成です
終わりに
ということで今回はtypstでテンプレートを作った話でした
色々忙しくて卒論を書くのはちょっと大変ですが、卒業できるように頑張ります...