Day1 ~src/choosenim.nim~
Day1: エントリーポイントになるchoosenim.nim
を流し読みする。Source Code
今は100%理解するためではなく、シルエットやイメージを掴むのが目的なのでざっと読む。
初手
とりあえずimportしているパッケージの中で、この2つはchoosenim固有のパッケージなので今後読むことになりそう。
import choosenimpkg/[download, builder, switcher, common, cliparams, versions]
import choosenimpkg/[utils, channel, telemetry]
他のパッケージはよくわからないメソッドなど参照が必要になったら都度読むことにする。
少し気になったので、アウトラインでchoosenim.nim
のメソッドとプロパティをチェックしてみる。
これくらいなら読めそう。
when isMainModuleブロックを読む
実質エントリーポイントのここからとりあえずSourceを眺めてみる。
choosenim.nim
when isMainModule:
var error = ""
var hint = ""
var params = newCliParams(proxyExeMode = false)
try:
parseCliParams(params)
createDir(params.chooseNimDir)
discard loadAnalytics(params)
performAction(params)
except NimbleError:
let currentExc = (ref NimbleError)(getCurrentException())
(error, hint) = getOutputInfo(currentExc)
# Report telemetry.
report(currentExc, params)
report(initEvent(ErrorEvent, label=currentExc.msg), params)
if error.len > 0:
displayTip()
display("Error:", error, Error, HighPriority)
if hint.len > 0:
display("Hint:", hint, Warning, HighPriority)
handleTelemetry(params)
quit(QuitFailure)
handleTelemetry(params)
今のお気持ち
-
parseCliParams
でパースしてparamsを構築(してそう)- [タスク]
cliparams.parseCliParams
を読む
- [タスク]
-
os.createDir
でparams.chooseNimDirを作成 -
loadAnalytics
が何してるのかわからん。- [タスク]telemetry.loadAnalyticsを読む
-
performAction
()が何してるのかよくわからん- [タスク]
choosenim.performAction
を読む
- [タスク]
-
except NimbleError
ブロックは、例外処理してるなぁ。 -
if error.len > 0:
ブロックでエラーが有ったときの出力処理をしているなぁ。 -
handleTelemetry
が何してるのかよくわからん- [タスク]
choosenim.handleTelemetry
を読む
- [タスク]
出てきたタスクのなかで、どれから読むか考える。
とりあえずchoosenim.nim
にあるperformAction
とhandleTelemetry
から読むか。
performActionを読む
proc performAction(params: CliParams) =
# Report telemetry.
report(initEvent(ActionEvent), params)
case params.command.normalize
of "update":
update(params)
of "show":
show(params)
of "versions":
versions(params)
else:
choose(params)
サブコマンドの処理分岐をしている。
とりあえずupdate,show,versions,chooseの処理を流し読みしてみる。
※長いのでコードは省略。Source Codeを読んで。
今のお気持ち
- [感想]とりあえず
performAction
でサブコマンドの分岐してるな。- [推察]コマンドを追加したいときは追記すれば良さそう。
- [感想]parseCliParamsを読めばパーサの処理とかわかりそうだし読むプライオリティあげよう。
- [発見]
addFileExt(ExeExt)
でOS固有の実行ファイル拡張子(例:.exe)をつけられる! - [発見]
walkDirs("dir/*")
でディレクトリ探査ができる(ちゃんとイテレータ)。ls dir/*
みたいな感じ - [感想]各サブコマンドの処理はなんとなくわかったけど
choosenimpkg
全部読んだ後でもう一度読もう- [タスク]choosenimpkgをすべて読んだら
update,show,versions,choose
を読み直す
- [タスク]choosenimpkgをすべて読んだら
handleTelemetryを読む
proc handleTelemetry(params: CliParams) =
if params.hasPendingReports():
display("Info:", "Waiting 5 secs for remaining telemetry data to be sent.",
priority=HighPriority)
waitForReport(5, params)
if params.hasPendingReports():
display("Warning:", "Could not send all telemetry data.",
Warning, HighPriority)
今のお気持ち
- [感想]レポート系の処理。保留中のレポートがあれば
waitForReport
で送信しているっぽい。- [補足]そういえばchoosenimのインストール時にテレメトリーデータを送るか聞かれたな。
- [タスク]
telemetry.waitForReport
を読む。
今日のまとめ。
なんとなく処理の流れはつかめた。
addFileExt(ExeExt)
は初見だった。今までwhenでCの#ifdefみたいに
let exeExt =
when defined(windows): ".exe"
else: ""
let filename = "filename" & exeExt
って書いてた。addFileExtのほうが使い勝手よいので覚えておく。
残っているタスク
残っているタスク
- [タスク]
cliparams.parseCliParams
を読む - [タスク]
telemetry.loadAnalytics
を読む - [タスク]
telemetry.waitForReport
を読む。 - [タスク]choosenimpkgをすべて読んだら
update,show,versions,choose
を読み直す
明日は何する?
- telemetry.nimを読む
clipramsよりも各セクションで呼ばれそうなので先に読むことにした。
今日はここまで。
付録:ソースコード
src/coosenim.nim
# Copyright (C) Dominik Picheta. All rights reserved.
# BSD-3-Clause License. Look at license.txt for more info.
import os, strutils, algorithm
import nimblepkg/[cli, version]
import nimblepkg/common as nimbleCommon
from nimblepkg/packageinfo import getNameVersion
import choosenimpkg/[download, builder, switcher, common, cliparams, versions]
import choosenimpkg/[utils, channel, telemetry]
proc installVersion(version: Version, params: CliParams) =
# Install the requested version.
let path = download(version, params)
# Extract the downloaded file.
let extractDir = params.getInstallationDir(version)
# Make sure no stale files from previous installation exist.
removeDir(extractDir)
extract(path, extractDir)
# A "special" version is downloaded from GitHub and thus needs a `.git`
# directory in order to let `koch` know that it should download a "devel"
# Nimble.
if version.isSpecial:
createDir(extractDir / ".git")
# Build the compiler
build(extractDir, version, params)
# Delete downloaded file
discard tryRemoveFile(path)
proc chooseVersion(version: string, params: CliParams) =
# Command is a version.
let version = parseVersion(version)
# Verify that C compiler is installed.
if params.needsCCInstall():
when defined(windows):
# Install MingW.
let path = downloadMingw32(params)
extract(path, getMingwPath(params))
else:
raise newException(ChooseNimError,
"No C compiler found. Nim compiler requires a C compiler.\n" &
"Install clang or gcc using your favourite package manager.")
# Verify that DLLs (openssl primarily) are installed.
when defined(windows):
if params.needsDLLInstall():
# Install DLLs.
let path = downloadDLLs(params)
extract(path, getBinDir(params))
if not params.isVersionInstalled(version):
installVersion(version, params)
switchTo(version, params)
proc choose(params: CliParams) =
if dirExists(params.command):
# Command is a file path likely pointing to an existing Nim installation.
switchTo(params.command, params)
else:
# Check for release channel.
if params.command.isReleaseChannel():
let version = getChannelVersion(params.command, params)
chooseVersion(version, params)
pinChannelVersion(params.command, version, params)
setCurrentChannel(params.command, params)
else:
chooseVersion(params.command, params)
proc updateSelf(params: CliParams) =
display("Updating", "choosenim", priority = HighPriority)
let version = getChannelVersion("self", params, live=true).newVersion
if version <= chooseNimVersion.newVersion:
display("Info:", "Already up to date at version " & chooseNimVersion,
Success, HighPriority)
return
# https://stackoverflow.com/a/9163044/492186
let tag = "v" & $version
let filename = "choosenim-" & $version & "_" & hostOS & "_" & hostCPU
let url = "https://github.com/dom96/choosenim/releases/download/$1/$2" % [
tag, filename
]
let newFilename = getAppDir() / "choosenim_new".addFileExt(ExeExt)
downloadFile(url, newFilename, params)
let appFilename = getAppFilename()
# Move choosenim.exe to choosenim_ver.exe
let oldFilename = "choosenim_" & chooseNimVersion.addFileExt(ExeExt)
display("Info:", "Renaming '$1' to '$2'" % [appFilename, oldFilename])
moveFile(appFilename, getAppDir() / oldFilename)
# Move choosenim_new.exe to choosenim.exe
display("Info:", "Renaming '$1' to '$2'" % [newFilename, appFilename])
moveFile(newFilename, appFilename)
display("Info:", "Setting +x on downloaded file")
inclFilePermissions(appFilename, {fpUserExec, fpGroupExec})
display("Info:", "Updated choosenim to version " & $version,
Success, HighPriority)
proc update(params: CliParams) =
if params.commands.len != 2:
raise newException(ChooseNimError,
"Expected 1 parameter to 'update' command")
let channel = params.commands[1]
if channel.toLowerAscii() == "self":
updateSelf(params)
return
display("Updating", channel, priority = HighPriority)
# Retrieve the current version for the specified channel.
let version = getChannelVersion(channel, params, live=true).newVersion
# Ensure that the version isn't already installed.
if not canUpdate(version, params):
display("Info:", "Already up to date at version " & $version,
Success, HighPriority)
if getSelectedVersion(params) != version:
switchTo(version, params)
return
# Make sure the archive is downloaded again if the version is special.
if version.isSpecial:
removeDir(params.getDownloadPath($version).splitFile.dir)
# Install the new version and pin it.
installVersion(version, params)
pinChannelVersion(channel, $version, params)
display("Updated", "to " & $version, Success, HighPriority)
# If the currently selected channel is the one that was updated, switch to
# the new version.
if getCurrentChannel(params) == channel:
switchTo(version, params)
proc show(params: CliParams) =
let channel = getCurrentChannel(params)
let path = getSelectedPath(params)
let (_, version) = getNameVersion(path)
if version != "":
display("Selected:", version, priority = HighPriority)
if channel.len > 0:
display("Channel:", channel, priority = HighPriority)
else:
display("Channel:", "No channel selected", priority = HighPriority)
display("Path:", path, priority = HighPriority)
var versions: seq[string] = @[]
for path in walkDirs(params.getInstallDir() & "/*"):
let (_, versionAvailable) = getNameVersion(path)
versions.add(versionAvailable)
if versions.len() > 1:
versions.sort(system.cmp, Descending)
if versions.contains("#head"):
versions.del(find(versions, "#head"))
versions.insert("#head", 0)
if versions.contains("#devel"):
versions.del(find(versions, "#devel"))
versions.insert("#devel", 0)
echo ""
display("Versions:", " ", priority = HighPriority)
for ver in versions:
if ver == version:
display("*", ver, Success, HighPriority)
else:
display("", ver, priority = HighPriority)
proc versions(params: CliParams) =
let currentChannel = getCurrentChannel(params)
let currentVersion = getCurrentVersion(params)
let specialVersions = getSpecialVersions(params)
let localVersions = getInstalledVersions(params)
let remoteVersions =
if params.onlyInstalled: @[]
else: getAvailableVersions(params)
proc isActiveTag(params: CliParams, version: Version): string =
let tag =
if version == currentVersion: "*"
else: " " # must have non-zero length, or won't be displayed
return tag
proc isLatestTag(params: CliParams, version: Version): string =
let tag =
if isLatestVersion(params, version): " (latest)"
else: ""
return tag
proc canUpdateTag(params: CliParams, channel: string): string =
let version = getChannelVersion(channel, params, live = (not params.onlyInstalled))
let channelVersion = parseVersion(version)
let tag =
if canUpdate(channelVersion, params): " (update available!)"
else: ""
return tag
#[ Display version information,now that it has been collected ]#
if currentChannel.len > 0:
display("Channel:", currentChannel & canUpdateTag(params, currentChannel), priority = HighPriority)
echo ""
# local versions
display("Installed:", " ", priority = HighPriority)
for version in localVersions:
let activeDisplay =
if version == currentVersion: Success
else: Message
display(isActiveTag(params, version), $version & isLatestTag(params, version), activeDisplay, priority = HighPriority)
for version in specialVersions:
display(isActiveTag(params, version), $version, priority = HighPriority)
echo ""
# if the "--installed" flag was passed, don't display remote versions as we didn't fetch data for them.
if (not params.onlyInstalled):
display("Available:", " ", priority = HighPriority)
for version in remoteVersions:
if not (version in localVersions):
display("", $version & isLatestTag(params, version), priority = HighPriority)
echo ""
proc performAction(params: CliParams) =
# Report telemetry.
report(initEvent(ActionEvent), params)
case params.command.normalize
of "update":
update(params)
of "show":
show(params)
of "versions":
versions(params)
else:
choose(params)
proc handleTelemetry(params: CliParams) =
if params.hasPendingReports():
display("Info:", "Waiting 5 secs for remaining telemetry data to be sent.",
priority=HighPriority)
waitForReport(5, params)
if params.hasPendingReports():
display("Warning:", "Could not send all telemetry data.",
Warning, HighPriority)
when isMainModule:
var error = ""
var hint = ""
var params = newCliParams(proxyExeMode = false)
try:
parseCliParams(params)
createDir(params.chooseNimDir)
discard loadAnalytics(params)
performAction(params)
except NimbleError:
let currentExc = (ref NimbleError)(getCurrentException())
(error, hint) = getOutputInfo(currentExc)
# Report telemetry.
report(currentExc, params)
report(initEvent(ErrorEvent, label=currentExc.msg), params)
if error.len > 0:
displayTip()
display("Error:", error, Error, HighPriority)
if hint.len > 0:
display("Hint:", hint, Warning, HighPriority)
handleTelemetry(params)
quit(QuitFailure)
handleTelemetry(params)