Go 패키지 시리즈 1 템플릿 사용하기

한동안 일에 치어서 블로깅에 소흘했는데 그 말인즉 공부를 안했다는 말인것 같다. 최근에 Go를 보고있는데 재미있어서 빠져드는 중이다. 요즘 트렌드인지 동시성(concurrency)에 상당히 강점을 가지면서 문법이 직관적이라 적응기간도 그리 길지 않은것 같다.

어제는 앱엔진에서 제공하는 도큐먼트를 보고 간단히 guestbook을 따라해 봤는데 템플릿에 대한 설명이 부족하길래 간만에 번역을 해보았다. 원문을 그대로 번역하진 않고 원문과 공식 Go문서를 번갈아 참고하며 정리했다.

글 내용중 좀 더 자세한 함수의 사용법에 대해서는 참고자료의 링크를 참고하도록 하자.

test

템플릿이란?

대부분의 서버사이드 언어들은 정적인 페이지에 스크립팅을 지원하기 위한 언어들을 제공한다. JSP나 PHP 스크립팅 같은 것이 그런 예이다. Go에서는 이와 유사한 스크립팅 언어로 template 패키지를 제공한다.

이 패키지는 다른 스크립트가 그러하든 정적인 텍스트에 리스트와 같은 오브젝트들을 결합하여 동적인 컨텐츠를 생산해낸다.

HTML을 만들어 내기 위해서는 html/template 라는 패키지를 사용한다. 이 패키지는 text/template 패키지와 유사한 기능을 제공하며, 추가로 브라우저에서 보안상의 문제를 해결하고 이스케이프 문자와 같은것들을 안전하게 변환해 주는 기능을 포함하고 있다.

템플릿은 정적인 텍스트와 내장된 커맨드들로 구성된다. 커맨드는 JSP나 PHP와 유사하게 {{ … }} 형식의 구분자로 표기된다.

템플릿에 Go객체가 적용되면, 당연히 템플릿내에서 이 객체의 필드들이 삽입될 수 있다. 템플릿에 적용된 Go객체는 “.”으로 표현된다. 즉, 이 객체가 문자열 이라면 {{.}} 으로 템플릿에 표시할 수 있다. 기본적으로 문자열 표시에는 fmt 패키지를 사용한다.

다음과 같은 구조체 객체가 있다

1
2
3
type Student struct {
Name string
}

이 경우 이름을 표시하려면 {{.Name}} 과 같이 표현할 수 있다.

정의(define)

결국, 템플릿은 변하지 않는 정적인 텍스트에 필요한 경우 특정 부분을 치환하기 위함이 목적이다. 이렇게 변하지 않은 부분을 정의할때 define문을 사용할 수 있다. define문은 “define”과 “end”사이에 템플릿을 선언하여 사용하면 된다.

1
2
3
4
`{{define “T1”}}ONE{{end}}
{{define “T2”}}TWO{{end}
{{define “T3”}}{{template “T1”}} {{template “T2”}}{{end}}
{{template “T3”}}`

이 예제에서 T1, T2 두개의 템플릿을 정의하고 T3는 이 두 템플릿을 사용하여 정의 하였다. 그리고, T3를 출력하여 “ONE TWO” 라는 결과를 출력하게 된다.

병합(merge)

template 패키지내의 Parse, ParseFile, Execute와 같은 메소드를 사용해서 템플릿을 로드하고, 그 후에 객체와 병합을 시도한다. 결국, 컨텐츠는 정의된 타입과 객체의 필드들을 이용하여 만들어 진다. 구조체 객체내 선언된 필드가 외부에 노출되기 위해서는 속성명은 대문자로 시작해야 한다.

예제

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
package main
import (
"os"
"text/template"
)
type Student struct {
//exported field since it begins
//with a capital letter
Name string
}
func main() {
//define an instance
s := Student{"Satish"}
//create a new template with some name
tmpl := template.New("test")
//parse some content and generate a template
tmpl, err := tmpl.Parse("Hello {{.Name}}!")
//A common use of panic is to abort if a function
//returns an error value that we don't know how to
//(or want to) handle.
if err != nil {
panic(err)
}
//merge template 'tmpl' with content of 's'
err1 := tmpl.Execute(os.Stdout, s)
if err1 != nil {
panic(err1)
}
}

소스 설명

  • 이름(test)과 함께 새로운 템플릿 할당
  • Parse는 문자열을 template객체로 변환
  • 필드값을 출력값에 포함시키기 위해서는 필드값을 중괄호로 둘러싸야 합니다. 예제에서, {{.Name}}은 “Student” 인스턴스인 “s”의 “Name”필드를 가르킵니다.
  • panic은 내장된 함수로서 현재 흐름을 중단시키고 패니킹 단계를 수행합니다. Panic은 panic함수를 직접 호출하여 시작될수 있다. 또한, 런타임 오류들 - 배열 인덱스를 잘못 탐색한 경우와 같은 - 에 의해서도 시작될 수 있다.
  • os.Stdout 은 표준 출력을 가르키는 변수로 미리 정의되어 있다. os.Stdout은 io.Writer를 구현한다.
  • Execute는 파싱된 템플릿에 특정 데이터 객체를 병합하여 결과를 표준출력(os.Stdout) 으로 출력한다.
  • error는 interface타입으로 오류 조건에 따라 값이 적용되며, nil일 경우 오류가 없음을 의미한다. Go 코드는 error값을 이용해 비정상적인 상태를 모니터링 한다.

파이프라인 (“|”)

파이프라인은 “|”문자로 구분되는 명령어의 나열이다. 각 커맨드의 결과가 다음 커맨드의 마지막 인자로 넘어가 최종 커맨드에 의해 출력되는 값이 파이프라인의 값이 된다.

1
{{"put" | printf "%s%s" "out" | printf "%q"}}

range 커맨드

다음 코드를 person.go 라는 파일로 저장하고 실행해 보자.

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
package main
import (
"os"
"text/template"
)
type Person struct {
Name string
Emails []string
}
const tmpl = `The name is {{.Name}}.
{{range .Emails}}
His email id is {{.}}
{{end}}
`
func main() {
person := Person{
Name: "Satish",
Emails: []string{"satish@rubylearning.org", "satishtalim@gmail.com"},
}
t := template.New("Person template")
t, err := t.Parse(tmpl)
if err != nil {
panic(err)
}
err = t.Execute(os.Stdout, person)
if err != nil {
panic(err)
}
}

range 커맨드는 person.Emails 슬라이스를 순환하면서 값을 출력한다.

1
2
3
4
5
The name is Satish.
His email id is satish@rubylearning.org
His email id is satishtalim@gmail.com

변수

template 패키지는 변수를 정의하고 사용할 수 있다. 위의 예제에서 이메일 주소앞에 이름을 함께 출력하려면 다음과 같이 코드를 수정할 수 있다.

1
2
3
4
{{$name := .Name}}
{{range .Emails}}
Name is {{$name}}, email is {{.}}
{{end}}

range 스코프에서 .은 person.Email 이므로 person.Name을 사용하기 위해서는 해당 스코프 밖에서 미리 변수를 정의하고, range 스코프에서 사용하면 된다. 변수는 “$”를 접두사로 사용하여 $name := .Name 과 같이 정의하고 range 스코프 안에서 접근해 사용하면 된다.

html/template

html/template는 HTML결과를 출력하기 위한 템플릿 패키지이다. text/template과 사용법이 비슷하며, 코드 인젝션(code injection)으로부터 안전한 출력을 위한 함수들을 추가로 지원한다.

결론

Go의 template 패키지는 아쉽게도 Regular Expression과 같은 강력한 기능을 제공하지는 않는다. 하지만, 사용법이 간단하고 일반적인 텍스트 치환은 큰 불편없이 사용할 수 있으니 사용에 무리는 없어 보인다.

참고자료

Learn to build and deploy simple Go Web Apps, Part Three
text/template
html/template