1+ #! /bin/bash
2+
3+ # —— 1. Locale guard —─────────────────────────────────────────────────────────
4+ # Enforcing a "C" locale guarantees reproducible sort order and decimal
5+ # separators. (Some CI images default to UTF-8 locales which may break awk.)
6+ export LC_ALL=C
7+ export LANG=C
8+
9+ # —— 2. CLI arguments with sensible fallbacks —───────────────────────────────
10+ INPUT_FILE=${1:- contracts/ gateway.fc} # Default contract path (for devs)
11+ OUTPUT_FILE=${2:- docs/ gateway.md} # Where to write the final docs
12+
13+ # Ensure the output directory exists so we don’t die on a missing path.
14+ mkdir -p " $( dirname " $OUTPUT_FILE " ) "
15+
16+ # —— 3. Work in a temp file so we never leave half-baked docs behind —────────
17+ TMP_FILE=$( mktemp) # Final markdown lives here until complete
18+ FILE_TMP=$( mktemp) # Intermediate file for awk output
19+
20+ # ##############################################################################
21+ # 4. Markdown preamble #
22+ # ##############################################################################
23+ {
24+ echo " # TON Gateway Docs"
25+ echo
26+ } > " $TMP_FILE "
27+
28+ # ##############################################################################
29+ # 5. AWK pass - the heavy lifter #
30+ # * Written to be POSIX-awk compatible (no /\s/, \+, gensub, etc.). #
31+ # * Inline comments are sprinkled liberally to clarify the logic. #
32+ # ##############################################################################
33+
34+ awk '
35+ ############################################################################
36+ # Utility helpers (trim) - POSIX-compliant implementations #
37+ ############################################################################
38+ function ltrim(s) { sub(/^[[:space:]]+/, "", s); return s }
39+ function rtrim(s) { sub(/[[:space:]]+$/, "", s); return s }
40+ function trim(s) { return rtrim(ltrim(s)) }
41+
42+ ############################################################################
43+ # Detect section-header comment lines we do *not* want to treat as doc text #
44+ ############################################################################
45+ function is_section_comment(line) {
46+ return line ~ /={3,}/ || line ~ /^(Sizes|GAS|OP|EXTERNAL|INTERNAL|PARSING|GETTERS|TL-B|AUTH)/
47+ }
48+
49+ ############################################################################
50+ # Extract function name from a FunC definition line #
51+ ############################################################################
52+ function extract_func_name(line, copy) {
53+ copy = line
54+ sub(/^[[:space:]]+/, "", copy) # Leading WS
55+ # If the line starts with "() func_name(" - strip the unit sigil.
56+ if (copy ~ /^\(\)[[:space:]]*[A-Za-z0-9_]+[[:space:]]*\(/)
57+ sub(/^\(\)[[:space:]]*/, "", copy)
58+ match(copy, /^([A-Za-z0-9_]+)[[:space:]]*\(/)
59+ return substr(copy, RSTART, RLENGTH - 1)
60+ }
61+
62+ ############################################################################
63+ # Identify lines that are *actual* function definitions (not if/while etc.) #
64+ ############################################################################
65+ function is_function_definition(line) {
66+ pat = "^[[:space:]]*(\\(\\))?[[:space:]]*[A-Za-z0-9_]+[[:space:]]*\\([^)]*\\)[[:space:]]*(impure)?[[:space:]]*(inline|inline_ref)?[[:space:]]*(method_id)?[[:space:]]*\\{";
67+ ctl = "^[[:space:]]*(if|while|repeat)[[:space:]]*\\(";
68+ return (line ~ pat) && !(line ~ ctl)
69+ }
70+
71+ ############################################################################
72+ # Public API filter - only expose handle_* + recv_* #
73+ ############################################################################
74+ function want_function(name) { return name ~ /^(handle_|recv_)/ }
75+
76+ ############################################################################
77+ # BEGIN block - initialise state #
78+ ############################################################################
79+ BEGIN {
80+ collecting_comment = 0 # Are we in a block of leading ";;" lines?
81+ comment = "" # Accumulated comment text
82+ const_header_printed = 0 # Printed the "## Constants" header yet?
83+ in_const_block = 0 # Are we in a block of constants?
84+ }
85+
86+ ############################################################################
87+ # 1) Capture consecutive ";; <text>" lines #
88+ ############################################################################
89+ /^[[:space:]]*;;/ {
90+ line = substr($0, index($0, ";;") + 2) # Strip the marker
91+ if (!is_section_comment(line)) {
92+ # Normalise capitalisation a tiny bit → "foo bar" → "Foo bar"
93+ text = trim(line)
94+ text = toupper(substr(text,1,1)) substr(text,2)
95+ comment = (comment ? comment "\n" : "") text
96+ collecting_comment = 1
97+ }
98+ next
99+ }
100+
101+ ############################################################################
102+ # 2) Function defs - emit md if we *just* captured a comment #
103+ ############################################################################
104+ {
105+ if (is_function_definition($0)) {
106+ if (in_const_block) {
107+ print ""
108+ in_const_block = 0
109+ }
110+ fname = extract_func_name($0)
111+ if (want_function(fname) && collecting_comment && comment) {
112+ print "### `" fname "`\n" # H3 header
113+ print "```func" # Code fence start
114+ print $0 # The definition line itself
115+ print "```\n" # Code fence end
116+ print comment "\n" # The doc string
117+ }
118+ collecting_comment = comment = "" # Reset for next round
119+ next
120+ }
121+ }
122+
123+ ############################################################################
124+ # 3) Constant extraction - opcode & error ids #
125+ ############################################################################
126+ /^[[:space:]]*const[[:space:]]+[A-Za-z0-9_:]+[[:space:]]*=/ {
127+ # Example line: const op::internal::deposit = 101;
128+
129+ # 3.1. Chop off the leading "const" keyword
130+ line = $0
131+ sub(/^[[:space:]]*const[[:space:]]+/, "", line)
132+
133+ # 3.2. Split on the first "=" to isolate LHS and RHS
134+ split(line, parts, "=")
135+ identifier = trim(parts[1])
136+ value_part = trim(parts[2])
137+
138+ # 3.3. Remove trailing semicolon and inline comments from RHS
139+ sub(/;.*/, "", value_part)
140+ value = trim(value_part)
141+
142+ # 3.4. Only emit op:: and error:: groups - the rest is internal noise
143+ if (identifier ~ /^op::/ || identifier ~ /^error::/) {
144+ if (!const_header_printed) {
145+ print "### Constants\n" # One-time section header
146+ const_header_printed = 1
147+ }
148+ print "- **" identifier "** = " value # • **op::blah** = 123
149+ in_const_block = 1
150+ }
151+ next
152+ }
153+
154+ ############################################################################
155+ # 4) Reset comment collection on any plain line #
156+ ############################################################################
157+ {
158+ collecting_comment = 0; comment = ""
159+ }
160+ ' " $INPUT_FILE " > " $FILE_TMP "
161+
162+ # ##############################################################################
163+ # 6. Append AWK output to the top-level markdown only if something was found #
164+ # ##############################################################################
165+ if [ -s " $FILE_TMP " ]; then
166+ {
167+ echo " ## $( basename " $INPUT_FILE " ) "
168+ echo
169+ cat " $FILE_TMP "
170+ } >> " $TMP_FILE "
171+ fi
172+
173+ rm " $FILE_TMP " # No longer needed
174+
175+ # ##############################################################################
176+ # 7. Strip CRLFs, trailing blank lines, and atomically move into place #
177+ # ##############################################################################
178+ tr -d ' \r' < " $TMP_FILE " | \
179+ sed -e :a -e ' /^\n*$/{$d;N;ba' -e ' }' > " $OUTPUT_FILE "
180+ rm " $TMP_FILE "
0 commit comments