티스토리 뷰
젠시(Genshi)는 웹 출력을 위하여 HTML, XML등의 텍스트 기반 자료를 분석, 처리 및 생성 할수 있는 파이썬 라이브러리로 핵심 기능은 태그, 속성, 내용을 분리해서 처리할 수 있는 템플릿 언어 입니다(http://genshi.edgewall.org/). Trac의 사용자 인터페이스를 위하여 개발되었지만 별도의 라이브러리 형태로 배포 되고 있어 필요에 따라 적절하게 사용할 수 있습니다.
홈페이지 로고에서 젠시의 이름을 유추해 보면 젠시는 직물을 만드는 원료가 되는 실을 가리키는 원사(原絲)의 중국어 발음에 따온 모양입니다. 템플릿이 하는 역할에 비추어 보면 의미가 통합니다. 본 포스팅에서는 Trac에서 실제 사용된 젠시의 기능을 중심으로 다루고자 합니다.
■ 마크업 스트림(Markup streams)
마크업 스트림은 XML이나 HTML 마크업 요소들의 이벤트 흐름으로 정의할 수 있습니다. 문자열로 된 XML이나 HTML을 파싱하거나 마크업 스트림의 일부를 잘라내거나 프로그램에서 직접 생성하는 방식으로 만들어 집니다.
아래 코드는 trac/mimeview/tests/patch.py 코드의 일부로 XML을 파싱하여 마크업 스트림으로 만드는 과정과 마크업 스트림을 문자열로 내보내는 사례가 모두 담겨 있습니다.
from genshi.core import Stream from genshi.input import HTMLParser, XML from trac.mimeview.api import Mimeview, RenderingContext from trac.mimeview.patch import PatchRenderer from trac.test import EnvironmentStub, Mock, MockPerm from trac.web.chrome import Chrome, web_context from trac.web.href import Href class PatchRendererTestCase(unittest.TestCase): def setUp(self): env = EnvironmentStub(enable=[Chrome, PatchRenderer]) req = Mock(base_path='', chrome={}, args={}, session={}, abs_href=Href('/'), href=Href('/'), locale='', perm=MockPerm(), authname=None, tz=None) self.context = web_context(req) self.patch = Mimeview(env).renderers[0] patch_html = open(os.path.join(os.path.split(__file__)[0], 'patch.html')) self.patch_html = Stream(list(HTMLParser(patch_html, encoding='utf-8'))) def _expected(self, expected_id): return self.patch_html.select('//div[@id="%s"]/div' % expected_id) def _test(self, expected_id, result): expected = self._expected(expected_id).render(encoding='utf-8') result = XML(result.render(encoding='utf-8')).render(encoding='utf-8') expected, result = expected.splitlines(), result.splitlines() for exp, res in zip(expected, result): self.assertEquals(exp, res) self.assertEquals(len(expected), len(result))
마크업 스트림을 생성하는 함수는 genshi.input 모듈에 있는 XMLParser, HTMLParser 클래스를 기반으로 한것으로 XML(), HTML() 함수는 XMLParser, HTMLParser 클래스를 사용하여 재사용 가능한 마크업 스트림을(Stream 클래스) 리턴합니다.
def XML(text): return Stream(list(XMLParser(StringIO(text)))) def HTML(text, encoding=None): return Stream(list(HTMLParser(BytesIO(text), encoding=encoding)))
리턴되는 마크업 스트림은 문자열로 된 이벤트 종류(START, END, TEXT, START_NS, END_NS, DOCTYPE, COMMENT, PI, START_CDATA, END_CDATA), 데이터, 위치(파일, 라인, 컬럼) 를 하나의 레코드 단위로 합니다. 다음은 HTML 함수를 이용하여 마크업 스트림을 생성하고 그 내용을 출력한 예제 입니다.
>>> from genshi.input import HTML >>> html = HTML('<body><h1>Foo</h1></body>', encoding='utf-8') >>> for kind, data, pos in html: print('%s %r %r' % (kind, data, pos)) START (QName('body'), Attrs()) (None, 1, 0) START (QName('h1'), Attrs()) (None, 1, 6) TEXT u'Foo' (None, 1, 10) END QName('h1') (None, 1, 13) END QName('body') (None, 1, 18)
마크업 스트림에 대한 수정 및 변경 작업은 필터라 부르는 함수에 의해 수행되는데 필터(Filter)는 Genshi에서 제공하는 내장 함수를 사용할 수 도 있고 마크업 스트림(Stream)을 인수로 하는 함수를 직접 작성해서 사용할 수도 있습니다. 다음은 Genshi에서 제공하는 내장 필터들입니다.
HTMLFormFiller :
폼내 필드들의 값을 사전 타입 형태로 설정할 수 있는 필터로 마크업 스트림내에서 이름을 찾아 해당 하는 값을 설정합니다. 사용 예제는 다음과 같습니다.(from genshi.filters import HTMLFormFiller)
filler = HTMLFormFiller(data=dict(username='john', remember=True))HTMLSanitizer :
자바스크립트등으로 작성된 위험성 있는 부분을 삭제하는 필터(from genshi.filters import HTMLSanitizer)Transformer :
특정 마크업의 선택, 매핑, 편집등 마크업 스트림의 변형을 돕는 필터(from genshi.filters import Transformer)Translator :
다국어 기반의 번역에 사용되는 필터(from genshi.filters import Translator)
필터를 사용하는 방법은 마크업 스트림을 인수로 직접 필터를 호출하는 방법이 있고, Stream 클래스의 fileter 메쏘드에 필터들을 넘겨주는 방법(여러 필터 전달 가능)과 마크업 스트림에 | 연산자로 필터를 적용하는 방법이 있습니다.
mstream = mstream.filter(myfilter, HTMLSanitizer ()) mstream = mstream | myfilter | HTMLSanitizer ()
필터링하거나 변형을 거친 마크업 스트림은 스트링 내보내기(직렬화, Serialization)로 정보 전송이나 저장에 활용할 수 있는데, Stream 클래스는 serialize() 메쏘드와 render() 메쏘드를 기본 제공 합니다. serialize는 각 마크업 단위의 오브젝트를 돌려주고, render()는 하나의 스트링을 리턴 합니다. 두 메쏘드 모두 스트링 이나 genshi.output에 있는 직렬화 클래스로 출력 형태를 다음과 같이 지정할 수 있습니다.
xml : XMLSerializer
xhtml : XHTMLSerializer
html : HTMLSerializer
text : TextSerializer
위의 설명에서 필터를 |(pipe) 연산자로 사용했듯이 직렬화 클래스도 | 연산자로 사용할 수 있습니다.
print(stream | HTMLSanitizer() | TextSerializer())
■ 템플릿 사용하기
젠시에서 제공하는 템플릿 엔진은 XML이나 HTML에 대한 마크업 템플릿과 단순 텍스트 파일에 대한 텍스트 템플릿 기능을 제공합니다. 템플릿 기능은 "from genshi.template import MarkupTemplate" 처럼 genshi.template 모듈에서 MarkupTemplate 또는 TextTemplate을 import하여 사용합니다.
템플릿을 사용하는 단계는 우선 TextTemplate나 MarkupTemplate으로 파일이나 문자열로 부터 직접 템플릿을 얻거나 TemplateLoader를 통해서 템플릿을 로드합니다. 다음은 Trac의 템플릿 로드 관련 코드 입니다.
def load_template(self, filename, method=None): """Retrieve a Template and optionally preset the template data. Also, if the optional `method` argument is set to `'text'`, a `NewTextTemplate` instance will be created instead of a `MarkupTemplate`. """ if not self.templates: self.templates = TemplateLoader( self.get_all_templates_dirs(), auto_reload=self.auto_reload, max_cache_size=self.genshi_cache_size, default_encoding="utf-8", variable_lookup='lenient', callback=lambda template: Translator(translation.get_translations()).setup(template)) if method == 'text': cls = NewTextTemplate else: cls = MarkupTemplate return self.templates.load(filename, cls=cls)
템플릿을 로드한 다음에는 템플릿의 generate() 메쏘드를 통해서 이름=값 인수 형태로 템플릿을 조립하여 스트림을 생성 합니다. 끝으로 스트림의 render 메쏘드로 결과를 내보내면 된다.
XML, HTML 또는 텍스트 파일로 작성하는 템플릿에는 아래의 예제 처럼 간단한 파이썬 표현 또는 코드 블럭을 삽입할 수 있는데 파이썬 표현은 ${표현}의 형태로 기술하고 코드 블럭은 <?python과 ?>사이에 파이썬 코드를 기술하면 됩니다. ${표현}이 함수 호출 등의 복잡한 문장이 아니고 영문자로 시작해서 영숫자, ., _만으로 기술되는 경우에는 {}는 생략할 수 있습니다. 또한 $ 문자를 표시하려면 $$식으로 두개를 겹치면 됩니다.
아래의 코드는 trac/wiki/templates/wiki_view.html 템플릿중 일부 입니다.
<div class="wikipage searchable" py:choose="" xml:space="preserve"> <py:when test="page.exists"> <div id="wikipage" class="trac-content" py:content="wiki_to_html(context, text)" /> <?python last_modification = (page.comment and _('Version %(version)s by %(author)s: %(comment)s', version=page.version, author=format_author(page.author), comment=page.comment) or _('Version %(version)s by %(author)s', version=page.version, author=format_author(page.author))) ?> <div py:if="not version" class="trac-modifiedby"> <span i18n:msg="reldate"> <a href="${href.wiki(page.name, action='diff', version=page.version)}" title="$last_modification">Last modified</a> ${pretty_dateinfo(page.time)} </span> <span class="trac-print" i18n:msg="date">Last modified on ${format_datetime(page.time)}</span> </div> </py:when> <py:otherwise> <p i18n:msg="name">The page ${name_of(page.resource)} does not exist. You can create it here.</p> <py:if test="higher"> <p>You could also create the same page higher in the hierarchy:</p> <ul> <li py:for="markup in higher">${markup}</li> </ul> </py:if> </py:otherwise> </div>
텍스트 템플릿에서도 동일한 파이썬 표현 방식을 사용하는데 코드 블럭 표현을 {% %} 사이에 기술하는 것으로 차이가 있습니다. 텍스트 템플릿은 메일 전송 등에 많이 사용하는데 다음은 Trac의 티켓 통보 메일에 사용되는 템플릿 예제 입니다.
$ticket_body_hdr $ticket_props {% choose ticket.new %}\ {% when True %}\ $ticket.description {% end %}\ {% otherwise %}\ {% if changes_body %}\ ${_('Changes (by %(author)s):', author=change.author)} $changes_body {% end %}\ {% if changes_descr %}\ {% if not changes_body and not change.comment and change.author %}\ ${_('Description changed by %(author)s:', author=change.author)} {% end %}\ $changes_descr -- {% end %}\ {% if change.comment %}\ ${_('Comment:') if changes_body else _('Comment (by %(author)s):', author=change.author)} $change.comment {% end %}\ {% end %}\ {% end %}\ -- ${_('Ticket URL: <%(link)s>', link=ticket.link)} $project.name <${project.url or abs_href()}> $project.descr
템플릿 조립 과정에서 템플릿에 있는 변수가 제공되지 않는다면 오류가 발생 합니다(strict 모드). 그런데 이런 경우라 할지라도 오류를 발생시키지 않고 진행할 수 있도록 할 수 있는데 이를 lenient 모드라 하고 MarkupTemplate, NewTextTemplate 에서는 인수로 lookup='lenient'를 넘겨주고 TemplateLoader에서는 variable_lookup='lenient'를 인수로 넘겨주면 됩니다. 단, lenient 모드에서도 특정 오브젝트가 아니라 오브젝트.속성식으로 2차적인 참조가 있고 해당 오브젝트가 제공되지 않으면 오류가 발생합니다.
위의 예제 코드 wiki_view.html를 보면 py:if, py:for등이 HTML 파일에 포함되어 있는데 이들은 템프릿에 포함되어 조립 과정을 지시하는 XML 템플릿 언어의 일부 입니다. 아래는 Genshi의 XML 템플릿 언어가 지원하는 지시자들 입니다. XML 템플릿 언어를 사용하려면 <html>태그에 "xmlns:py="http://genshi.edgewall.org/" 네임 스페이스를 추가해 주어야 합니다. 지시자들은 <li>같은 태그의 속성 형태로 <li py:for="markup in higher">와 같이 py:지시자 형태로 사용할 수도 있고 태그 처럼 <py:지시자 tst=""> 형태로 <py:if test="higher">와 같이 사용할 수도 있습니다. 일반 태그 처럼 지시자를 기술한 경우에는 </py:if> 처럼 닫는 태그도 기술해 주어야 합니다.
* py:if
지정한 조건이 True일때만 연관 블럭을 표시 합니다.
* py:choose
py:when, py:otherwise와 함께 복합 조건을 기술할 수 있습니다. 위의 예제에서도 사용되고 있는데 앞 부분에 <py:choose>이 기술되어 있습니다.
* py:for
루프를 구성할 수 있습니다. 태그 속성 방식으로 py:for="item in items"로 기술했다면 태그 방식으로는 <py:for each="item in items">로 기술 합니다. items 에는 배열이나 사전등의 타입이 올 수 있습니다.
* py:def
템플릿 내에서 반복적으로 사용할 수 있는 매크로를 정의 합니다. 속성 또는 태그 방식으로(function=""으로 지정) 기술하고 템플릿에서 파이썬 표현식으로 함수호출 처럼 기술하면 해당하는 매크로 블럭을 표시합니다. 인수를 지정할 수도 있습니다.
<div>
<p py:def="greeting(name)" class="greeting">
Hello, ${name}!
</p>
${greeting('world')}
${greeting('everyone else')}
</div>
위의 예제는 <p>태그 블럭이 매크로로 지정되었는데 아래의 ${greeting('world')}와 ${greeting('everyone else')}가 없다면 <p>태그 블럭은 표시되지 않습니다. ${greeting('world')}를 기술하면 인수로 world가 전달되고 <p>태그 내에 "Hello, world"가 표시되는 방식 입니다. 위의 예제를 태그 방식으로 기술하면 <py:def function="greeting(name)"> 입니다.
* py:match
py:match="body"와 같은 방식으로 특정 태그를 찾고(검색 스트링은 XPath 표현법으로 기술 합니다 - http://www.w3.org/TR/xpath/ 참조) 검색된 태그 블럭을 py:match 블럭내부에서 select(경로) 함수를 통해 추출하여 표시하는 지시자 입니다. py:match를 <py:match path="body" once="true">처럼 태그 방식으로 기술하는 경우에는 path로 검색 경로를 지정하고 once(기본값 false, 한번만 검색할지 여부), buffer(기본값 true, 검색된 태그를 메모리에 버퍼링할지 여부), recursive(기본값 true, match 과정을 자체 출력 과정에도 적용할지 여부) 등의 검색 힌트를 지정할 수 있습니다. 다음은 trac/wiki/templates/wiki_diff.html에 있는 템플릿 일부 입니다.
<py:match path="div[@id='content']" once="true"><div py:attrs="select('@*')">
${select('*|text()')}
<form py:if="'WIKI_DELETE' in perm(page.resource) and
(not changes[0].diffs or new_version == latest_version)"
method="get" action="${href.wiki(page.name)}">
<div>
<input type="hidden" name="action" value="delete" />
<input type="hidden" name="version" value="$new_version" />
<input type="hidden" name="old_version" value="$old_version" />
<py:choose>
<input py:when="new_version - old_version > 1" type="submit" name="delete_version" value="${_('Delete version %(old_version)d to version %(version)d', old_version=(old_version + 1), version=new_version)}" />
<input py:otherwise="" type="submit" name="delete_version" value="${_('Delete version %(version)d', version=new_version)}" />
</py:choose>
</div>
</form>
</div></py:match>
위 코드에 있는 py:match는 id가 content인 <div>태그를 찾아서 '@*' XPath를 통해 div의 모든 속성을 가져오고, '*|text()' XPath로 <div>내부의 모든 요소를 가져오도록 한 템플릿 입니다.
* py:with
변수에 값을 설정하는 표현식을 기술하는 지시자로 태그 방식으로 기술할 때는 <py:with vars="y=7; z=x+10">와 같이 vars를 사용 합니다. 다음은 trac/templates/layout.html 템플릿의 일부로 py:match에서 추출된 값을 변수에 값을 설정하는데 사용하는 예제 입니다.
<py:match path="head" once="true"><head>
<title py:with="title = list(select('title/text()'))">
<py:if test="title">${title} – </py:if>${project.name or 'Trac'}
</title>
* py:attrs
태그의 속성을 설정하는 지시자로 단순 변수로 지정하는 경우에는 { 'class' : 'title' }과 같이 사전 타입으로 속성을 설정할 수 있고 py:attrs="select('@*')" 처럼 py:match의 결과를 가져올 수도 있습니다. 단 py:attrs는 태그 방식으로 기술할 수는 없습니다.
* py:content
태그의 속성에서만 기술할 수 있고 표현식 결과를 태그의 내용으로 설정 합니다. 아래의 코드는 trac/ticket/templates/milestone_edit.html 템플릿의 일부로 선택 박스의 내용을 설정하고 있습니다. for 루프 속에서 <option>과 </option> 사이에 값을 설정 합니다.
<option py:for="milestone in milestones" value="${milestone.name}" py:content="milestone.name"></option>
* py:replace
해당 블럭을 표현식 결과 값으로 태그를 포함하여 대치 시킵니다. 태그 방식으로 표현할 때는 value를 사용하여 <py:replace value="title">___</py:replace>와 같이 기술하여 title 변수 값으로 <py:replace>태그 전체를 대치 시킵니다.
* py:strip
태그 속성으로 기술되어 조건이 True이거나 비어(empty)있으면 해당 태그를(종료 태그 포함) 지웁니다. 해당 태그를 지우고 그 내부의 요소들은 그대로 남기는 효과를 냅니다. 아래의 코드는 trac/templates/about.html 템플릿의 일부로 url 값이 없으면 <a> 태그를 지워 의미 없는 링크가 만들어지지 않도록 간단히 처리하는 모습 입니다.
<th py:with="url = plugin.info.home_page or plugin.info.author_email and 'mailto:' + plugin.info.author_email">
<a py:strip="not url" href="$url">$plugin.name</a>
</th>
* xi:include
다른 템플릿을 포함시킬때 사용하는 것으로 <html>태그에 xmlns:xi="http://www.w3.org/2001/XInclude"와 같이 xi:에 대한 네임 스페이스를 지정해 주어야 합니다. href로 경로를 지정하며 경로에 표현식을 사용할 수 있어 동적으로 포함시킬 템플릿을 지정할 수 있고 다른 py:for, py:if 지시자들과 함께 사용하여 조건/다중 인클루드도 가능 합니다. 다음은 trac/templates/layout.html 템플릿의 일부로 동적인 파일명 지정과 <xi:fallback />로 포함시킬 파일이 없더라도 오류가 표시되지 않도록 했음을 확인할 수 있습니다. 또한 parse="text"를 추가해 주면 지정한 파일을 마크업 템플릿이 아닌 텍스트 템플릿으로 로딩할 수도 있습니다.
<xi:include href="$chrome.theme"><xi:fallback /></xi:include>
<xi:include href="site.html"><xi:fallback /></xi:include>
* i18n:msg
파라미터와 태그 마크업을 감안한 복잡한 다국어 변환을 위한 지시자로 사용을 위해서는 <html>태그에 xmlns:i18n="http://genshi.edgewall.org/i18n" 네임 스페이스를 추가해 주어야 합니다. 아래는 trac/templates/attachment.html 템플릿의 일부로 파라미터 4개로 구성된 다국어 기반 메시지를 표시하는 템플릿이고
<th scope="col" i18n:msg="file,size,author,date">
File $attachment.filename,
<span title="${_('%(size)s bytes', size=attachment.size)}">${pretty_size(attachment.size)}</span>
(added by ${authorinfo(attachment.author)}, ${pretty_dateinfo(attachment.date)})
</th>
아래는 <th>...</th>사이의 메시지를 아이디로 하는 프랑스어 번역 파일의(Message catalog) 일부 입니다.
msgid ""
"File %(file)s,\n"
" [1:%(size)s]\n"
" (added by %(author)s, %(date)s)"
msgstr ""
"Pièce jointe %(file)s,\n"
" [1:%(size)s]\n"
" (ajoutée par %(author)s, %(date)s)"
템플릿에서 조립된 메시지를 가지고 같은 형태의 메시지 아이디(msgid)를 찾고 해당 언어에 맞는 msgstr로 내용을 대치시키는 것입니다. i18n:msg=""는 메시지 아이디 검색에 사용할 파라미터가 없다는 의미로 메시지 내부에 태그가 없다면 단순 번역 작업과 동일한 의미 입니다. 번역 파일에 %(파라미터명)s는 템플릿, msgid, msgstr에서 상호 지정 가능한 파라미터를 의미하고 [순번:스트링]은 단순 태그와 태그 내부 스트링을 나타내고, [순번:%(파라미터명)s]은 태그 블럭 자체를 파라미터로 사용한다는 의미로 위의 템플릿에서는 <span>...</span>블럭이 [1:%(size)s]에 해당 합니다. 아래는 about.html 템플릿의 일부로 파라미터는 없지만 메시지 아이디에 태그가 포함되어 있는 메시지 입니다.
<p i18n:msg="">Trac is distributed under the modified BSD License.<br />
The complete text of the license can be found
<a href="http://trac.edgewall.org/wiki/TracLicense">online</a>
as well as in the <tt>COPYING</tt> file included in the distribution.</p>
msgid ""
"Trac is distributed under the modified BSD License.[1:]\n"
" The complete text of the license can be found\n"
" [2:online]\n"
" as well as in the [3:COPYING] file included in the distribution."
msgstr ""
"El Trac es distribueix sota la Llicència BSD modificada.[1:]\n"
" Es pot trobar el text complet de la llicència\n"
" [2:en línia]\n"
" així com al fitxer [3:COPYING] inclòs a la distribució."
* i18n:choose
단수 표현 및 복수 표현을 위한 것으로 i18n:singular, i18n:plural과 함께 사용 합니다. 아래는 trac/ticket/templates/ticket_change.html 템플릿의 일부 입니다.
<py:if test="change_replies">
<i18n:choose numeral="len(change_replies)">
<span i18n:singular="">follow-up:</span>
<span i18n:plural="">follow-ups:</span>
</i18n:choose>
<py:for each="reply in change_replies">
${commentref('↓ ', reply, 'follow-up')}
</py:for>
</py:if>
위에서 i18n 네임 스페이스 기반의 복잡한 다국어 처리가 아닌 템플릿에서 다국어 처리를 간단히 처리할 수 있는 단순한 방법은 gettext()함수를 사용하거나 줄여서 _()함수를 사용하는 방법 입니다. 아래의 코드는 trac/templates/attachment.html 템플릿의 일부로 버튼 텍스트를 다국어 기반으로 표시하려는 예제 입니다. 이 방법은 메시지 아이디가 고정된 형태일 때 사용할 수 있습니다.
<div class="buttons">
<input type="hidden" name="action" value="new" />
<input type="hidden" name="realm" value="$parent.realm" />
<input type="hidden" name="id" value="$parent.id" />
<input type="submit" value="${_('Add attachment')}" />
<input type="submit" name="cancel" value="${_('Cancel')}" />
</div>
텍스트 템플릿에 사용되는 언어는 XML 템플릿과는 차이가 있는데 위의 메일 템플릿 예제 처럼 {% %} 사이에 지시자를 기술하는 방식 입니다.
{% if 표현식 %}
if 다음 표현식이 참이면 {% end %} 까지의 블럭을 표시 합니다.{% choose 표현식 %}
{% when 표현식 %}...{% end %} 블럭, {% otherwise %}...{% end %} 블럭과 함께 다중 조건에 따른 표시를 수행 합니다. {% end %}로 블럭을 닫아야 합니다.{% for %}
{% end %} 사이의 블럭에 대한 루프 처리를 수행 합니다.{% def 매크로명(마라미터) %}
{% end %} 사이의 블럭을 매크로로 정의하고 호출시 매크로를 표시 합니다.{% include 파일명 %}
텍스트 템플릿을 포함 시킵니다. 파일명 부분에 표현식을 통해 동적으로 포함될 템플릿을 조정할 수 있습니다.{% with 표현식 %}
{% end %} 블럭 사이에 사용할 변수 값 설정{#... #}
주석문
■ Trac의 템플릿
Trac의 사용자 인터페이스중에 젠시(Genshi)를 거치지 않는 것이 없을 정도로 Genshi는 Trac을 위해서 만들기 시작한 것도 사실 입니다. 아래는 Trac에서 사용되고 있는 템플릿의 구조를 간단하게 나타낸 것입니다.
* trac * templates 기본 템플릿 about.html attachment.html attach_file_form.html author_or_creator.rss diff_div.html diff_options.html diff_view.html error.html history_view.html index.html layout.html list_of_attachments.html macros.html macros.rss page_index.html preview_file.html progress_bar.html progress_bar_grouped.html theme.html * admin templates 관리자 템플릿 admin.html admin_basics.html admin_components.html admin_enums.html admin_legacy.html admin_logging.html admin_milestones.html admin_perms.html admin_plugins.html admin_versions.html deploy_trac.cgi deploy_trac.fcgi deploy_trac.wsgi * prefs templates 설정 템플릿 prefs.html prefs_advanced.html prefs_datetime.html prefs_general.html prefs_keybindings.html prefs_language.html prefs_pygments.html prefs_userinterface.html * search templates 검색 템플릿 opensearch.xml search.html * ticket templates 티켓 템플릿 batch_modify.html batch_ticket_notify_email.txt milestone_delete.html milestone_edit.html milestone_view.html query.html query.rss query_results.html report.rss report_delete.html report_edit.html report_list.html report_list.rss report_view.html roadmap.html ticket.html ticket.rss ticket_box.html ticket_change.html ticket_notify_email.txt ticket_preview.html * timeline templates 프로젝트 기록 템플릿 timeline.html timeline.rss * versioncontrol templates 형상관리 템플릿 admin_repositories.html browser.html changeset.html diff_form.html dirlist_thead.html dir_entries.html path_links.html repository_index.html revisionlog.html revisionlog.rss revisionlog.txt sortable_th.html * wiki templates 기본 템플릿 wiki_delete.html wiki_diff.html wiki_edit.html wiki_edit_form.html wiki_page_path.html wiki_rename.html wiki_view.html
'IT 일반' 카테고리의 다른 글
이슈 트래커와 형상 관리 도구를 사용하기 위한 일반적 지식 (0) | 2018.12.29 |
---|---|
협업 개발 시스템 Trac 분석하기 (0) | 2018.12.27 |
협업 개발 시스템 Trac 수정을 위한 개발 환경 준비하기 (0) | 2018.12.27 |
CPU의 이해와 구성 - 정보처리 필기 해설 9 (0) | 2017.04.12 |
MySQL SQL 로깅하기 (0) | 2017.03.22 |