ujimushi(@旧sradjp(15364))の日記

旧スラドの日記の引越先です

PlutoPlotly.jlが特定のLinux環境でエラー→原因はBaseDirs.jl→イシュー作成から即日改修済み

最近Pluto.jlPlots.jlのお試し用に色々使っているのだが, そういやPlutoPlotly.jl ってあったよなぁと思ってPluto.jl上で,

using PlutoPlotly

とするといきなりエラー。まる一日かかってようやく原因が分かる。

最初はよく分からなかったのだが,エラーメッセージの'が'原因を解く鍵だった。

julia> using PlutoPlotly
Precompiling PlutoPlotly
         Info Given PlutoPlotly was explicitly requested, output will be shown live 
ERROR: LoadError: InitError: StringIndexError: invalid index [41], valid nearby indices [39]=>'プ', [42]=>'\"'
Stacktrace:
  [1] string_index_err(s::String, i::Int64)
    @ Base ./strings/string.jl:12
   [2] SubString{String}(s::String, i::Int64, j::Int64)
    @ Base ./strings/substring.jl:35
  [3] SubString
    @ ./strings/substring.jl:41 [inlined]
  [4] SubString
    @ ./strings/substring.jl:47 [inlined]
  [5] SubString
    @ ./strings/substring.jl:43 [inlined]
  [6] getindex
    @ ./strings/substring.jl:292 [inlined]
  [7] parseuserdirs(configdir::String)
    @ BaseDirs ~/.julia/packages/BaseDirs/tgw06/src/unix.jl:23
  [8] reload()
    @ BaseDirs ~/.julia/packages/BaseDirs/tgw06/src/unix.jl:56
  [9] __init__()
    @ BaseDirs ~/.julia/packages/BaseDirs/tgw06/src/BaseDirs.jl:38

まず答えから言うと,BaseDirs/src/unix.jlの23行目 が原因で,そのまま原因の関数のソースを抜粋してみる。

function parseuserdirs(configdir::String)
    validnames = ("XDG_DESKTOP_DIR", "XDG_DOWNLOAD_DIR", "XDG_TEMPLATES_DIR",
                  "XDG_PUBLICSHARE_DIR", "XDG_DOCUMENTS_DIR", "XDG_MUSIC_DIR",
                  "XDG_PICTURES_DIR", "XDG_VIDEOS_DIR")
    userdirsfile = joinpath(configdir, "user-dirs.dirs")
    if isfile(userdirsfile)
        keys = Symbol[]
        values = String[]
        for line in Iterators.map(strip, eachline(userdirsfile))
            if !startswith(line, '#') && occursin('=', line)
                key, value = split(line, '=', limit=2)
                if key in validnames
                    if startswith(value, '"') && endswith(value, '"')
                        value = unescape_string(value[2:end-1])   # 23行目
                    end
                    if startswith(value, "\$HOME")
                        value = string(homedir(), value[6:end])
                    end
                    if startswith(value, '/')
                        push!(keys, Symbol(key))
                        push!(values, value)
                    end
                end
            end
        end
        NamedTuple{Tuple(keys)}(values)
    else
        (;)
    end
end

ソースの中身としてはuserdirsfileの設定ファイルを読み込んでvalueを加工してキー・バリューのNamedTupleを作ろうとしているのだが,

一目value[2:end-1]が原因というのが分かる。utf-8でマルチバイト文字がある時,1文字のうちの2byte目以降のインデックスを 参照すると一発エラーとなるからだ。

ちなみにuserdirsfileはだいたい~/.config/user-dirs.dirsで,コメント行を除くと私の使っている ubuntu 23.10では次のような感じ。

XDG_DESKTOP_DIR="$HOME/デスクトップ"
XDG_DOWNLOAD_DIR="$HOME/ダウンロード"
XDG_TEMPLATES_DIR="$HOME/テンプレート"
XDG_PUBLICSHARE_DIR="$HOME/公開"
XDG_DOCUMENTS_DIR="$HOME/ドキュメント"
XDG_MUSIC_DIR="$HOME/ミュージック"
XDG_PICTURES_DIR="$HOME/ピクチャ"
XDG_VIDEOS_DIR="$HOME/ビデオ"

大体ubuntu Desktopで日本語インストールしてそのままならこういう設定になっている。エラーに「プ」があるので,この一行目でエラーになっているらしい。再現するのも簡単で,Replで次のようにして再現できた。

julia> line="XDG_DESKTOP_DIR=\"\$HOME/デスクトップ\""
"XDG_DESKTOP_DIR=\"\$HOME/デスクトップ\""

julia> key, value = split(line, '=', limit=2)
2-element Vector{SubString{String}}:
 "XDG_DESKTOP_DIR"
 "\"\$HOME/デスクトップ\""

julia> value[2:end-1]
ERROR: StringIndexError: invalid index [41], valid nearby indices [39]=>'プ', [42]=>'\"'
Stacktrace:
 [1] string_index_err(s::String, i::Int64)
   @ Base ./strings/string.jl:12
 [2] SubString{String}(s::String, i::Int64, j::Int64)
   @ Base ./strings/substring.jl:35
 [3] SubString
   @ ./strings/substring.jl:41 [inlined]
 [4] SubString
   @ ./strings/substring.jl:47 [inlined]
 [5] SubString
   @ ./strings/substring.jl:43 [inlined]
 [6] getindex(s::SubString{String}, r::UnitRange{Int64})
   @ Base ./strings/substring.jl:292
 [7] top-level scope
   @ REPL[4]:1

まぁindexを使わずに先頭と最後の"をとってしまえばいいので, 厳密に直すならreplace(value, r"^\"" => "", r"\"$" => "") とか 少し横着するなら strip(value, '"')ぐらいでもOKか。

julia> replace(value, r"^\"" => "", r"\"$" => "")
"\$HOME/デスクトップ"

julia> strip(value, '"')
"\$HOME/デスクトップ"

プルリクを出すか,イシューを上げるか…

どちらで直すかよく分からないので,イシューかな?

一応,暫定でLinux側の設定を修正しない時は

(@v1.10) pkg> dev BaseDirs

から,~/.julia/dev/BaseDirs/src/unix.jlを次のような感じで修正した後, パッケージモードからadd PlutoPlotlyができるようになるのでやっておく。

index 3f10214..d31aab8 100644
--- a/src/unix.jl
+++ b/src/unix.jl
@@ -20,7 +20,7 @@ function parseuserdirs(configdir::String)
                 key, value = split(line, '=', limit=2)
                 if key in validnames
                     if startswith(value, '"') && endswith(value, '"')
-                        value = unescape_string(value[2:end-1])
+                        value = unescape_string(strip(value, ['"']))
                     end
                     if startswith(value, "\$HOME")
                         value = string(homedir(), value[6:end])

Plutoのノートブックからは最初の方のセルに

begin
    use Pkg
    Pkg.develop("BaseDirs")
    using PlutoPlotly
end

とかやると,Plutoのパッケージ管理から外れた状態で実行可能となる。

まぁ修正依頼かけるしかないよなぁ…めんどい。

追記(2/17 18:00過ぎ)

イシュー投げたら速攻で対応 され,しかもindexアクセスしていた他のソースも修正され,テストも追加されていました。 私と違って全然仕事早いっすわ。こんな人になりたかった。

chop関数知らんかったっす。また参考にします。