0&&(a+=n),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"===l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("===l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),r=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;const t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e,n){const t=e.input[e.index-1],r=e.input[e.index+e[0].length];"."!==t&&"."!==r||n.ignoreMatch()}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return function t(s,o){const l=s;if(s.compiled)return l;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords;let c=null;if("object"==typeof s.keywords&&(c=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,w(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemes&&c)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l.keywordPatternRe=n(s.lexemes||c||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__beforeBegin=i),s.begin||(s.begin=/\B|\b/),l.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(l.endRe=n(s.end)),l.terminator_end=d(s.end)||"",s.endsWithParent&&o.terminator_end&&(l.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(l.illegalRe=n(s.illegal)),void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){t(e,l)})),s.starts&&t(s.starts,o),l.matcher=function(e){const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(l),l}(e)}(E),N="",y=s||_,k={},O=new f.__emitter(f);!function(){for(var e=[],n=y;n!==E;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>O.openNode(e))}();var A="",I=0,S=0,B=0,L=!1;try{for(y.matcher.considerAll();;){B++,L?L=!1:(y.matcher.lastIndex=S,y.matcher.considerAll());const e=y.matcher.exec(o);if(!e)break;const n=x(o.substring(S,e.index),e);S=e.index+n}return x(o.substr(S)),O.closeAllNodes(),O.finalize(),N=O.toHTML(),{relevance:I,value:N,language:e,illegal:!1,emitter:O,top:y}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:o.slice(S-100,S+100),mode:n.mode},sofar:N,relevance:0,value:R(o),emitter:O};if(l)return{illegal:!1,relevance:0,value:R(o),emitter:O,language:e,top:y,errorRaised:n};throw n}}function v(e,n){n=n||f.languages||Object.keys(i);var t=function(e){const n={relevance:0,emitter:new f.__emitter(f),value:R(e),illegal:!1,top:h};return n.emitter.addText(e),n}(e),r=t;return n.filter(T).filter(I).forEach((function(n){var a=m(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function x(e){return f.tabReplace||f.useBR?e.replace(c,e=>"\n"===e?f.useBR?"
":e:f.tabReplace?e.replace(/\t/g,f.tabReplace):e):e}function E(e){let n=null;const t=function(e){var n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=f.languageDetectRe.exec(n);if(t){var r=T(t[1]);return r||(console.warn(g.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?t[1]:"no-highlight"}return n.split(/\s+/).find(e=>p(e)||T(e))}(e);if(p(t))return;S("before:highlightBlock",{block:e,language:t}),f.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(/
/g,"\n"):n=e;const r=n.textContent,a=t?b(t,r,!0):v(r),i=k(n);if(i.length){const e=document.createElement("div");e.innerHTML=a.value,a.value=O(i,k(e),r)}a.value=x(a.value),S("after:highlightBlock",{block:e,result:a}),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?s[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,t,a.language),e.result={language:a.language,re:a.relevance,relavance:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance,relavance:a.second_best.relevance})}const N=()=>{if(!N.called){N.called=!0;var e=document.querySelectorAll("pre code");a.forEach.call(e,E)}};function T(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}function A(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach(e=>{s[e]=n})}function I(e){var n=T(e);return n&&!n.disableAutodetect}function S(e,n){var t=e;o.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:b,highlightAuto:v,fixMarkup:x,highlightBlock:E,configure:function(e){f=y(f,e)},initHighlighting:N,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",N,!1)},registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!l)throw n;console.error(n),r=h}r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&A(r.aliases,{languageName:e})},listLanguages:function(){return Object.keys(i)},getLanguage:T,registerAliases:A,requireLanguage:function(e){var n=T(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:I,inherit:y,addPlugin:function(e){o.push(e)}}),t.debugMode=function(){l=!1},t.safeMode=function(){l=!0},t.versionString="10.1.1";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(t,_),t}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);
+hljs.registerLanguage("apache",function(){"use strict";return function(e){var n={className:"number",begin:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?"};return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0,contains:[e.HASH_COMMENT_MODE,{className:"section",begin:"?",end:">",contains:[n,{className:"number",begin:":\\d{1,5}"},e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute",begin:/\w+/,relevance:0,keywords:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"},contains:[{className:"meta",begin:"\\s\\[",end:"\\]$"},{className:"variable",begin:"[\\$%]\\{",end:"\\}",contains:["self",{className:"number",begin:"[\\$%]\\d+"}]},n,{className:"number",begin:"\\d+"},e.QUOTE_STRING_MODE]}}],illegal:/\S/}}}());
+hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const t={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,t]};t.contains.push(n);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},i=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b-?[a-z\._]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[i,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,n,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}());
+hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},i={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},o={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},c=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+c,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:c,returnBegin:!0,contains:[o],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"",contains:[].concat(_,u,d,[s,{begin:"\\b(deque|list|queue|priority_queue|pair|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",end:">",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin:/,end:/>/,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:s,strings:a,keywords:l}}}}());
+hljs.registerLanguage("c",function(){"use strict";return function(e){var n=e.getLanguage("c-like").rawDefinition();return n.name="C",n.aliases=["c","h"],n}}());
+hljs.registerLanguage("coffeescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((e=>n=>!e.includes(n))(["var","const","let","function","static"])).join(" "),literal:n.concat(["yes","no","on","off"]).join(" "),built_in:a.concat(["npm","print"]).join(" ")},i="[A-Za-z$_][0-9A-Za-z$_]*",s={className:"subst",begin:/#\{/,end:/}/,keywords:t},o=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,s]},{begin:/"/,end:/"/,contains:[r.BACKSLASH_ESCAPE,s]}]},{className:"regexp",variants:[{begin:"///",end:"///",contains:[s,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)",relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+i},{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{begin:"```",end:"```"},{begin:"`",end:"`"}]}];s.contains=o;var c=r.inherit(r.TITLE_MODE,{begin:i}),l={className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,keywords:t,contains:["self"].concat(o)}]};return{name:"CoffeeScript",aliases:["coffee","cson","iced"],keywords:t,illegal:/\/\*/,contains:o.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{className:"function",begin:"^\\s*"+i+"\\s*=\\s*(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[c,l]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function",begin:"(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[l]}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[c]},c]},{begin:i+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}());
+hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}());
+hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E={begin:"<",end:">",contains:[{beginKeywords:"in out"},i]},_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:"?",end:">"}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}}());
+hljs.registerLanguage("css",function(){"use strict";return function(e){var n={begin:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]};return{name:"CSS",case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",illegal:/:/,returnBegin:!0,contains:[{className:"keyword",begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,n]}]}}}());
+hljs.registerLanguage("diff",function(){"use strict";return function(e){return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{begin:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{begin:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{className:"comment",variants:[{begin:/Index: /,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^\-{3}/,end:/$/},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/}]},{className:"addition",begin:"^\\+",end:"$"},{className:"deletion",begin:"^\\-",end:"$"},{className:"addition",begin:"^\\!",end:"$"}]}}}());
+// Add Erlang language support
+hljs.registerLanguage("erlang",function(){"use strict";return function(e){var n="[a-z'][a-zA-Z0-9_']*",r="("+n+":"+n+"|"+n+")",a={keyword:"after and andalso|10 band begin bnot bor bsl bzr bxor case catch cond div end fun if let not of orelse|10 query receive rem try when xor",literal:"false true"},i=e.COMMENT("%","$"),c={className:"number",begin:"\\b(\\d+(_\\d+)*#[a-fA-F0-9]+(_[a-fA-F0-9]+)*|\\d+(_\\d+)*(\\.\\d+(_\\d+)*)?([eE][-+]?\\d+)?)",relevance:0},s={begin:"fun\\s+"+n+"/\\d+"},t={begin:r+"\\(",end:"\\)",returnBegin:!0,relevance:0,contains:[{begin:r,relevance:0},{begin:"\\(",end:"\\)",endsWithParent:!0,returnEnd:!0,relevance:0}]},d={begin:"{",end:"}",relevance:0},o={begin:"\\b_([A-Z][A-Za-z0-9_]*)?",relevance:0},l={begin:"[A-Z][a-zA-Z0-9_]*",relevance:0},b={begin:"#"+e.UNDERSCORE_IDENT_RE,relevance:0,returnBegin:!0,contains:[{begin:"#"+e.UNDERSCORE_IDENT_RE,relevance:0},{begin:"{",end:"}",relevance:0}]},g={beginKeywords:"fun receive if try case",end:"end",keywords:a};g.contains=[i,s,e.inherit(e.APOS_STRING_MODE,{className:""}),g,t,e.QUOTE_STRING_MODE,c,d,o,l,b];var u=[i,s,g,t,e.QUOTE_STRING_MODE,c,d,o,l,b];t.contains[1].contains=u,d.contains=u,b.contains[1].contains=u;var E={className:"params",begin:"\\(",end:"\\)",contains:u};return{name:"Erlang",aliases:["erl"],keywords:a,illegal:"(|\\*=|\\+=|-=|/\\*|\\*/|\\(\\*|\\*\\))",contains:[{className:"function",begin:"^"+n+"\\s*\\(",end:"->",returnBegin:!0,illegal:"\\(|#|//|/\\*|\\\\|:|;",contains:[E,e.inherit(e.TITLE_MODE,{begin:n})],starts:{end:";|\\.",keywords:a,contains:u}},i,{begin:"^-",end:"\\.",relevance:0,excludeEnd:!0,returnBegin:!0,keywords:{$pattern:"-"+e.IDENT_RE,keyword:"-module -record -undef -export -ifdef -ifndef -author -copyright -doc -vsn -import -include -include_lib -compile -define -else -endif -file -behaviour -behavior -spec"},contains:[E]},c,e.QUOTE_STRING_MODE,b,o,l,d,{begin:/\.$/}]}}}());
\ No newline at end of file
diff --git a/docs/build-all.sh b/docs/build-docs.sh
similarity index 100%
rename from docs/build-all.sh
rename to docs/build-docs.sh
diff --git a/docs/deploy-dry-run.sh b/docs/deploy-dry-run.sh
new file mode 100755
index 000000000..32f3024a0
--- /dev/null
+++ b/docs/deploy-dry-run.sh
@@ -0,0 +1,94 @@
+#!/bin/bash
+
+# Deployment Dry Run Script
+# Simulates the permaweb deployment without actually deploying
+
+# Colors for output
+GREEN='\033[0;32m'
+YELLOW='\033[0;33m'
+RED='\033[0;31m'
+NC='\033[0m' # No Color
+
+# Default values (can be overridden by environment variables)
+ARNS_NAME="${ARNS_NAME:-hyperbeam}"
+UNDERNAME="${UNDERNAME:-book}"
+DEPLOY_FOLDER="${DEPLOY_FOLDER:-docs/book/dist}"
+
+echo -e "${YELLOW}============================================${NC}"
+echo -e "${YELLOW} DEPLOYMENT DRY RUN SIMULATION${NC}"
+echo -e "${YELLOW}============================================${NC}"
+echo ""
+
+# Simulate the deployment command that would be run
+echo -e "${GREEN}Would execute deployment command:${NC}"
+echo "npx permaweb-deploy \\"
+echo " --arns-name=${ARNS_NAME} \\"
+echo " --undername=${UNDERNAME} \\"
+echo " --ant-process=\${ANT_PROCESS} \\"
+echo " --deploy-folder=${DEPLOY_FOLDER}"
+echo ""
+
+# Check if the deploy folder exists
+if [ -d "$DEPLOY_FOLDER" ]; then
+ echo -e "${GREEN}â Deploy folder exists: ${DEPLOY_FOLDER}${NC}"
+
+ # Count files in deploy folder
+ FILE_COUNT=$(find "$DEPLOY_FOLDER" -type f | wc -l)
+ FOLDER_SIZE=$(du -sh "$DEPLOY_FOLDER" | cut -f1)
+ echo -e "${GREEN} - Contains ${FILE_COUNT} files${NC}"
+ echo -e "${GREEN} - Total size: ${FOLDER_SIZE}${NC}"
+
+ # List some key files
+ echo ""
+ echo -e "${GREEN}Key files to be deployed:${NC}"
+ if [ -f "$DEPLOY_FOLDER/index.html" ]; then
+ echo -e "${GREEN} â index.html${NC}"
+ else
+ echo -e "${RED} â index.html (missing - deployment would fail)${NC}"
+ fi
+
+ # Check for CSS and JS files
+ CSS_COUNT=$(find "$DEPLOY_FOLDER" -name "*.css" | wc -l)
+ JS_COUNT=$(find "$DEPLOY_FOLDER" -name "*.js" | wc -l)
+ HTML_COUNT=$(find "$DEPLOY_FOLDER" -name "*.html" | wc -l)
+
+ echo -e "${GREEN} - ${CSS_COUNT} CSS files${NC}"
+ echo -e "${GREEN} - ${JS_COUNT} JavaScript files${NC}"
+ echo -e "${GREEN} - ${HTML_COUNT} HTML files${NC}"
+
+else
+ echo -e "${RED}â Deploy folder does not exist: ${DEPLOY_FOLDER}${NC}"
+ echo -e "${RED} Deployment would fail!${NC}"
+ echo ""
+ echo -e "${YELLOW}To fix this, run:${NC}"
+ echo " cd docs/book && mdbook build"
+ exit 1
+fi
+
+echo ""
+echo -e "${GREEN}Environment Variables Check:${NC}"
+if [ -n "$DEPLOY_KEY" ]; then
+ echo -e "${GREEN} â DEPLOY_KEY is set${NC}"
+else
+ echo -e "${YELLOW} ! DEPLOY_KEY not set (would use default)${NC}"
+fi
+
+if [ -n "$ANT_PROCESS" ]; then
+ echo -e "${GREEN} â ANT_PROCESS is set${NC}"
+else
+ echo -e "${YELLOW} ! ANT_PROCESS not set (required for deployment)${NC}"
+fi
+
+echo ""
+echo -e "${GREEN}Deployment Target:${NC}"
+echo -e "${GREEN} - ArNS Name: ${ARNS_NAME}${NC}"
+echo -e "${GREEN} - Undername: ${UNDERNAME}${NC}"
+echo -e "${GREEN} - Full URL: https://${UNDERNAME}_${ARNS_NAME}.arweave.net${NC}"
+
+echo ""
+echo -e "${YELLOW}============================================${NC}"
+echo -e "${YELLOW} DRY RUN COMPLETE${NC}"
+echo -e "${YELLOW}============================================${NC}"
+echo ""
+echo -e "${GREEN}â Deployment validation successful!${NC}"
+echo -e "${YELLOW}To perform actual deployment, run without --dry-run flag${NC}"
\ No newline at end of file
diff --git a/docs/erlang-literate-parser.js b/docs/erlang-literate-parser.js
new file mode 100755
index 000000000..477fecb0f
--- /dev/null
+++ b/docs/erlang-literate-parser.js
@@ -0,0 +1,1633 @@
+#!/usr/bin/env node
+
+/**
+ * Comprehensive Erlang Literate Documentation Generator
+ *
+ * Handles all comment types:
+ * - %%% Module documentation
+ * - %% Function/section documentation
+ * - % Inline comments (converted to prose)
+ * - @doc, @param, @returns annotations
+ */
+
+import fs from 'fs';
+import path from 'path';
+
+// Character codes for faster comparisons
+const CHAR_CODES = {
+ PERCENT: 37, // '%'
+ DASH: 45, // '-'
+ OPEN_PAREN: 40, // '('
+ CLOSE_PAREN: 41, // ')'
+ OPEN_BRACE: 123, // '{'
+ CLOSE_BRACE: 125, // '}'
+ OPEN_BRACKET: 91, // '['
+ CLOSE_BRACKET: 93, // ']'
+ DOT: 46 // '.'
+};
+
+// String constants to avoid repeated allocations
+const STRINGS = {
+ EMPTY: '',
+ SPACE: ' ',
+ NEWLINE: '\n',
+ TRIPLE_PERCENT: '%%%',
+ DOUBLE_PERCENT: '%%',
+ SINGLE_PERCENT: '%',
+ SPEC_PREFIX: '-spec ',
+ ERLANG: 'erlang',
+ BACKTICK: '`',
+ PARAM_TAG: '@param',
+ RETURNS_TAG: '@returns',
+ PARAMETERS_HEADER: '### Parameters',
+ RETURNS_HEADER: '### Returns',
+ EXPORTED_FUNCTIONS: '## Exported Functions',
+ SEPARATOR: '---'
+};
+
+// Precompiled regex patterns for performance
+const REGEX = {
+ MODULE: /^-module\(([^)]+)\)/,
+ EXPORT: /^-export\(\[([^\]]+)\]\)/,
+ EXPORT_PREFIX: /^-export\(\[/,
+ INCLUDE: /^-include(?:_lib)?\(["']([^"']+)["']\)/,
+ DEFINE: /^-define\(([^,)]+)(?:,\s*(.*))?\)/,
+ BEHAVIOUR: /^-behaviour\(([^)]+)\)/,
+ RECORD: /^-record\(([^,)]+),\s*\{/,
+ TYPE: /^-type\s+([a-z][a-z0-9_]*)\(/,
+ SPEC: /^-spec\s+([a-z][a-z0-9_]*)\s*\(/,
+ FUNCTION: /^([a-z][a-z0-9_]*)\s*\(/,
+ ATTRIBUTE: /^-([a-z][a-z0-9_]*)\(/,
+ DOC_BLOCK_START: /^%% @doc/,
+ COMMENT_SINGLE: /^\s*%[^%]/,
+ COMMENT_DOUBLE: /^\s*%%[^%]/,
+ BACKTICK_QUOTE: /`([^']*?)'/g,
+ HTML_ENTITIES_LT: /<</g,
+ HTML_ENTITIES_GT: />>/g,
+ PRE_TAG: /([\s\S]*?)<\/pre>/g,
+ // Match numbered list items that start with either "1." or "1:" (allow optional space before the punctuation)
+ NUMBERED_LIST: /^\d+\s*[.:]\s/,
+ BULLET_LIST: /^[-*]\s/,
+ HEADING: /^#{1,6}\s/,
+ CODE_FENCE: /^```/,
+ RETURNS_TOKENS: /(\{[^}]+\}|\bok\b|\berror\b|\bnot_found\b|\btrue\b|\bfalse\b)/gi,
+ LEADING_RETURN_TOKEN: /^(\s*)(\{[^}]+\}|\[[^\]]+\]|ok|error|not_found|true|false)(\b|\s|$)/i,
+ STANDALONE_TUPLE: /(^|[^`])(\{[^}]+\})([^`]|$)/g,
+ PARAM: /^@param\s+(\S+)\s*(.*)/,
+ RETURNS: /^@returns?\s*/,
+ OPTION_DEF: /^(`[^`]+`)\s*:\s*(.*)$/,
+ DEFINITION: /^(\S+(?:\s*:\s*\S+)?)\s*:\s*(.*)$/,
+ MULTIPLE_NEWLINES: /\n\s*\n\s*\n/g,
+ TRAILING_SPACES: /[ \t]+$/gm,
+ TRIM: /^\s+|\s+$/g,
+ RETURNS_LIKE_TUPLE: /^`?\{[^}]+\}`?/,
+ RETURNS_LIKE_ATOM: /^`?(ok|error|not_found|true|false)\b/i,
+ REMOVE_DOC: /@doc\s*/g,
+ WHITESPACE_NORMALIZE: /\s+/g,
+ COMMA_START: /^,\s*/,
+ COMMA_END: /,\s*$/,
+ EMPTY_LINE: /^\s*$/,
+ COLON_END: /:\s*$/,
+ COMMENT_DOUBLE_PREFIX: /^\s*%%\s?/,
+ COMMENT_SINGLE_PREFIX: /^\s*%\s?/
+};
+
+class ErlangLiterateParser {
+ constructor(options = {}) {
+ this.options = {
+ githubBase: 'https://github.com/permaweb/HyperBEAM/blob/edge/src',
+ verbose: false,
+ ...options
+ };
+ this.reset();
+ }
+
+ reset() {
+ this.lines = [];
+ this.moduleInfo = {
+ name: null,
+ doc: null,
+ exports: [],
+ includes: [],
+ defines: [],
+ behaviours: [],
+ records: [],
+ types: [],
+ specs: [],
+ attributes: []
+ };
+ this.functions = [];
+ this.undocumentedFunctions = [];
+ this.commentedCodeBlocks = [];
+ this.conditionalDirectives = [];
+ this.sections = [];
+ this.currentState = {
+ inFunction: false,
+ functionName: STRINGS.EMPTY,
+ functionSpec: STRINGS.EMPTY,
+ functionDoc: STRINGS.EMPTY,
+ functionLines: [],
+ pendingDoc: STRINGS.EMPTY,
+ specFunctionName: STRINGS.EMPTY,
+ braceDepth: 0,
+ parenDepth: 0,
+ inlineDocTags: []
+ };
+ }
+
+ parseFile(filePath) {
+ const content = fs.readFileSync(filePath, 'utf8');
+ this.reset();
+ this.lines = content.split(STRINGS.NEWLINE);
+
+ this.extractModuleInfo();
+ this.processFunctions();
+
+ return this.generateMarkdown(path.basename(filePath));
+ }
+
+ extractModuleInfo() {
+ const moduleDoc = [];
+ let inModuleDoc = false;
+ let moduleDocCollected = false;
+ const linesLength = this.lines.length;
+
+ for (let i = 0; i < linesLength; i++) {
+ const line = this.lines[i];
+ const trimmed = line.trim();
+
+ if (!trimmed) {
+ if (inModuleDoc) moduleDoc.push(STRINGS.EMPTY);
+ continue;
+ }
+
+ // Fast character check before regex
+ const firstChar = trimmed.charCodeAt(0);
+
+ // Handle all module-level declarations
+ if (firstChar === CHAR_CODES.DASH) {
+ // Module name
+ if (trimmed.startsWith('-module(')) {
+ const moduleMatch = trimmed.match(REGEX.MODULE);
+ if (moduleMatch) {
+ this.moduleInfo.name = moduleMatch[1];
+ }
+ continue;
+ }
+
+ // Exports - handle multi-line exports
+ if (REGEX.EXPORT_PREFIX.test(trimmed)) {
+ const exportLines = this.collectMultiLineConstruct(i, '[', ']');
+ const fullExport = exportLines.join(' ');
+ const exportMatch = fullExport.match(REGEX.EXPORT);
+ if (exportMatch) {
+ const exports = exportMatch[1]
+ .split(',')
+ .map(e => e.trim())
+ .filter(Boolean);
+ this.moduleInfo.exports.push(...exports);
+ }
+ i += exportLines.length - 1;
+ continue;
+ }
+
+ // Includes
+ const includeMatch = trimmed.match(REGEX.INCLUDE);
+ if (includeMatch) {
+ this.moduleInfo.includes.push({
+ file: includeMatch[1],
+ line: trimmed
+ });
+ continue;
+ }
+
+ // Defines
+ const defineMatch = trimmed.match(REGEX.DEFINE);
+ if (defineMatch) {
+ this.moduleInfo.defines.push({
+ name: defineMatch[1],
+ value: defineMatch[2] || '',
+ line: trimmed
+ });
+ continue;
+ }
+
+ // Behaviours
+ const behaviourMatch = trimmed.match(REGEX.BEHAVIOUR);
+ if (behaviourMatch) {
+ this.moduleInfo.behaviours.push(behaviourMatch[1]);
+ continue;
+ }
+
+ // Records
+ if (REGEX.RECORD.test(trimmed)) {
+ const recordLines = this.collectMultiLineConstruct(i, '{', '}');
+ const recordMatch = trimmed.match(REGEX.RECORD);
+ if (recordMatch) {
+ this.moduleInfo.records.push({
+ name: recordMatch[1],
+ definition: recordLines.join('\n')
+ });
+ }
+ i += recordLines.length - 1;
+ continue;
+ }
+
+ // Types
+ const typeMatch = trimmed.match(REGEX.TYPE);
+ if (typeMatch) {
+ const typeLines = this.collectMultiLineConstruct(i, '(', ')');
+ this.moduleInfo.types.push({
+ name: typeMatch[1],
+ definition: typeLines.join('\n')
+ });
+ i += typeLines.length - 1;
+ continue;
+ }
+
+ // Specs (collect but don't process here)
+ if (REGEX.SPEC.test(trimmed)) {
+ const specLines = this.collectMultiLineConstruct(i, '(', ')');
+ const specMatch = trimmed.match(REGEX.SPEC);
+ if (specMatch) {
+ this.moduleInfo.specs.push({
+ function: specMatch[1],
+ definition: specLines.join('\n')
+ });
+ }
+ i += specLines.length - 1;
+ continue;
+ }
+
+ // Other attributes
+ const attrMatch = trimmed.match(REGEX.ATTRIBUTE);
+ if (attrMatch) {
+ this.moduleInfo.attributes.push({
+ name: attrMatch[1],
+ line: trimmed
+ });
+ continue;
+ }
+ }
+
+ // Module documentation - only collect at the beginning of the file
+ if (firstChar === CHAR_CODES.PERCENT && trimmed.startsWith(STRINGS.TRIPLE_PERCENT) && !moduleDocCollected) {
+ inModuleDoc = true;
+ let docLine = trimmed.substring(3).trim();
+ docLine = docLine.replace(REGEX.REMOVE_DOC, STRINGS.EMPTY);
+
+ // Check for termination pattern (%%% ''')
+ if (docLine === "'''") {
+ inModuleDoc = false; // End module documentation processing
+ moduleDocCollected = true; // Mark as collected
+ continue; // Continue processing rest of file
+ }
+
+ moduleDoc.push(docLine);
+ } else if (inModuleDoc && (firstChar === CHAR_CODES.PERCENT || firstChar === CHAR_CODES.DASH)) {
+ inModuleDoc = false;
+ moduleDocCollected = true; // Mark as collected
+ // Don't break - continue processing this line for module declarations
+ i--; // Re-process this line outside module doc context
+ }
+ }
+
+ this.moduleInfo.doc = this.cleanDocumentation(this.fixModuleDocCodeBlocks(moduleDoc.join(STRINGS.NEWLINE)));
+ }
+
+ collectMultiLineConstruct(startIdx, openChar, closeChar) {
+ const lines = [];
+ let depth = 0;
+ let found = false;
+
+ for (let i = startIdx; i < this.lines.length; i++) {
+ const line = this.lines[i];
+ lines.push(line);
+
+ for (let j = 0; j < line.length; j++) {
+ const char = line[j];
+ if (char === openChar) {
+ depth++;
+ found = true;
+ } else if (char === closeChar && found) {
+ depth--;
+ if (depth === 0) {
+ return lines;
+ }
+ }
+ }
+
+ // Safety check for runaway constructs
+ if (i - startIdx > 100) break;
+ }
+
+ return lines;
+ }
+
+ processFunctions() {
+ const linesLength = this.lines.length;
+ const processedFunctions = new Set();
+ const commentedCodeBlocks = [];
+ let currentCommentedBlock = [];
+ let inCommentedBlock = false;
+
+ for (let i = 0; i < linesLength; i++) {
+ const line = this.lines[i];
+ const trimmed = line.trim();
+
+ if (!trimmed) {
+ if (inCommentedBlock) {
+ currentCommentedBlock.push(line);
+ }
+ continue;
+ }
+
+ // Check for comment-style section headers
+ if (trimmed.startsWith('%%%') && trimmed.match(/^%%%-{10,}$/)) {
+ // This is a dash line, check if next line is a header and line after that is also dashes
+ if (i + 1 < linesLength && i + 2 < linesLength) {
+ const nextLine = this.lines[i + 1].trim();
+ const afterLine = this.lines[i + 2].trim();
+
+ if (nextLine.startsWith('%%%') && !nextLine.match(/^%%%-{10,}$/) &&
+ afterLine.match(/^%%%-{10,}$/)) {
+ // Extract header text
+ const headerText = nextLine.replace(/^%%%\s*/, '').trim();
+ if (headerText) {
+ this.sections.push({
+ type: 'section_header',
+ title: headerText,
+ lineNumber: i + 2 // Store line number for sorting later
+ });
+ }
+ // Skip the next two lines
+ i += 2;
+ continue;
+ }
+ }
+ }
+
+ // Check for commented-out code blocks (lines starting with % but containing code patterns)
+ if (trimmed.startsWith('%') && !trimmed.startsWith('%%')) {
+ const uncommented = trimmed.substring(1).trim();
+ if (this.looksLikeCode(uncommented)) {
+ if (!inCommentedBlock) {
+ inCommentedBlock = true;
+ currentCommentedBlock = [];
+ }
+ currentCommentedBlock.push(line);
+ continue;
+ } else if (inCommentedBlock) {
+ // End of commented code block
+ if (currentCommentedBlock.length > 0) {
+ commentedCodeBlocks.push({
+ type: 'commented_code',
+ lines: [...currentCommentedBlock],
+ startLine: i - currentCommentedBlock.length + 1
+ });
+ }
+ inCommentedBlock = false;
+ currentCommentedBlock = [];
+ }
+ } else if (inCommentedBlock) {
+ // End of commented code block
+ if (currentCommentedBlock.length > 0) {
+ commentedCodeBlocks.push({
+ type: 'commented_code',
+ lines: [...currentCommentedBlock],
+ startLine: i - currentCommentedBlock.length + 1
+ });
+ }
+ inCommentedBlock = false;
+ currentCommentedBlock = [];
+ }
+
+ // Check for start of function documentation block
+ if (REGEX.DOC_BLOCK_START.test(trimmed)) {
+ this.collectFunctionDoc(i);
+ continue;
+ }
+
+ // Check for -spec
+ if (trimmed.startsWith(STRINGS.SPEC_PREFIX)) {
+ this.collectParamTagsBeforeSpec(i);
+ this.collectSpec(i);
+ const specMatch = trimmed.match(REGEX.SPEC);
+ if (specMatch) {
+ this.currentState.specFunctionName = specMatch[1];
+ }
+ continue;
+ }
+
+ // Check for conditional compilation directives
+ if (trimmed.startsWith('-ifdef(') || trimmed.startsWith('-ifndef(') ||
+ trimmed.startsWith('-else') || trimmed.startsWith('-endif')) {
+ this.addDirectiveToOutput(line, i);
+ continue;
+ }
+
+ // Check for function start
+ const funcMatch = trimmed.match(REGEX.FUNCTION);
+ if (funcMatch && !this.currentState.inFunction) {
+ const functionName = this.currentState.specFunctionName || funcMatch[1];
+ processedFunctions.add(functionName);
+
+ if (this.currentState.pendingDoc) {
+ this.currentState.functionDoc = this.currentState.pendingDoc;
+ this.currentState.pendingDoc = STRINGS.EMPTY;
+ this.startFunction(functionName, i);
+ } else {
+ // This is an undocumented function
+ this.startUndocumentedFunction(functionName, i);
+ // Skip ahead to avoid reprocessing
+ while (i < linesLength && !this.isFunctionEnd(this.lines[i])) {
+ i++;
+ }
+ }
+ this.currentState.specFunctionName = STRINGS.EMPTY;
+ }
+
+ // If in function, collect lines
+ if (this.currentState.inFunction) {
+ this.collectFunctionLine(line);
+ if (this.isFunctionEnd(line)) {
+ this.endFunction();
+ }
+ }
+ }
+
+ // Handle remaining commented code block
+ if (inCommentedBlock && currentCommentedBlock.length > 0) {
+ commentedCodeBlocks.push({
+ type: 'commented_code',
+ lines: currentCommentedBlock,
+ startLine: linesLength - currentCommentedBlock.length + 1
+ });
+ }
+
+ if (this.currentState.inFunction) {
+ this.endFunction();
+ }
+
+ // Store commented code blocks for later inclusion
+ this.commentedCodeBlocks = commentedCodeBlocks;
+ }
+
+ collectFunctionDoc(startIdx) {
+ const docLines = [];
+ const linesLength = this.lines.length;
+
+ for (let i = startIdx; i < linesLength; i++) {
+ const line = this.lines[i];
+ const trimmed = line.trim();
+
+ if (trimmed.startsWith(STRINGS.DOUBLE_PERCENT)) {
+ let docLine = trimmed.substring(2).trim();
+ if (i === startIdx) {
+ docLine = docLine.replace(REGEX.REMOVE_DOC, STRINGS.EMPTY);
+ }
+ docLines.push(docLine);
+ } else if (!trimmed) {
+ // Look ahead for more %% comments
+ let j = i + 1;
+ while (j < linesLength && !this.lines[j].trim()) j++;
+
+ if (j < linesLength && this.lines[j].trim().startsWith(STRINGS.DOUBLE_PERCENT)) {
+ docLines.push(STRINGS.EMPTY);
+ } else {
+ break;
+ }
+ } else if (!trimmed.startsWith(STRINGS.SINGLE_PERCENT)) {
+ break;
+ }
+ }
+
+ this.currentState.pendingDoc = docLines.join(STRINGS.NEWLINE);
+ }
+
+ collectParamTagsBeforeSpec(specIdx) {
+ const paramLines = [];
+ let hitDocBlock = false;
+
+ for (let i = specIdx - 1; i >= 0; i--) {
+ const trimmed = this.lines[i].trim();
+
+ if (REGEX.DOC_BLOCK_START.test(trimmed)) {
+ hitDocBlock = true;
+ break;
+ }
+
+ if (!trimmed || (!trimmed.startsWith(STRINGS.DOUBLE_PERCENT) && !trimmed.startsWith(STRINGS.SINGLE_PERCENT))) {
+ break;
+ }
+
+ if (trimmed.startsWith(STRINGS.DOUBLE_PERCENT) &&
+ (trimmed.includes(STRINGS.PARAM_TAG) || trimmed.includes(STRINGS.RETURNS_TAG))) {
+ paramLines.unshift(trimmed.substring(2).trim());
+ }
+ }
+
+ if (paramLines.length > 0 && !hitDocBlock) {
+ const existingDoc = this.currentState.pendingDoc;
+ const newDoc = paramLines.join(STRINGS.NEWLINE);
+ this.currentState.pendingDoc = existingDoc ?
+ existingDoc + STRINGS.NEWLINE + newDoc : newDoc;
+ }
+ }
+
+ collectSpec(startIdx) {
+ const specLines = [];
+ let depth = 0;
+ const linesLength = this.lines.length;
+
+ for (let i = startIdx; i < linesLength; i++) {
+ const line = this.lines[i];
+ specLines.push(line);
+
+ // Fast character-based depth tracking
+ for (let j = 0, len = line.length; j < len; j++) {
+ const charCode = line.charCodeAt(j);
+ if (charCode === CHAR_CODES.OPEN_PAREN) depth++;
+ else if (charCode === CHAR_CODES.CLOSE_PAREN) depth--;
+ }
+
+ if (line.trim().endsWith('.') && depth === 0) {
+ break;
+ }
+ }
+
+ this.currentState.functionSpec = specLines.join(STRINGS.NEWLINE);
+ }
+
+ startFunction(name, lineNumber = 0) {
+ this.currentState.inFunction = true;
+ this.currentState.functionName = name;
+ this.currentState.functionLineNumber = lineNumber;
+ this.currentState.functionLines.length = 0;
+ this.currentState.braceDepth = 0;
+ this.currentState.parenDepth = 0;
+ this.currentState.inlineDocTags.length = 0;
+ }
+
+ collectFunctionLine(line) {
+ this.currentState.functionLines.push(line);
+
+ // Fast character-based depth tracking
+ for (let i = 0, len = line.length; i < len; i++) {
+ const charCode = line.charCodeAt(i);
+ switch (charCode) {
+ case CHAR_CODES.OPEN_BRACE:
+ case CHAR_CODES.OPEN_BRACKET:
+ this.currentState.braceDepth++;
+ break;
+ case CHAR_CODES.CLOSE_BRACE:
+ case CHAR_CODES.CLOSE_BRACKET:
+ this.currentState.braceDepth--;
+ break;
+ case CHAR_CODES.OPEN_PAREN:
+ this.currentState.parenDepth++;
+ break;
+ case CHAR_CODES.CLOSE_PAREN:
+ this.currentState.parenDepth--;
+ break;
+ }
+ }
+ }
+
+ isFunctionEnd(line) {
+ const trimmed = line.trim();
+ return this.currentState.braceDepth === 0 &&
+ this.currentState.parenDepth === 0 &&
+ trimmed.charCodeAt(trimmed.length - 1) === CHAR_CODES.DOT &&
+ trimmed.charCodeAt(0) !== CHAR_CODES.PERCENT;
+ }
+
+ endFunction() {
+ const processedBody = this.processFunctionBody(this.currentState.functionLines);
+
+ this.functions.push({
+ name: this.currentState.functionName,
+ spec: this.currentState.functionSpec,
+ doc: this.currentState.functionDoc,
+ body: processedBody,
+ hasImplementation: true,
+ lineNumber: this.currentState.functionLineNumber
+ });
+
+ // Reset state efficiently
+ this.currentState.inFunction = false;
+ this.currentState.functionName = STRINGS.EMPTY;
+ this.currentState.functionLineNumber = 0;
+ this.currentState.functionSpec = STRINGS.EMPTY;
+ this.currentState.functionDoc = STRINGS.EMPTY;
+ this.currentState.functionLines.length = 0;
+ this.currentState.specFunctionName = STRINGS.EMPTY;
+ this.currentState.inlineDocTags.length = 0;
+ }
+
+ startUndocumentedFunction(name, startLine) {
+ const functionLines = [];
+ let braceDepth = 0;
+ let parenDepth = 0;
+ let i = startLine;
+
+ // Collect the entire function
+ while (i < this.lines.length) {
+ const line = this.lines[i];
+ functionLines.push(line);
+
+ // Track depth
+ for (let j = 0; j < line.length; j++) {
+ const charCode = line.charCodeAt(j);
+ switch (charCode) {
+ case CHAR_CODES.OPEN_BRACE:
+ case CHAR_CODES.OPEN_BRACKET:
+ braceDepth++;
+ break;
+ case CHAR_CODES.CLOSE_BRACE:
+ case CHAR_CODES.CLOSE_BRACKET:
+ braceDepth--;
+ break;
+ case CHAR_CODES.OPEN_PAREN:
+ parenDepth++;
+ break;
+ case CHAR_CODES.CLOSE_PAREN:
+ parenDepth--;
+ break;
+ }
+ }
+
+ // Check if function ended
+ const trimmed = line.trim();
+ if (braceDepth === 0 && parenDepth === 0 &&
+ trimmed.charCodeAt(trimmed.length - 1) === CHAR_CODES.DOT &&
+ trimmed.charCodeAt(0) !== CHAR_CODES.PERCENT) {
+ break;
+ }
+
+ i++;
+ }
+
+ // Find corresponding spec
+ const spec = this.moduleInfo.specs.find(s => s.function === name);
+
+ this.undocumentedFunctions.push({
+ name,
+ spec: spec ? spec.definition : null,
+ body: this.processFunctionBody(functionLines),
+ lines: functionLines
+ });
+ }
+
+ // Helper method to detect if a line looks like code
+ looksLikeCode(line) {
+ if (!line || line.length === 0) return false;
+
+ // Check for common code patterns
+ const codePatterns = [
+ /^[a-z][a-z0-9_]*\s*\(/, // function calls: function(
+ /^[A-Z][a-zA-Z0-9_]*\s*=/, // variable assignments: Var =
+ /^\s*\{/, // tuples/records: {
+ /^\s*\[/, // lists: [
+ /^\s*case\s+/, // case statements
+ /^\s*if\s+/, // if statements
+ /^\s*catch\s+/, // catch blocks
+ /^\s*after\s+/, // after blocks
+ /^\s*end[,.]?\s*$/, // end keywords
+ /^\s*ok\s*$/, // ok atoms
+ /^\s*true\s*$/, // boolean atoms
+ /^\s*false\s*$/, // boolean atoms
+ /->\s*$/, // arrow operators
+ /^\s*\?[A-Z]/, // macro usage
+ /^\s*[a-z_][a-z0-9_]*\s*\(/, // function definitions
+ /^\s*\d+\s*$/, // numbers
+ /^\s*".*"\s*$/, // strings
+ /^\s*<<.*>>\s*$/, // binaries
+ /^\s*#\w+/, // record syntax
+ /^\s*receive\s+/, // receive blocks
+ /^\s*spawn/, // spawn calls
+ /^\s*gen_server:/, // gen_server calls
+ /^\s*supervisor:/, // supervisor calls
+ /\bmatch\b|\bguard\b|\btry\b|\bfun\b/, // erlang keywords
+ ];
+
+ return codePatterns.some(pattern => pattern.test(line));
+ }
+
+ // Helper method to add conditional compilation directives
+ addDirectiveToOutput(line, lineNumber) {
+ this.conditionalDirectives.push({
+ line: line.trim(),
+ lineNumber: lineNumber + 1,
+ type: 'conditional_compilation'
+ });
+ }
+
+ processFunctionBody(lines) {
+ const segments = [];
+ const currentCode = [];
+ const pendingTagLines = [];
+
+ const flushCode = () => {
+ if (currentCode.length > 0) {
+ segments.push({ type: 'code', content: currentCode.join(STRINGS.NEWLINE) });
+ currentCode.length = 0;
+ }
+ };
+
+ const flushTags = () => {
+ if (pendingTagLines.length > 0) {
+ const tagText = pendingTagLines.join(STRINGS.NEWLINE);
+ const parsed = this.parseDocumentation(tagText);
+ const docParts = [];
+
+ if (parsed.params.length > 0) {
+ docParts.push(STRINGS.PARAMETERS_HEADER, STRINGS.EMPTY);
+ parsed.params.forEach(p => {
+ const desc = this.cleanDocumentation(p.description || STRINGS.EMPTY, true);
+ docParts.push(`- ${STRINGS.BACKTICK}${p.name}${STRINGS.BACKTICK} - ${desc}`);
+ });
+ docParts.push(STRINGS.EMPTY);
+ }
+
+ if (parsed.returns.length > 0) {
+ docParts.push(STRINGS.RETURNS_HEADER, STRINGS.EMPTY);
+ const expanded = parsed.returns.flatMap(r => this.splitReturnsIntoOutcomes(r));
+ expanded.forEach(r => docParts.push(`- ${this.formatReturnsText(r)}`));
+ docParts.push(STRINGS.EMPTY);
+ }
+
+ if (docParts.length > 0) {
+ segments.push({ type: 'doc', content: docParts.join(STRINGS.NEWLINE) });
+ }
+ pendingTagLines.length = 0;
+ }
+ };
+
+ for (const line of lines) {
+ const trimmed = line.trim();
+
+ if (REGEX.COMMENT_SINGLE.test(trimmed) || REGEX.COMMENT_DOUBLE.test(trimmed)) {
+ flushCode();
+
+ const commentText = REGEX.COMMENT_DOUBLE.test(trimmed)
+ ? line.replace(REGEX.COMMENT_DOUBLE_PREFIX, STRINGS.EMPTY)
+ : line.replace(REGEX.COMMENT_SINGLE_PREFIX, STRINGS.EMPTY);
+ const cleaned = this.cleanInlineComment(commentText);
+
+ const returnsLikeTuple = REGEX.RETURNS_LIKE_TUPLE.test(cleaned);
+ const returnsLikeAtom = REGEX.RETURNS_LIKE_ATOM.test(cleaned);
+ const isTagParam = cleaned.startsWith(STRINGS.PARAM_TAG);
+ const isTagReturns = cleaned.startsWith(STRINGS.RETURNS_TAG);
+
+ if (isTagParam || isTagReturns || returnsLikeTuple || returnsLikeAtom) {
+ const lineAsTag = (isTagParam || isTagReturns)
+ ? cleaned.trim()
+ : `${STRINGS.RETURNS_TAG} ${cleaned.trim()}`;
+ pendingTagLines.push(lineAsTag);
+ } else if (pendingTagLines.length > 0) {
+ const lastTag = pendingTagLines[pendingTagLines.length - 1];
+ if (lastTag.startsWith(STRINGS.RETURNS_TAG) || lastTag.startsWith(STRINGS.PARAM_TAG)) {
+ pendingTagLines.push(cleaned.trim());
+ } else {
+ flushTags();
+ segments.push({ type: 'comment', content: cleaned });
+ }
+ } else {
+ flushTags();
+ segments.push({ type: 'comment', content: cleaned });
+ }
+ } else {
+ flushTags();
+ currentCode.push(line);
+ }
+ }
+
+ flushTags();
+ flushCode();
+ return segments;
+ }
+
+ cleanInlineComment(text) {
+ return text.replace(REGEX.BACKTICK_QUOTE, `${STRINGS.BACKTICK}$1${STRINGS.BACKTICK}`).trim();
+ }
+
+ fixCodeBlocks(text) {
+ if (!text) return text;
+
+ const lines = text.split(STRINGS.NEWLINE);
+ const result = [];
+ let inCodeBlock = false;
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ const trimmed = line.trim();
+
+ // Check if this is a code block delimiter
+ if (REGEX.CODE_FENCE.test(trimmed)) {
+ if (!inCodeBlock && trimmed === '```') {
+ // Start of unmarked code block - check if it's empty first
+ let blockContent = [];
+ let j = i + 1;
+
+ // Collect content until closing ```
+ while (j < lines.length) {
+ const contentLine = lines[j];
+ if (contentLine.trim() === '```') {
+ break;
+ }
+ blockContent.push(contentLine);
+ j++;
+ }
+
+ // If block is empty, skip it entirely
+ const hasContent = blockContent.some(line => line.trim() !== '');
+ if (!hasContent) {
+ // Skip the empty code block entirely
+ i = j; // Skip to after the closing ```
+ continue;
+ }
+
+ // Determine appropriate language based on content
+ const nextLine = blockContent.length > 0 ? blockContent[0].trim() : '';
+ let language = 'text'; // default
+
+ // Heuristics to determine language based on content
+ if (nextLine.startsWith('/') || nextLine.includes('Parameters:') ||
+ nextLine.includes('- `') || nextLine.includes('(optional)')) {
+ language = 'text';
+ } else if (nextLine.includes('#{') || nextLine.includes('<<') ||
+ nextLine.includes('->') || nextLine.match(/^[a-z_]+\(/)) {
+ language = 'erlang';
+ }
+
+ // For text blocks, add ignore attribute to prevent mdBook testing
+ if (language === 'text') {
+ result.push('```text,ignore');
+ } else {
+ result.push('```' + language);
+ }
+ inCodeBlock = true;
+ } else if (inCodeBlock && trimmed === '```') {
+ // End of code block
+ result.push(line);
+ inCodeBlock = false;
+ } else {
+ // Already has language specifier or other case
+ result.push(line);
+ if (trimmed.startsWith('```')) {
+ inCodeBlock = !inCodeBlock;
+ }
+ }
+ } else {
+ result.push(line);
+ }
+ }
+
+ return result.join(STRINGS.NEWLINE);
+ }
+
+ fixModuleDocCodeBlocks(text) {
+ if (!text) return STRINGS.EMPTY;
+
+ const lines = text.split(STRINGS.NEWLINE);
+ const result = [];
+ let inCodeBlock = false;
+ let codeBlockStart = -1;
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ const trimmed = line.trim();
+
+ // Check for code block start
+ if (trimmed === '```' && !inCodeBlock) {
+ inCodeBlock = true;
+ codeBlockStart = i;
+ result.push(line);
+ continue;
+ }
+
+ // Check for code block end
+ if (trimmed === '```' && inCodeBlock) {
+ inCodeBlock = false;
+ result.push(line);
+ continue;
+ }
+
+ // Check for implicit code block end (new section starting with /)
+ if (inCodeBlock && trimmed.startsWith('/')) {
+ // Close the previous code block
+ result.push('```');
+ result.push('');
+ inCodeBlock = false;
+ }
+
+ result.push(line);
+ }
+
+ // Close any unclosed code block at the end
+ if (inCodeBlock) {
+ result.push('```');
+ }
+
+ return result.join(STRINGS.NEWLINE);
+ }
+
+ cleanDocumentation(text, skipCodeBlockFix = false) {
+ if (!text) return STRINGS.EMPTY;
+
+ text = text.replace(REGEX.PRE_TAG, (match, content) => this.formatPreContent(content));
+
+ // Only fix unmarked code blocks for module-level documentation
+ // Skip for function documentation to avoid excessive text,ignore blocks
+ if (!skipCodeBlockFix) {
+ text = this.fixCodeBlocks(text);
+ }
+
+ let cleaned = text
+ .replace(REGEX.BACKTICK_QUOTE, `${STRINGS.BACKTICK}$1${STRINGS.BACKTICK}`)
+ .replace(REGEX.HTML_ENTITIES_LT, '<<')
+ .replace(REGEX.HTML_ENTITIES_GT, '>>')
+ .replace(REGEX.REMOVE_DOC, STRINGS.EMPTY)
+ .replace(REGEX.MULTIPLE_NEWLINES, '\n\n')
+ .replace(REGEX.TRAILING_SPACES, STRINGS.EMPTY)
+ .replace(REGEX.TRIM, STRINGS.EMPTY);
+
+ return this.reflowNumberedLists(cleaned);
+ }
+
+ convertCommentStyleHeaders(text) {
+ if (!text) return text;
+
+ const lines = text.split(STRINGS.NEWLINE);
+ const result = [];
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ const trimmed = line.trim();
+
+ // Look for pattern: dashes followed by text followed by dashes
+ if (trimmed.match(/^-{10,}$/)) {
+ // This is a dash line, check if next line is a header
+ if (i + 1 < lines.length) {
+ const nextLine = lines[i + 1];
+ const nextTrimmed = nextLine.trim();
+
+ // Check if the line after is also dashes (closing the header)
+ if (i + 2 < lines.length && lines[i + 2].trim().match(/^-{10,}$/)) {
+ // This is a comment-style header: convert to markdown
+ if (nextTrimmed) {
+ result.push(`## ${nextTrimmed}`);
+ result.push(STRINGS.EMPTY);
+ }
+ // Skip the next two lines (header text and closing dashes)
+ i += 2;
+ continue;
+ }
+ }
+ }
+
+ result.push(line);
+ }
+
+ return result.join(STRINGS.NEWLINE);
+ }
+
+ generateInterleavedContent(md) {
+ // Create a combined list of sections and functions, sorted by line number
+ const contentItems = [];
+
+ // Add sections
+ for (const section of this.sections) {
+ if (section.type === 'section_header') {
+ contentItems.push({
+ type: 'section',
+ lineNumber: section.lineNumber,
+ title: section.title
+ });
+ }
+ }
+
+ // Add functions
+ const groupedFunctions = this.groupFunctionsByName(this.functions);
+ for (const group of groupedFunctions) {
+ // Use the line number of the first function in the group
+ const lineNumber = group.functions[0]?.lineNumber || 0;
+ contentItems.push({
+ type: 'function_group',
+ lineNumber: lineNumber,
+ group: group
+ });
+ }
+
+ // Sort by line number
+ contentItems.sort((a, b) => a.lineNumber - b.lineNumber);
+
+ // Generate markdown for each item
+ for (const item of contentItems) {
+ if (item.type === 'section') {
+ md.push(`## ${item.title}`);
+ md.push(STRINGS.EMPTY);
+ } else if (item.type === 'function_group') {
+ this.generateFunctionGroupMarkdown(md, item.group);
+ }
+ }
+ }
+
+ generateFunctionGroupMarkdown(md, group) {
+ md.push(`## ${group.name}`);
+ md.push(STRINGS.EMPTY);
+
+ const combinedDoc = this.combineFunctionDocs(group.functions);
+ if (combinedDoc) {
+ md.push(combinedDoc);
+ md.push(STRINGS.EMPTY);
+ }
+
+ for (const func of group.functions) {
+ if (func.spec) {
+ md.push(`\`\`\`${STRINGS.ERLANG}`);
+ md.push(func.spec.trim());
+ md.push('```');
+ md.push(STRINGS.EMPTY);
+ }
+
+ if (func.body?.length > 0) {
+ md.push(STRINGS.EMPTY);
+ for (const segment of func.body) {
+ if (segment.type === 'comment') {
+ md.push(segment.content);
+ md.push(STRINGS.EMPTY);
+ } else if (segment.type === 'doc') {
+ md.push(segment.content);
+ md.push(STRINGS.EMPTY);
+ } else if (segment.type === 'code') {
+ md.push(`\`\`\`${STRINGS.ERLANG}`);
+ md.push(segment.content.trim());
+ md.push('```');
+ md.push(STRINGS.EMPTY);
+ }
+ }
+ }
+ }
+
+ md.push(STRINGS.EMPTY);
+ }
+
+ formatReturnsText(text) {
+ if (!text) return STRINGS.EMPTY;
+ let result = this.cleanDocumentation(text, true);
+
+ const leadingMatch = result.match(REGEX.LEADING_RETURN_TOKEN);
+ if (leadingMatch) {
+ const [, leadSpace, token] = leadingMatch;
+ result = leadSpace + STRINGS.BACKTICK + token + STRINGS.BACKTICK +
+ result.slice(leadSpace.length + token.length);
+ }
+
+ return result.replace(REGEX.STANDALONE_TUPLE,
+ (m, pre, tuple, post) => `${pre}${STRINGS.BACKTICK}${tuple}${STRINGS.BACKTICK}${post}`);
+ }
+
+ splitReturnsIntoOutcomes(text) {
+ if (!text) return [];
+ const s = this.cleanDocumentation(text, true);
+ const matches = [];
+ let match;
+
+ // Reset regex lastIndex to avoid issues with global regex
+ REGEX.RETURNS_TOKENS.lastIndex = 0;
+ while ((match = REGEX.RETURNS_TOKENS.exec(s)) !== null) {
+ matches.push({ index: match.index, token: match[0] });
+ }
+
+ if (matches.length === 0 || matches[0].index > 0) {
+ return [s.trim()];
+ }
+
+ const parts = [];
+ const matchesLength = matches.length;
+ for (let i = 0; i < matchesLength; i++) {
+ const start = matches[i].index;
+ const nextStart = (i + 1 < matchesLength) ? matches[i + 1].index : s.length;
+ let segment = s.slice(start, nextStart).trim()
+ .replace(REGEX.COMMA_START, STRINGS.EMPTY)
+ .replace(REGEX.COMMA_END, STRINGS.EMPTY);
+ if (segment) parts.push(segment.trim());
+ }
+
+ return parts.filter(Boolean);
+ }
+
+ reflowNumberedLists(text) {
+ if (!text) return STRINGS.EMPTY;
+ const lines = text.split(STRINGS.NEWLINE);
+ const out = [];
+ let inNumbered = false;
+ let lastNumIndex = -1;
+
+ const linesLength = lines.length;
+ for (let i = 0; i < linesLength; i++) {
+ const raw = lines[i];
+ const trimmed = raw.trim();
+
+ const isNumbered = REGEX.NUMBERED_LIST.test(trimmed);
+ const isBullet = REGEX.BULLET_LIST.test(trimmed);
+ const isHeading = REGEX.HEADING.test(trimmed);
+ const isCodeFence = REGEX.CODE_FENCE.test(trimmed);
+
+ // Ensure a blank line BEFORE any list (numbered or bullet) begins
+ if ((isNumbered || isBullet) && out.length > 0) {
+ const prev = out[out.length - 1];
+ if (prev.trim() !== STRINGS.EMPTY) {
+ out.push(STRINGS.EMPTY);
+ }
+ }
+
+ if (isNumbered) {
+ out.push(trimmed);
+ inNumbered = true;
+ lastNumIndex = out.length - 1;
+ continue;
+ }
+
+ // Pass through bullet list lines unchanged (no reflow of bullets for now)
+ if (isBullet) {
+ out.push(raw);
+ continue;
+ }
+
+ if (inNumbered) {
+ if (!trimmed) {
+ out.push(STRINGS.EMPTY);
+ inNumbered = false;
+ lastNumIndex = -1;
+ continue;
+ }
+ if (!isNumbered && !isBullet && !isHeading && !isCodeFence) {
+ out[lastNumIndex] = out[lastNumIndex] + STRINGS.SPACE + trimmed;
+ continue;
+ }
+ inNumbered = false;
+ lastNumIndex = -1;
+ }
+
+ out.push(raw);
+ }
+
+ // Ensure blank line separation
+ const separated = [];
+ const outLength = out.length;
+ for (let i = 0; i < outLength; i++) {
+ const cur = out[i];
+ const next = i + 1 < outLength ? out[i + 1] : STRINGS.EMPTY;
+ separated.push(cur);
+ // If a paragraph ends with ':' and is immediately followed by a numbered list,
+ // insert a blank line between them to satisfy Markdown list rendering rules.
+ if (REGEX.COLON_END.test(cur.trim()) && next && REGEX.NUMBERED_LIST.test(next.trim())) {
+ if (separated[separated.length - 1] !== STRINGS.EMPTY) {
+ separated.push(STRINGS.EMPTY);
+ }
+ }
+ }
+
+ return separated.join(STRINGS.NEWLINE);
+ }
+
+ formatPreContent(content) {
+ const lines = content.trim().split(STRINGS.NEWLINE);
+ const formatted = [];
+
+ let i = 0;
+ const linesLength = lines.length;
+ while (i < linesLength) {
+ const line = lines[i].trim();
+
+ if (!line) {
+ i++;
+ continue;
+ }
+
+ const defMatch = line.match(REGEX.DEFINITION);
+
+ if (defMatch) {
+ const [, term, initialDesc] = defMatch;
+ let fullDescription = initialDesc.trim();
+
+ let j = i + 1;
+ while (j < linesLength) {
+ const nextLine = lines[j];
+
+ if (!nextLine.trim()) {
+ j++;
+ continue;
+ }
+
+ if (nextLine.trim().match(REGEX.DEFINITION)) {
+ break;
+ }
+
+ if (nextLine.trim()) {
+ fullDescription += STRINGS.SPACE + nextLine.trim();
+ }
+ j++;
+ }
+
+ formatted.push(STRINGS.EMPTY, `**${term.trim()}**`, STRINGS.EMPTY, fullDescription);
+ i = j;
+ } else {
+ if (line.toLowerCase().includes('hyperbeam') && line.includes('options')) {
+ formatted.push(STRINGS.EMPTY, `### ${line}`, STRINGS.EMPTY);
+ } else {
+ const optMatch = line.match(REGEX.OPTION_DEF);
+ if (optMatch) {
+ const [, optName, optDesc] = optMatch;
+ formatted.push(STRINGS.EMPTY, `**${optName}**`, STRINGS.EMPTY, optDesc);
+ } else {
+ formatted.push(line);
+ }
+ }
+ i++;
+ }
+ }
+
+ return formatted.join(STRINGS.NEWLINE);
+ }
+
+ parseDocumentation(docText) {
+ const lines = docText.split(STRINGS.NEWLINE);
+ const result = {
+ description: [],
+ params: [],
+ returns: []
+ };
+
+ let currentSection = 'description';
+ let currentParam = null;
+ let lastReturnIndex = -1;
+
+ for (const line of lines) {
+ const trimmed = line.trim();
+
+ const paramMatch = trimmed.match(REGEX.PARAM);
+ if (paramMatch) {
+ if (currentParam) {
+ result.params.push(currentParam);
+ }
+ currentParam = {
+ name: paramMatch[1],
+ description: paramMatch[2] || STRINGS.EMPTY
+ };
+ currentSection = 'param';
+ continue;
+ }
+
+ if (REGEX.RETURNS.test(trimmed)) {
+ if (currentParam) {
+ result.params.push(currentParam);
+ currentParam = null;
+ }
+ const returnsText = trimmed.replace(REGEX.RETURNS, STRINGS.EMPTY);
+ result.returns.push(returnsText);
+ lastReturnIndex = result.returns.length - 1;
+ currentSection = 'returns';
+ continue;
+ }
+
+ if (currentSection === 'description') {
+ if (trimmed) {
+ result.description.push(trimmed);
+ } else {
+ const last = result.description[result.description.length - 1];
+ if (last !== STRINGS.EMPTY) {
+ result.description.push(STRINGS.EMPTY);
+ }
+ }
+ } else if (currentSection === 'param' && currentParam && trimmed) {
+ currentParam.description += STRINGS.SPACE + trimmed;
+ } else if (currentSection === 'returns' && trimmed) {
+ if (lastReturnIndex >= 0) {
+ result.returns[lastReturnIndex] =
+ (result.returns[lastReturnIndex] + STRINGS.SPACE + trimmed)
+ .replace(REGEX.WHITESPACE_NORMALIZE, STRINGS.SPACE).trim();
+ } else {
+ result.returns.push(trimmed);
+ lastReturnIndex = result.returns.length - 1;
+ }
+ }
+ }
+
+ if (currentParam) {
+ result.params.push(currentParam);
+ }
+
+ return result;
+ }
+
+ addSeparator(md) {
+ // Only add separator if the last entry is not empty and not already a separator
+ if (md.length > 0) {
+ const lastLine = md[md.length - 1];
+ const prevLine = md.length > 1 ? md[md.length - 2] : '';
+
+ // Don't add separator if last line is already empty and previous is separator
+ if (lastLine === STRINGS.EMPTY && prevLine === STRINGS.SEPARATOR) {
+ return;
+ }
+
+ // Don't add separator if last line is already a separator
+ if (lastLine === STRINGS.SEPARATOR) {
+ return;
+ }
+
+ // Add separator with proper spacing
+ if (lastLine !== STRINGS.EMPTY) {
+ md.push(STRINGS.EMPTY);
+ }
+ md.push(STRINGS.SEPARATOR);
+ md.push(STRINGS.EMPTY);
+ }
+ }
+
+ generateMarkdown(fileName) {
+ const githubUrl = `${this.options.githubBase}/${fileName}`;
+ const md = [];
+
+ // Header
+ md.push(`# ${this.moduleInfo.name || fileName.replace('.erl', STRINGS.EMPTY)}`);
+ md.push(STRINGS.EMPTY);
+ md.push(`[View source on GitHub](${githubUrl})`);
+ md.push(STRINGS.EMPTY);
+
+ // Metadata section
+ this.generateMetadataSection(md);
+
+ // Module documentation
+ if (this.moduleInfo.doc) {
+ md.push(this.moduleInfo.doc);
+ this.addSeparator(md);
+ }
+
+ // Generate interleaved content (sections and functions) sorted by line number
+ this.generateInterleavedContent(md);
+
+ // Commented-out code blocks
+ if (this.commentedCodeBlocks && this.commentedCodeBlocks.length > 0) {
+ md.push('## Commented-Out Code');
+ md.push(STRINGS.EMPTY);
+ md.push('*The following code blocks are commented out but may contain useful examples:*');
+ md.push(STRINGS.EMPTY);
+
+ for (const block of this.commentedCodeBlocks) {
+ md.push('```erlang');
+ for (const line of block.lines) {
+ md.push(line);
+ }
+ md.push('```');
+ md.push(STRINGS.EMPTY);
+ }
+ }
+
+ // Conditional compilation directives
+ if (this.conditionalDirectives && this.conditionalDirectives.length > 0) {
+ md.push('## Conditional Compilation');
+ md.push(STRINGS.EMPTY);
+ md.push('*The following conditional compilation directives are used in this module:*');
+ md.push(STRINGS.EMPTY);
+
+ md.push('```erlang');
+ for (const directive of this.conditionalDirectives) {
+ md.push(directive.line);
+ }
+ md.push('```');
+ md.push(STRINGS.EMPTY);
+ }
+
+ // Undocumented functions section
+ if (this.undocumentedFunctions.length > 0) {
+ md.push('## Undocumented Functions');
+ md.push(STRINGS.EMPTY);
+ md.push('*The following functions lack documentation comments but are included for completeness:*');
+ md.push(STRINGS.EMPTY);
+
+ for (const func of this.undocumentedFunctions) {
+ md.push(`### ${func.name}`);
+ md.push(STRINGS.EMPTY);
+
+ if (func.spec) {
+ md.push(`\`\`\`${STRINGS.ERLANG}`);
+ md.push(func.spec.trim());
+ md.push('```');
+ md.push(STRINGS.EMPTY);
+ }
+
+ if (func.body?.length > 0) {
+ for (const segment of func.body) {
+ if (segment.type === 'comment') {
+ md.push(segment.content);
+ md.push(STRINGS.EMPTY);
+ } else if (segment.type === 'code') {
+ md.push(`\`\`\`${STRINGS.ERLANG}`);
+ md.push(segment.content.trim());
+ md.push('```');
+ md.push(STRINGS.EMPTY);
+ }
+ }
+ }
+ }
+
+ this.addSeparator(md);
+ }
+
+ this.addSeparator(md);
+ md.push(`*Generated from [${fileName}](${githubUrl})*`);
+
+ const finalMarkdown = md.join(STRINGS.NEWLINE);
+ return finalMarkdown;
+ }
+
+ generateMetadataSection(md) {
+ md.push('## Module Metadata');
+ md.push(STRINGS.EMPTY);
+
+ // Basic module information
+ md.push(`**Module:** \`${this.moduleInfo.name || 'unknown'}\``);
+ md.push(`**Exports:** ${this.moduleInfo.exports.length} functions`);
+
+ if (this.moduleInfo.behaviours.length > 0) {
+ md.push(`**Behaviours:** ${this.moduleInfo.behaviours.map(b => `\`${b}\``).join(', ')}`);
+ }
+
+ if (this.moduleInfo.includes.length > 0) {
+ md.push(`**Includes:** ${this.moduleInfo.includes.length} files`);
+ }
+
+ if (this.moduleInfo.defines.length > 0) {
+ md.push(`**Defines:** ${this.moduleInfo.defines.length} macros`);
+ }
+
+ if (this.moduleInfo.records.length > 0) {
+ md.push(`**Records:** ${this.moduleInfo.records.length} records`);
+ }
+
+ if (this.moduleInfo.types.length > 0) {
+ md.push(`**Types:** ${this.moduleInfo.types.length} type definitions`);
+ }
+
+ md.push(STRINGS.EMPTY);
+
+ // Exports section
+ if (this.moduleInfo.exports.length > 0) {
+ md.push('### Exported Functions');
+ md.push(STRINGS.EMPTY);
+ this.moduleInfo.exports.forEach(exp => {
+ md.push(`- \`${exp}\``);
+ });
+ md.push(STRINGS.EMPTY);
+ }
+
+ // Includes section
+ if (this.moduleInfo.includes.length > 0) {
+ md.push('### Includes');
+ md.push(STRINGS.EMPTY);
+ md.push('```erlang');
+ this.moduleInfo.includes.forEach(inc => {
+ md.push(inc.line);
+ });
+ md.push('```');
+ md.push(STRINGS.EMPTY);
+ }
+
+ // Defines section
+ if (this.moduleInfo.defines.length > 0) {
+ md.push('### Macro Definitions');
+ md.push(STRINGS.EMPTY);
+ md.push('```erlang');
+ this.moduleInfo.defines.forEach(def => {
+ md.push(def.line);
+ });
+ md.push('```');
+ md.push(STRINGS.EMPTY);
+ }
+
+ // Records section
+ if (this.moduleInfo.records.length > 0) {
+ md.push('### Record Definitions');
+ md.push(STRINGS.EMPTY);
+ this.moduleInfo.records.forEach(rec => {
+ md.push(`#### \`${rec.name}\``);
+ md.push(STRINGS.EMPTY);
+ md.push('```erlang');
+ md.push(rec.definition);
+ md.push('```');
+ md.push(STRINGS.EMPTY);
+ });
+ }
+
+ // Types section
+ if (this.moduleInfo.types.length > 0) {
+ md.push('### Type Definitions');
+ md.push(STRINGS.EMPTY);
+ this.moduleInfo.types.forEach(type => {
+ md.push(`#### \`${type.name}\``);
+ md.push(STRINGS.EMPTY);
+ md.push('```erlang');
+ md.push(type.definition);
+ md.push('```');
+ md.push(STRINGS.EMPTY);
+ });
+ }
+
+ this.addSeparator(md);
+ }
+
+ groupFunctionsByName(functions) {
+ const groups = [];
+ let currentGroup = null;
+
+ for (const func of functions) {
+ if (!currentGroup || currentGroup.name !== func.name) {
+ currentGroup = { name: func.name, functions: [func] };
+ groups.push(currentGroup);
+ } else {
+ currentGroup.functions.push(func);
+ }
+ }
+
+ return groups;
+ }
+
+ combineFunctionDocs(functions) {
+ for (const func of functions) {
+ if (func.doc) {
+ const parsed = this.parseDocumentation(func.doc);
+ const combinedDoc = [];
+
+ if (parsed.description.length > 0) {
+ combinedDoc.push(this.cleanDocumentation(parsed.description.join(STRINGS.NEWLINE), true));
+ combinedDoc.push(STRINGS.EMPTY);
+ }
+
+ if (parsed.params.length > 0) {
+ combinedDoc.push(STRINGS.PARAMETERS_HEADER);
+ combinedDoc.push(STRINGS.EMPTY);
+ parsed.params.forEach(param => {
+ const desc = this.cleanDocumentation(param.description, true);
+ combinedDoc.push(`- ${STRINGS.BACKTICK}${param.name}${STRINGS.BACKTICK} - ${desc}`);
+ });
+ combinedDoc.push(STRINGS.EMPTY);
+ }
+
+ if (parsed.returns.length > 0) {
+ combinedDoc.push(STRINGS.RETURNS_HEADER);
+ combinedDoc.push(STRINGS.EMPTY);
+ const expanded = parsed.returns.flatMap(r => this.splitReturnsIntoOutcomes(r));
+ expanded.forEach(ret => combinedDoc.push(`- ${this.formatReturnsText(ret)}`));
+ combinedDoc.push(STRINGS.EMPTY);
+ }
+
+ return combinedDoc.join(STRINGS.NEWLINE);
+ }
+ }
+ return null;
+ }
+}
+
+// CLI Interface
+function main() {
+ const args = process.argv.slice(2);
+ const verbose = args.includes('-v') || args.includes('--verbose');
+
+ const srcDir = process.env.SRC_DIR || path.join(process.cwd(), 'src');
+ const outputDir = process.env.OUTPUT_DIR || path.join(process.cwd(), 'docs/book/src');
+
+ if (!fs.existsSync(outputDir)) {
+ fs.mkdirSync(outputDir, { recursive: true });
+ }
+
+ const files = fs.readdirSync(srcDir).filter(f => f.endsWith('.erl'));
+ const parser = new ErlangLiterateParser({ verbose });
+
+ console.log(`Processing ${files.length} Erlang files...`);
+
+ for (const file of files) {
+ if (verbose) console.log(` Processing ${file}...`);
+
+ try {
+ const inputPath = path.join(srcDir, file);
+ const outputPath = path.join(outputDir, `${file}.md`);
+ const markdown = parser.parseFile(inputPath);
+ fs.writeFileSync(outputPath, markdown);
+ } catch (error) {
+ console.error(`Error processing ${file}:`, error.message);
+ }
+ }
+
+ console.log(`â Generated documentation in ${outputDir}`);
+}
+
+if (import.meta.url === `file://${process.argv[1]}`) {
+ main();
+}
+
+export default ErlangLiterateParser;
\ No newline at end of file
diff --git a/docs/generate-literate-docs.sh b/docs/generate-literate-docs.sh
new file mode 100755
index 000000000..0c1328897
--- /dev/null
+++ b/docs/generate-literate-docs.sh
@@ -0,0 +1,200 @@
+#!/bin/bash
+
+# Comprehensive Erlang Literate Documentation Builder (JavaScript Implementation)
+# This script wraps the JavaScript parser for seamless integration
+
+# Colors for output
+GREEN='\033[0;32m'
+YELLOW='\033[0;33m'
+RED='\033[0;31m'
+NC='\033[0m' # No Color
+
+# Get script directory and root
+SCRIPT_DIR="$(dirname "$(realpath "$0")")"
+ROOT_DIR="$(dirname "$SCRIPT_DIR")"
+cd "$ROOT_DIR"
+
+# Configuration
+SRC_DIR="${SRC_DIR:-$ROOT_DIR/src}"
+OUTPUT_DIR="${OUTPUT_DIR:-$ROOT_DIR/docs/book/src}"
+PARSER_SCRIPT="$SCRIPT_DIR/erlang-literate-parser.js"
+
+# Parse arguments
+VERBOSE=false
+DRY_RUN=false
+SHOW_HELP=false
+
+if [[ "$@" == *"-h"* ]] || [[ "$@" == *"--help"* ]]; then
+ SHOW_HELP=true
+fi
+if [[ "$@" == *"-v"* ]] || [[ "$@" == *"--verbose"* ]]; then
+ VERBOSE=true
+fi
+if [[ "$@" == *"--dry-run"* ]] || [[ "$@" == *"--dryrun"* ]]; then
+ DRY_RUN=true
+fi
+
+if [ "$SHOW_HELP" = true ]; then
+ echo -e "${GREEN}HyperBEAM Literate Erlang Documentation Generator (JavaScript)${NC}"
+ echo "========================================================"
+ echo ""
+ echo "Usage: $0 [OPTIONS]"
+ echo ""
+ echo "Options:"
+ echo " -v, --verbose Enable verbose output"
+ echo " --dry-run Simulate deployment without actually deploying"
+ echo " -h, --help Show this help message"
+ echo ""
+ echo "Environment Variables:"
+ echo " SRC_DIR Source directory (default: ./src)"
+ echo " OUTPUT_DIR Output directory (default: ./docs/book/src)"
+ echo " DEPLOY_KEY Arweave wallet key for deployment"
+ echo " ANT_PROCESS ANT process ID for ArNS deployment"
+ echo ""
+ echo "Examples:"
+ echo " $0 # Generate documentation normally"
+ echo " $0 -v # Generate with verbose output"
+ echo " $0 --dry-run # Test deployment without deploying"
+ echo " $0 --dry-run -v # Dry run with verbose output"
+ echo ""
+ exit 0
+fi
+
+echo -e "${GREEN}HyperBEAM Literate Erlang Documentation Generator (JavaScript)${NC}"
+if [ "$DRY_RUN" = true ]; then
+ echo -e "${YELLOW}[DRY RUN MODE] - No actual deployment will occur${NC}"
+fi
+echo "========================================================"
+
+# Check for Node.js
+if ! command -v node &> /dev/null; then
+ echo -e "${RED}Error: Node.js is required but not installed.${NC}"
+ echo "Please install Node.js (version 14 or later) to use this parser."
+ echo ""
+ echo "On macOS with Homebrew: brew install node"
+ echo "On Ubuntu: curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - && sudo apt-get install -y nodejs"
+ exit 1
+fi
+
+# Check Node.js version (need ES modules support)
+NODE_VERSION=$(node --version | cut -d'v' -f2 | cut -d'.' -f1)
+if [ "$NODE_VERSION" -lt 14 ]; then
+ echo -e "${RED}Error: Node.js version 14 or later is required for ES modules support.${NC}"
+ echo "Current version: $(node --version)"
+ exit 1
+fi
+
+# Verify source directory exists
+if [ ! -d "$SRC_DIR" ]; then
+ echo -e "${RED}Error: Source directory not found: $SRC_DIR${NC}"
+ exit 1
+fi
+
+# Verify parser script exists
+if [ ! -f "$PARSER_SCRIPT" ]; then
+ echo -e "${RED}Error: Parser script not found: $PARSER_SCRIPT${NC}"
+ exit 1
+fi
+
+# Make parser executable
+chmod +x "$PARSER_SCRIPT"
+
+# Count Erlang files
+ERL_COUNT=$(find "$SRC_DIR" -name "*.erl" -type f | wc -l)
+if [ "$ERL_COUNT" -eq 0 ]; then
+ echo -e "${YELLOW}Warning: No .erl files found in $SRC_DIR${NC}"
+ exit 0
+fi
+
+echo "Source directory: $SRC_DIR"
+echo "Output directory: $OUTPUT_DIR"
+echo "Found $ERL_COUNT Erlang files"
+echo ""
+
+# Create output directory if it doesn't exist
+mkdir -p "$OUTPUT_DIR"
+
+# Run the JavaScript parser
+echo -e "${GREEN}Generating literate documentation...${NC}"
+
+# Set environment variables and run parser
+export SRC_DIR="$SRC_DIR"
+export OUTPUT_DIR="$OUTPUT_DIR"
+
+if [ "$VERBOSE" = true ]; then
+ node "$PARSER_SCRIPT" --verbose
+else
+ node "$PARSER_SCRIPT"
+fi
+
+PARSER_EXIT_CODE=$?
+
+if [ $PARSER_EXIT_CODE -eq 0 ]; then
+ echo ""
+ echo -e "${GREEN}â Literate documentation generated successfully${NC}"
+
+ # List generated files
+ if [ "$VERBOSE" = true ]; then
+ echo ""
+ echo "Generated files:"
+ ls -la "$OUTPUT_DIR"/*.md 2>/dev/null | while read -r line; do
+ echo " $line"
+ done
+ fi
+
+ # Copy to mdBook if it exists
+ if [ -d "$ROOT_DIR/docs/book/src" ]; then
+ if [ "$DRY_RUN" = true ]; then
+ echo -e "${YELLOW}[DRY RUN] Would copy documentation to mdBook...${NC}"
+ echo -e "${YELLOW}[DRY RUN] â Documentation would be copied to mdBook${NC}"
+ else
+ echo -e "${GREEN}Copying documentation to mdBook...${NC}"
+ cp "$OUTPUT_DIR"/*.md "$ROOT_DIR/docs/book/src/" 2>/dev/null
+ if [ $? -eq 0 ]; then
+ echo -e "${GREEN}â Documentation copied to mdBook${NC}"
+ else
+ echo -e "${YELLOW}Warning: Could not copy to mdBook (no files generated?)${NC}"
+ fi
+ fi
+ fi
+
+ # Build mdBook if available
+ if command -v mdbook &> /dev/null && [ -f "$ROOT_DIR/docs/book/book.toml" ]; then
+ if [ "$DRY_RUN" = true ]; then
+ echo -e "${YELLOW}[DRY RUN] Would build mdBook...${NC}"
+ echo -e "${YELLOW}[DRY RUN] â mdBook would be built successfully${NC}"
+ echo -e "${YELLOW}[DRY RUN] Would be viewable at: file://$ROOT_DIR/docs/book/dist/index.html${NC}"
+ else
+ echo -e "${GREEN}Building mdBook...${NC}"
+ cd "$ROOT_DIR/docs/book"
+ mdbook build
+ if [ $? -eq 0 ]; then
+ echo -e "${GREEN}â mdBook built successfully${NC}"
+ echo "View at: file://$ROOT_DIR/docs/book/dist/index.html"
+ else
+ echo -e "${YELLOW}Warning: mdBook build failed${NC}"
+ fi
+ cd "$ROOT_DIR"
+ fi
+ fi
+
+ # Run deployment dry run if requested
+ if [ "$DRY_RUN" = true ]; then
+ echo ""
+ echo -e "${YELLOW}Running deployment dry run...${NC}"
+ "$SCRIPT_DIR/deploy-dry-run.sh"
+ fi
+
+ echo ""
+ if [ "$DRY_RUN" = true ]; then
+ echo -e "${YELLOW}Documentation generation and deployment dry run complete!${NC}"
+ else
+ echo -e "${GREEN}Documentation generation complete!${NC}"
+ fi
+ echo "Output directory: $OUTPUT_DIR"
+
+else
+ echo ""
+ echo -e "${RED}â Documentation generation failed (exit code: $PARSER_EXIT_CODE)${NC}"
+ exit $PARSER_EXIT_CODE
+fi
\ No newline at end of file
diff --git a/docs/serve-book.sh b/docs/serve-book.sh
new file mode 100755
index 000000000..ef7ddaea6
--- /dev/null
+++ b/docs/serve-book.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+# HyperBEAM Documentation Build and Serve Script
+# This script generates literate documentation and serves it locally
+
+set -e
+
+# Colors for output
+GREEN='\033[0;32m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+echo -e "${BLUE}đ Building and serving HyperBEAM documentation...${NC}"
+
+# Generate literate documentation
+echo -e "${GREEN}đ Generating literate documentation...${NC}"
+./generate-literate-docs.sh
+
+# Build and serve mdBook
+echo -e "${GREEN}đ Building mdBook...${NC}"
+cd book
+mdbook build
+
+echo -e "${GREEN}đ Starting development server...${NC}"
+echo -e "${BLUE}đ Documentation will be available at: http://localhost:3033${NC}"
+echo -e "${BLUE}đ Auto-reload enabled - changes will be reflected automatically${NC}"
+echo -e "${BLUE}âšī¸ Press Ctrl+C to stop the server${NC}"
+
+mdbook serve --port 3033
\ No newline at end of file