so://phie

soso

My pretty neat website
git clone git://soophie.de/soso

feat: Added blob view and raw mode


commit: 54e48f4731d1ba89b0ac3191fd0995a150ac8ff2
parent: e3d82ae94ec73ac952aa6e03ea0b069a11dd9822
author: Sophie <info@soophie.de>
time: 2026-10-09 16:45

diff --git a/src/duc_macro.h b/src/duc_macro.h

index 5a0f426..491af84 100644

--- a/src/duc_macro.h

+++ b/src/duc_macro.h

1@@ -6,7 +6,9 @@

2

3 #define DUC_OUT ((orb_ctx_t *) out)

4 #define DUC_WRITE(str) { buf_write(DUC_OUT->out, str); }

5+#define DUC_CLEAR() { buf_free(DUC_OUT->out); }

6

7+#define BINARY(bytes, len) { buf_write_len(DUC_OUT->out, bytes, len); }

8 #define HTML(str) DUC_WRITE(str ? str : "")

9 #define STRING(str) { \

10 buf_t buf = BUF_NULL; \

11

12

diff --git a/src/main.c b/src/main.c

index cc34490..a603864 100644

--- a/src/main.c

+++ b/src/main.c

1@@ -66,7 +66,7 @@ TPEE_GET("/git/*", git_repo_page) {

2 if (!tab_mode) {

3 tab_mode = strdup("log");

4 }

5- const char *tab_mode_list[] = { "log", "tree", "refs", "commit", "blob" };

6+ const char *tab_mode_list[] = { "log", "tree", "refs", "commit", "blob", "raw" };

7 size_t tab_mode_list_len = sizeof(tab_mode_list) / sizeof(tab_mode_list[0]);

8 bool is_tab_mode_valid = false;

9 for (size_t i = 0; i < tab_mode_list_len; i++) {

10@@ -89,14 +89,19 @@ TPEE_GET("/git/*", git_repo_page) {

11 .repo_name = repo_name,

12 .repo_path = repo_path,

13 .tab_mode = tab_mode,

14- .ref = tpee_pairs_get(&query_list, "ref"),

15- .oid = tpee_pairs_get(&query_list, "oid"),

16+ .flag_ref_oid = tpee_pairs_get(&query_list, "ref"),

17+ .flag_oid = tpee_pairs_get(&query_list, "oid"),

18+ .flag_path = tpee_pairs_get(&query_list, "path"),

19 }));

20 orb_module_t page_module = orb_module_create(&PAGE_TYPE, &((page_param_list_t) {

21 .title = repo_name,

22 .module = &git_repo_module,

23 }));

24- orb_module_run(&page_module, &ctx);

25+ orb_module_t *module = &page_module;

26+ if (strcmp(tab_mode, "raw") == 0) {

27+ module = &git_repo_module;

28+ }

29+ orb_module_run(module, &ctx);

30 tpee_pairs_free(&query_list);

31 if (tab_mode) {

32 free(tab_mode);

33

34

diff --git a/src/module/git_repo.c b/src/module/git_repo.c

index 6db8d8b..e728bd8 100644

--- a/src/module/git_repo.c

+++ b/src/module/git_repo.c

1@@ -12,8 +12,9 @@ static orb_rc_t init(orb_module_t *self) {

2 .path = param_list->repo_path,

3 .meta = {0},

4 .handle = NULL,

5- .ref_oid = param_list->ref,

6- .oid = param_list->oid,

7+ .flag_ref_oid = param_list->flag_ref_oid,

8+ .flag_oid = param_list->flag_oid,

9+ .flag_path = param_list->flag_path,

10 .ref_head = NULL,

11 .ref = NULL,

12 .ref_list = NULL,

13@@ -142,9 +143,9 @@ static orb_rc_t fetch(orb_module_t *self) {

14 }

15 git_reference_iterator_free(iter);

16 }

17- if (data->repo.ref_head && data->repo.ref_oid && strcmp(data->repo.ref_head->oid, data->repo.ref_oid) != 0) {

18+ if (data->repo.ref_head && data->repo.flag_ref_oid && strcmp(data->repo.ref_head->oid, data->repo.flag_ref_oid) != 0) {

19 git_oid ref_oid;

20- if (git_oid_fromstr(&ref_oid, data->repo.ref_oid) == 0) {

21+ if (git_oid_fromstr(&ref_oid, data->repo.flag_ref_oid) == 0) {

22 git_reference_iterator *iter = NULL;

23 git_reference *ref_handle = NULL;

24 git_reference_iterator_new(&iter, data->repo.handle);

25@@ -153,7 +154,7 @@ static orb_rc_t fetch(orb_module_t *self) {

26 if (oid && git_oid_equal(oid, &ref_oid)) {

27 data->repo.ref = calloc(1, sizeof(git_repo_ref_t));

28 git_repo_ref_t *ref = data->repo.ref;

29- ref->oid = strdup(data->repo.ref_oid);

30+ ref->oid = strdup(data->repo.flag_ref_oid);

31 const char *ref_name = git_reference_name(ref_handle);

32 if (git_reference_is_branch(ref_handle)) {

33 ref->name = strdup(ref_name + strlen("refs/heads/"));

34

35

diff --git a/src/module/git_repo.h b/src/module/git_repo.h

index 99c9b27..a8b3eee 100644

--- a/src/module/git_repo.h

+++ b/src/module/git_repo.h

1@@ -8,8 +8,9 @@

2 typedef struct {

3 char *repo_name;

4 char *repo_path;

5- char *ref;

6- char *oid;

7+ char *flag_ref_oid;

8+ char *flag_oid;

9+ char *flag_path;

10 const char *tab_mode;

11 } git_repo_param_list_t;

12

13@@ -34,6 +35,7 @@ typedef struct {

14 char *name;

15 char *content;

16 size_t size;

17+ bool is_binary;

18 } git_repo_blob_t;

19

20 typedef struct {

21@@ -47,8 +49,9 @@ typedef struct {

22 char *path;

23 git_repo_meta_t meta;

24 git_repository *handle;

25- char *ref_oid;

26- char *oid;

27+ char *flag_ref_oid;

28+ char *flag_oid;

29+ char *flag_path;

30 git_repo_ref_t *ref_head;

31 git_repo_ref_t *ref;

32 git_repo_ref_t *ref_list;

33

34

diff --git a/src/module/git_repo_blob.c b/src/module/git_repo_blob.c

index d3be875..58a3acb 100644

--- a/src/module/git_repo_blob.c

+++ b/src/module/git_repo_blob.c

1@@ -7,25 +7,68 @@ static orb_rc_t init(orb_module_t *self) {

2 duc_git_repo_blob_t *data = self->data;

3 git_repo_blob_param_list_t *param_list = self->param_list;

4 if (param_list) {

5+ data->repo = param_list->repo;

6 data->is_markdown = param_list->is_markdown;

7+ data->is_raw = param_list->is_raw;

8 }

9 return ORB_OK;

10 }

11

12+static int _fetch_tree_cb(const char *root, const git_tree_entry *entry_handle, void *data) {

13+ git_repo_blob_tree_cb_t *handle = data;

14+ const char *name = git_tree_entry_name(entry_handle);

15+ git_filemode_t mode_handle = git_tree_entry_filemode(entry_handle);

16+ if (mode_handle != GIT_FILEMODE_TREE) {

17+ const git_oid *oid = git_tree_entry_id(entry_handle);

18+ size_t file_path_len = strlen(root) + strlen(name);

19+ char file_path[file_path_len + 1];

20+ snprintf(file_path, sizeof(file_path), "%s%s", root, name);

21+ if (strcmp(handle->file_path, file_path) == 0) {

22+ git_oid_cpy(handle->blob_oid, oid);

23+ return -1;

24+ }

25+ }

26+ return 0;

27+}

28+

29 static orb_rc_t fetch(orb_module_t *self) {

30 duc_git_repo_blob_t *data = self->data;

31- git_repo_blob_param_list_t *param_list = self->param_list;

32- if (!param_list) return ORB_FATAL(1);

33- if (!param_list->repo) return ORB_FATAL(2);

34- if (!param_list->repo->oid) return ORB_FATAL(3);

35- data->repo = param_list->repo;

36+ if (!data->repo) return ORB_FATAL(1);

37 git_oid blob_oid;

38- if (git_oid_fromstr(&blob_oid, data->repo->oid) != 0) return ORB_FATAL(4);

39+ if (data->repo->flag_oid) {

40+ if (git_oid_fromstr(&blob_oid, data->repo->flag_oid) != 0) return ORB_FATAL(2);

41+ }

42+ if (data->repo->flag_path) {

43+ char *ref_oid = NULL;

44+ if (data->repo->ref_head) ref_oid = data->repo->ref_head->oid;

45+ if (data->repo->ref) ref_oid = data->repo->ref->oid;

46+ if (!ref_oid) return ORB_ERR(2);

47+ const char *spec_suffix = "^{commit}";

48+ size_t spec_len = strlen(ref_oid) + strlen(spec_suffix) + 1;

49+ char spec[spec_len];

50+ snprintf(spec, spec_len, "%s%s", ref_oid, spec_suffix);

51+ git_object *obj = NULL;

52+ if (git_revparse_single(&obj, data->repo->handle, spec) == 0) {

53+ git_commit *commit_handle = (git_commit *) obj;

54+ git_tree *tree_handle = NULL;

55+ if (git_commit_tree(&tree_handle, commit_handle) == 0) {

56+ git_tree_walk(tree_handle, GIT_TREEWALK_PRE, _fetch_tree_cb, &((git_repo_blob_tree_cb_t) {

57+ .blob_oid = &blob_oid,

58+ .file_path = data->repo->flag_path,

59+ }));

60+ git_tree_free(tree_handle);

61+ }

62+ git_object_free(obj);

63+ }

64+ if (git_oid_is_zero(&blob_oid)) return ORB_FATAL(3);

65+ }

66 git_blob *blob_handle = NULL;

67- if (git_blob_lookup(&blob_handle, data->repo->handle, &blob_oid) != 0) return ORB_FATAL(5);

68+ if (git_blob_lookup(&blob_handle, data->repo->handle, &blob_oid) != 0) return ORB_FATAL(4);

69 data->blob = calloc(1, sizeof(git_repo_blob_t));

70 git_repo_blob_t *blob = data->blob;

71- blob->oid = strdup(data->repo->oid);

72+ char oid_str[GIT_OID_HEXSZ + 1];

73+ git_oid_tostr(oid_str, sizeof(oid_str), &blob_oid);

74+ blob->oid = strdup(oid_str);

75 blob->name = NULL;

76 size_t size = (size_t) git_blob_rawsize(blob_handle);

77 const char *content = git_blob_rawcontent(blob_handle);

78@@ -35,6 +78,7 @@ static orb_rc_t fetch(orb_module_t *self) {

79 blob->content[size] = '\0';

80 blob->size = size;

81 }

82+ blob->is_binary = git_blob_is_binary(blob_handle);

83 git_blob_free(blob_handle);

84 return ORB_OK;

85 }

86

87

diff --git a/src/module/git_repo_blob.h b/src/module/git_repo_blob.h

index 023d81b..054080d 100644

--- a/src/module/git_repo_blob.h

+++ b/src/module/git_repo_blob.h

1@@ -6,6 +6,12 @@

2 typedef struct {

3 git_repo_t *repo;

4 bool is_markdown;

5+ bool is_raw;

6 } git_repo_blob_param_list_t;

7

8+typedef struct {

9+ git_oid *blob_oid;

10+ const char *file_path;

11+} git_repo_blob_tree_cb_t;

12+

13 extern const orb_module_type_t GIT_REPO_BLOB_TYPE;

14

15

diff --git a/src/module/git_repo_commit.c b/src/module/git_repo_commit.c

index fd85e7b..0829870 100644

--- a/src/module/git_repo_commit.c

+++ b/src/module/git_repo_commit.c

1@@ -10,16 +10,16 @@ static orb_rc_t fetch(orb_module_t *self) {

2 git_repo_commit_param_list_t *param_list = self->param_list;

3 if (!param_list) return ORB_FATAL(1);

4 if (!param_list->repo) return ORB_FATAL(2);

5- if (!param_list->repo->oid) return ORB_FATAL(3);

6+ if (!param_list->repo->flag_oid) return ORB_FATAL(3);

7 data->repo = param_list->repo;

8 git_oid commit_oid;

9- if (git_oid_fromstr(&commit_oid, data->repo->oid) != 0) return ORB_FATAL(4);

10+ if (git_oid_fromstr(&commit_oid, data->repo->flag_oid) != 0) return ORB_FATAL(4);

11 git_commit *commit_handle = NULL;

12 git_commit *parent_commit_handle = NULL;

13 git_commit_lookup(&commit_handle, data->repo->handle, &commit_oid);

14 data->commit = calloc(1, sizeof(git_repo_commit_t));

15 git_repo_commit_t *commit = data->commit;

16- commit->oid = strdup(data->repo->oid);

17+ commit->oid = strdup(data->repo->flag_oid);

18 commit->message = strdup(git_commit_message(commit_handle));

19 commit->summary = strdup(git_commit_summary(commit_handle));

20 const char *body = git_commit_body(commit_handle);

21

22

diff --git a/src/module/git_repo_tree.c b/src/module/git_repo_tree.c

index 2ec8d50..f20159b 100644

--- a/src/module/git_repo_tree.c

+++ b/src/module/git_repo_tree.c

1@@ -4,6 +4,15 @@

2

3 #include "../templ/duc_git_repo_tree.h"

4

5+static orb_rc_t init(orb_module_t *self) {

6+ duc_git_repo_tree_t *data = self->data;

7+ git_repo_tree_param_list_t *param_list = self->param_list;

8+ if (param_list) {

9+ data->repo = param_list->repo;

10+ }

11+ return ORB_OK;

12+}

13+

14 static int _fetch_tree_cb(const char *root, const git_tree_entry *entry_handle, void *data) {

15 git_repo_tree_cb_t *handle = data;

16 const char *name = git_tree_entry_name(entry_handle);

17@@ -36,30 +45,22 @@ static int _fetch_tree_cb(const char *root, const git_tree_entry *entry_handle,

18

19 static orb_rc_t fetch(orb_module_t *self) {

20 duc_git_repo_tree_t *data = self->data;

21- git_repo_tree_param_list_t *param_list = self->param_list;

22- if (!param_list) return ORB_ERR(1);

23- if (!param_list->repo) return ORB_ERR(2);

24- data->repo = param_list->repo;

25- git_object *obj = NULL;

26- git_commit *commit_handle = NULL;

27+ if (!data->repo) return ORB_ERR(1);

28 char *ref_oid = NULL;

29- if (param_list->repo->ref_head) {

30- ref_oid = param_list->repo->ref_head->oid;

31- }

32- if (param_list->repo->ref) {

33- ref_oid = param_list->repo->ref->oid;

34- }

35- if (!ref_oid) return ORB_ERR(3);

36+ if (data->repo->ref_head) ref_oid = data->repo->ref_head->oid;

37+ if (data->repo->ref) ref_oid = data->repo->ref->oid;

38+ if (!ref_oid) return ORB_ERR(2);

39 const char *spec_suffix = "^{commit}";

40 size_t spec_len = strlen(ref_oid) + strlen(spec_suffix) + 1;

41 char spec[spec_len];

42 snprintf(spec, spec_len, "%s%s", ref_oid, spec_suffix);

43- if (git_revparse_single(&obj, param_list->repo->handle, spec) == 0) {

44- commit_handle = (git_commit *) obj;

45+ git_object *obj = NULL;

46+ if (git_revparse_single(&obj, data->repo->handle, spec) == 0) {

47+ git_commit *commit_handle = (git_commit *) obj;

48 git_tree *tree_handle = NULL;

49 if (git_commit_tree(&tree_handle, commit_handle) == 0) {

50 git_tree_walk(tree_handle, GIT_TREEWALK_PRE, _fetch_tree_cb, &((git_repo_tree_cb_t) {

51- .repo_handle = param_list->repo->handle,

52+ .repo_handle = data->repo->handle,

53 .entry_list = &data->entry_list,

54 .entry_list_len = &data->entry_list_len,

55 }));

56@@ -91,6 +92,7 @@ static void cleanup(orb_module_t *self) {

57

58 const orb_module_type_t GIT_REPO_TREE_TYPE = {

59 .size = sizeof(duc_git_repo_tree_t),

60+ .init = init,

61 .fetch = fetch,

62 .render = render,

63 .cleanup = cleanup,

64

65

diff --git a/templ/git_repo.html b/templ/git_repo.html

index b3fad4c..63524fb 100644

--- a/templ/git_repo.html

+++ b/templ/git_repo.html

1@@ -11,17 +11,23 @@

2 git_repo_t repo;

3 const char *tab_mode;

4 ) %

5-

6+% if (ctx->tab_mode && strcmp(ctx->tab_mode, "raw") == 0) {

7+ ORB_MODULE_DEF(GIT_REPO_BLOB_TYPE, &((git_repo_blob_param_list_t) {

8+ .repo = &ctx->repo,

9+ .is_raw = true,

10+ }))

11+ return;

12+} %

13 <div class="git--repo">

14 <h2>% STRING(ctx->repo.meta.name) %</h2>

15 <p>

16- % if (ctx->repo.meta.description) { % <span>% STRING(ctx->repo.meta.description) %</span><br/> % } %

17- git clone <a href="git://soophie.de/% STRING(ctx->repo.meta.name) %">git://soophie.de/% STRING(ctx->repo.meta.name) %</a>

18+ % if (ctx->repo.meta.description) { % <span class="repo--description">% STRING(ctx->repo.meta.description) %</span><br/> % } %

19+ <span class="repo--clone">git clone <a href="git://soophie.de/% STRING(ctx->repo.meta.name) %">git://soophie.de/% STRING(ctx->repo.meta.name) %</a></span>

20 </p>

21 <ul class="link-list">

22 <li class="link % if (strcmp(ctx->tab_mode, "log") == 0 || strcmp(ctx->tab_mode, "commit") == 0) { %link--active% } %"><a href="/git/% STRING(ctx->repo.meta.name) if (ctx->repo.ref) { %?ref=% STRING(ctx->repo.ref->oid) } %">log</a></li>

23- % bool is_readme = ctx->repo.oid && ctx->repo.readme_oid && strcmp(ctx->repo.oid, ctx->repo.readme_oid) == 0; %

24- % bool is_license = ctx->repo.oid && ctx->repo.license_oid && strcmp(ctx->repo.oid, ctx->repo.license_oid) == 0; %

25+ % bool is_readme = ctx->repo.flag_oid && ctx->repo.readme_oid && strcmp(ctx->repo.flag_oid, ctx->repo.readme_oid) == 0; %

26+ % bool is_license = ctx->repo.flag_oid && ctx->repo.license_oid && strcmp(ctx->repo.flag_oid, ctx->repo.license_oid) == 0; %

27 <li class="link % if (strcmp(ctx->tab_mode, "tree") == 0 || (strcmp(ctx->tab_mode, "blob") == 0 && !is_readme && !is_license)) { %link--active% } %"><a href="/git/% STRING(ctx->repo.meta.name) %/tree% if (ctx->repo.ref) { %?ref=% STRING(ctx->repo.ref->oid) } %">tree</a></li>

28 <li class="link % if (strcmp(ctx->tab_mode, "refs") == 0) { %link--active% } %"><a href="/git/% STRING(ctx->repo.meta.name) %/refs% if (ctx->repo.ref) { %?ref=% STRING(ctx->repo.ref->oid) } %">refs</a></li>

29 % if (ctx->repo.readme_oid) { %

30@@ -41,9 +47,9 @@

31 if (strcmp(ctx->tab_mode, "refs") == 0) tab = "refs";

32 }

33 bool is_branch_select = true;

34- if (ctx->repo.oid) {

35- if (ctx->repo.readme_oid && strcmp(ctx->repo.oid, ctx->repo.readme_oid) == 0) is_branch_select = false;

36- if (ctx->repo.license_oid && strcmp(ctx->repo.oid, ctx->repo.license_oid) == 0) is_branch_select = false;

37+ if (ctx->repo.flag_oid) {

38+ if (ctx->repo.readme_oid && strcmp(ctx->repo.flag_oid, ctx->repo.readme_oid) == 0) is_branch_select = false;

39+ if (ctx->repo.license_oid && strcmp(ctx->repo.flag_oid, ctx->repo.license_oid) == 0) is_branch_select = false;

40 }

41 %

42 % if (is_branch_select) { %

43@@ -83,8 +89,8 @@

44 } %

45 % if (ctx->tab_mode && strcmp(ctx->tab_mode, "blob") == 0) {

46 bool is_markdown = false;

47- if (ctx->repo.oid) {

48- if (ctx->repo.readme_oid && strcmp(ctx->repo.oid, ctx->repo.readme_oid) == 0) is_markdown = true;

49+ if (ctx->repo.flag_oid) {

50+ if (ctx->repo.readme_oid && strcmp(ctx->repo.flag_oid, ctx->repo.readme_oid) == 0) is_markdown = true;

51 }

52 ORB_MODULE_DEF(GIT_REPO_BLOB_TYPE, &((git_repo_blob_param_list_t) {

53 .repo = &ctx->repo,

54

55

diff --git a/templ/git_repo_blob.html b/templ/git_repo_blob.html

index 2a0c059..3f0b9ad 100644

--- a/templ/git_repo_blob.html

+++ b/templ/git_repo_blob.html

1@@ -1,22 +1,84 @@

2 % #include "src/duc_macro.h" %

3 % #include "src/module/git_repo.h" %

4 % #include "src/module/git_repo_blob.h" %

5+% #include <math.h> %

6

7 % DUC_TEMPLDEF(

8 duc_git_repo_blob,

9 git_repo_t *repo;

10 git_repo_blob_t *blob;

11 bool is_markdown;

12+ bool is_raw;

13 ) %

14

15 % if (ctx->is_markdown) { %

16 <div class="blob--markdown">% MARKDOWN(ctx->blob->content) %</div>

17-% } else { %

18- <p>

19- <span>blob: % STRING(ctx->blob->oid) %</span><br/>

20- <span>size: % NUMBER(ctx->blob->size) %</span>

21- </p>

22- <pre class="blob--code">% STRING(ctx->blob->content) %</pre>

23+% } else if (ctx->is_raw) {

24+ DUC_CLEAR()

25+ BINARY(ctx->blob->content, ctx->blob->size)

26+ return;

27+} else { %

28+ <div class="blob">

29+ <pre class="blob--meta"><span>blob: % STRING(ctx->blob->oid) % (% NUMBER(ctx->blob->size) % bytes)</span><a class="link--raw" href="/git/% STRING(ctx->repo->meta.name) %/raw?oid=% STRING(ctx->repo->flag_oid) %">[raw]</a></pre>

30+ % if (ctx->blob->is_binary) { %

31+ <pre class="blob--code"><span class="blob--binary">Binary file: <a href="/git/% STRING(ctx->repo->meta.name) %/raw?oid=% STRING(ctx->repo->flag_oid) %">[view raw]</p></span></pre>

32+ % } else { %

33+ <pre class="blob--code">%

34+

35+ size_t len = strlen(ctx->blob->content);

36+ size_t i = 0;

37+ size_t ln = 0;

38+ size_t sol = 0;

39+ while (i < len) {

40+ char c = ctx->blob->content[i];

41+ if (c == '\n') {

42+ ln++;

43+ }

44+ i++;

45+ }

46+ size_t pad = floor(log10(ln + 1)) + 1;

47+ i = 0;

48+ ln = 0;

49+ sol = 0;

50+ size_t df = 0;

51+ bool is_meta = false;

52+ while (i < len) {

53+ char c = ctx->blob->content[i];

54+ if (c == '\n') {

55+ size_t ln_len = i - sol;

56+ char line[ln_len + 1];

57+ memcpy(line, ctx->blob->content + sol, ln_len);

58+ line[ln_len] = '\0';

59+ sol = i + 1;

60+ bool is_diff = strncmp(line, "diff --git", 10) == 0;

61+ bool is_hunk = strncmp(line, "@@ ", 3) == 0;

62+ if (is_diff) {

63+ is_meta = true;

64+ }

65+ if (is_diff && ln != 0) {

66+ df++;

67+ %</pre>%

68+ %<pre class="diff--meta">%

69+ }

70+ if (is_hunk && is_meta) {

71+ is_meta = false;

72+ ln = 0;

73+ %</pre>%

74+ %<pre class="diff--code">%

75+ }

76+ if (is_meta) {

77+ %<p>%STRING(line)%</p>%

78+ }

79+ else {

80+ %<p id="% NUMBER(ln + 1) %" class="% if (is_hunk) { %is-hunk% } % % if (strncmp(line, "+", 1) == 0 && strncmp(line, "+++", 3) != 0) { %is-add% } % % if (strncmp(line, "-", 1) == 0 && strncmp(line, "---", 3) != 0) { %is-rem% } %"><span class="ln"><a href="#% NUMBER(ln + 1) %">% PRINTF("%%*ld", pad, ln + 1) %</a></span><span class="line"><code>% STRING(line) %</code></span></p>%

81+ }

82+ ln++;

83+ }

84+ i++;

85+ }

86+ %</pre>

87+ % } %

88+ </div>

89 % } %

90

91 % DUC_ENDTEMPL %

92

93