summaryrefslogtreecommitdiffhomepage
path: root/vhosts/blog/public/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html
blob: 39517f9078d565324c7001ed7bba7dec3a5ed312 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
<!DOCTYPE html>
<html lang="ja-JP">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="author" content="nsfisis">
    <meta name="copyright" content="&copy; 2024 nsfisis">
    <meta name="description" content="Go言語の text/template における with や range は &quot;.&quot; を上書きする。これらの内側から外側の &quot;.&quot; にアクセスする方法を調べた。">
    <meta name="keywords" content="Go">
    <meta property="og:type" content="article">
    <meta property="og:title" content="【Go】 text/template の with や range の内側から外側の &quot;.&quot; にアクセスする|REPL: Rest-Eat-Program Loop">
    <meta property="og:description" content="Go言語の text/template における with や range は &quot;.&quot; を上書きする。これらの内側から外側の &quot;.&quot; にアクセスする方法を調べた。">
    <meta property="og:site_name" content="REPL: Rest-Eat-Program Loop">
    <meta property="og:locale" content="ja_JP">
    <link rel="icon" type="image/svg+xml" href="/favicon.svg">
    <title>【Go】 text/template の with や range の内側から外側の &quot;.&quot; にアクセスする|REPL: Rest-Eat-Program Loop</title>
    <link rel="stylesheet" href="/style.css?h=ad87bb00eb411fe20ba3a8366c69e490">
  </head>
  <body class="single">
    <header class="header">
      <div class="site-logo">
        <a href="/">REPL: Rest-Eat-Program Loop</a>
      </div>
      <nav class="nav">
        <ul>
          <li>
            <a href="/about/">About</a>
          </li>
          <li>
            <a href="/posts/">Posts</a>
          </li>
          <li>
            <a href="/slides/">Slides</a>
          </li>
          <li>
            <a href="/tags/">Tags</a>
          </li>
        </ul>
      </nav>
    </header>
    <main class="main">
      <article class="post-single">
        <header class="post-header">
          <h1 class="post-title">【Go】 text/template の with や range の内側から外側の &quot;.&quot; にアクセスする</h1>
          <ul class="post-tags">
            <li class="tag">
              <a href="/tags/go/">Go</a>
            </li>
          </ul>
        </header>
        <div class="post-content">
          <section id="changelog">
            <h2><a href="#changelog">更新履歴</a></h2>
            <ol>
              <li class="revision">
                <time datetime="2024-08-19">2024-08-19</time>: 公開
              </li>
            </ol>
          </section>
          <section id="section--tldr">
            <h2><a href="#section--tldr">TL;DR</a></h2>
            <p>
              常にトップレベルを指す特殊変数 <code>$</code> を使えばよい。
            </p>
          </section>
          <section id="section--intro">
            <h2><a href="#section--intro">はじめに</a></h2>
            <p>
              Go には、標準ライブラリにテンプレートライブラリ <code>text/template</code> がある。 この <code>text/template</code> における制御構造、<code>with</code> と <code>range</code> は次のように使われる。
            </p>
            <div class="codeblock">
              <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span># {{ .Title }}</span></span>
<span class="line"><span></span></span>
<span class="line"><span># User</span></span>
<span class="line"><span></span></span>
<span class="line"><span>{{ with .User }}</span></span>
<span class="line"><span>  {{ .Name }} ({{ .ID }})</span></span>
<span class="line"><span>{{ end }}</span></span>
<span class="line"><span></span></span>
<span class="line"><span># Items</span></span>
<span class="line"><span></span></span>
<span class="line"><span>{{ range .Items }}</span></span>
<span class="line"><span>  - {{ . }}</span></span>
<span class="line"><span>{{ end }}</span></span></code></pre>
            </div>
            <p>
              <code>text/template</code> の <code>.</code> は、現在の操作対象を表す特殊なオブジェクトである。
            </p>
            <p>
              <code>with</code> や <code>range</code> は、<code>.</code> を変更する効果を持つ。 <code>with</code> は引数に渡されたオブジェクトを <code>.</code> へセットして、内部のテンプレートを実行する。 <code>range</code> は引数に渡されたイテレート可能なオブジェクトに対し、それぞれの要素を <code>.</code> へセットして、要素の個数だけ内部のテンプレートを実行する。
            </p>
            <p>
              つまりこのテンプレートは、次のような構造をレンダリングしている (<code>Execute()</code> の第2引数)。
            </p>
            <div class="codeblock">
              <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#24292E">tmpl.</span><span style="color:#6F42C1">Execute</span><span style="color:#24292E">(out, </span><span style="color:#6F42C1">Params</span><span style="color:#24292E">{</span></span>
<span class="line"><span style="color:#24292E">    Title: </span><span style="color:#032F62">"foo"</span><span style="color:#24292E">,</span></span>
<span class="line"><span style="color:#24292E">    User: </span><span style="color:#6F42C1">User</span><span style="color:#24292E">{</span></span>
<span class="line"><span style="color:#24292E">        ID:   </span><span style="color:#005CC5">123</span><span style="color:#24292E">,</span></span>
<span class="line"><span style="color:#24292E">        Name: </span><span style="color:#032F62">"john"</span><span style="color:#24292E">,</span></span>
<span class="line"><span style="color:#24292E">    },</span></span>
<span class="line"><span style="color:#24292E">    Items: []</span><span style="color:#D73A49">string</span><span style="color:#24292E">{</span></span>
<span class="line"><span style="color:#032F62">        "hoge"</span><span style="color:#24292E">,</span></span>
<span class="line"><span style="color:#032F62">        "piyo"</span><span style="color:#24292E">,</span></span>
<span class="line"><span style="color:#032F62">        "fuga"</span><span style="color:#24292E">,</span></span>
<span class="line"><span style="color:#24292E">    },</span></span>
<span class="line"><span style="color:#24292E">})</span></span></code></pre>
            </div>
          </section>
          <section id="section--what-i-want-to-do">
            <h2><a href="#section--what-i-want-to-do">やりたいこと</a></h2>
            <p>
              今回おこないたいのは、<code>with</code> や <code>range</code> の中で、その外側で使われていたトップレベルのオブジェクトを参照することだ。
            </p>
            <div class="codeblock">
              <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span>{{ with .User }}</span></span>
<span class="line"><span>  ここから .Title を参照するには?</span></span>
<span class="line"><span>{{ end }}</span></span>
<span class="line"><span></span></span>
<span class="line"><span>{{ range .Items }}</span></span>
<span class="line"><span>  ここから .User を参照するには?</span></span>
<span class="line"><span>{{ end }}</span></span></code></pre>
            </div>
            <p>
              <code>with</code> や <code>range</code> は、<code>.</code> を自身の対象オブジェクトに変更するので、 単に <code>{{ with .User }}</code> の中で <code>.Title</code> と書いても、それは <code>User</code> の <code>Title</code> プロパティを参照しているとみなされる。
            </p>
            <p>
              <code>text/template</code> では変数が使えるので、テンプレートの先頭で
            </p>
            <div class="codeblock">
              <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span>{{ $params := . }}</span></span></code></pre>
            </div>
            <p>
              とでもしておけば実現は可能である。
            </p>
            <p>
              しかしながら、頻発するシチュエーションにしてはあまりに不恰好である。よりスマートな方法が用意されているはずだ。
            </p>
          </section>
          <section id="section--solution">
            <h2><a href="#section--solution">解決方法</a></h2>
            <p>
              常にトップレベルを指す特殊変数 <code>$</code> を使えばよい。
            </p>
            <div class="codeblock">
              <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span>{{ with .User }}</span></span>
<span class="line"><span>  {{ $.Title }}</span></span>
<span class="line"><span>{{ end }}</span></span>
<span class="line"><span></span></span>
<span class="line"><span>{{ range .Items }}</span></span>
<span class="line"><span>  {{ $.User.Name }}</span></span>
<span class="line"><span>{{ end }}</span></span></code></pre>
            </div>
            <p>
              <code>$</code> は、テンプレートが実行されるときに渡されたオブジェクトを指す。 これを使えば現在の <code>.</code> に関係なくトップレベルを参照できる。
            </p>
            <p>
              このことは、<a href="https://pkg.go.dev/text/template#hdr-Variables" rel="noreferrer" target="_blank"><code>text/template</code> の公式ドキュメント</a>にも以下のように記載されている。
            </p>
            <blockquote>
              <p>
                When execution begins, $ is set to the data argument passed to Execute, that is, to the starting value of dot.
              </p>
            </blockquote>
          </section>
          <section id="section--reference">
            <h2><a href="#section--reference">参考</a></h2>
            <ul>
              <li>
                <a href="https://stackoverflow.com/questions/14800204/in-a-template-how-do-you-access-an-outer-scope-while-inside-of-a-with-or-rang" rel="noreferrer" target="_blank">直接の出典である Stack Overflow の回答: <span>In a template how do you access an outer scope while inside of a “with” or “range” scope?</span></a>
              </li>
              <li>
                <a href="https://pkg.go.dev/text/template#hdr-Variables" rel="noreferrer" target="_blank">大元の出典である <code>text/template</code> の公式ドキュメント</a>
              </li>
            </ul>
          </section>
        </div>
      </article>
    </main>
    <footer class="footer">
      &copy; 2021 nsfisis
    </footer>
  </body>
</html>