88 lines
26 KiB
HTML
88 lines
26 KiB
HTML
<!doctype html><html lang=en dir=auto><head><script src="/livereload.js?mindelay=10&v=2&port=1313&path=livereload" data-no-instant defer></script><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name=robots content="index, follow"><title>Extension of P2PRC to create your own modification | Akilan</title>
|
||
<meta name=keywords content="P2PRC,AST,Extend"><meta name=description content="P2PRC as a abstraction"><meta name=author content="Akilan Selvacoumar"><link rel=canonical href=http://localhost:1313/technicalposts/p2prc-ast/><link crossorigin=anonymous href=/assets/css/stylesheet.f49d66caae9ea0fd43f21f29e71a8d3e284517ed770f2aa86fa012953ad3c9ef.css integrity="sha256-9J1myq6eoP1D8h8p5xqNPihFF+13Dyqob6ASlTrTye8=" rel="preload stylesheet" as=style><link rel=icon href=http://localhost:1313/favicon.ico><link rel=icon type=image/png sizes=16x16 href=http://localhost:1313/favicon-16x16.png><link rel=icon type=image/png sizes=32x32 href=http://localhost:1313/favicon-32x32.png><link rel=apple-touch-icon href=http://localhost:1313/apple-touch-icon.png><link rel=mask-icon href=http://localhost:1313/safari-pinned-tab.svg><meta name=theme-color content="#2e2e33"><meta name=msapplication-TileColor content="#2e2e33"><link rel=alternate hreflang=en href=http://localhost:1313/technicalposts/p2prc-ast/><noscript><style>#theme-toggle,.top-link{display:none}</style><style>@media(prefers-color-scheme:dark){:root{--theme:rgb(29, 30, 32);--entry:rgb(46, 46, 51);--primary:rgb(218, 218, 219);--secondary:rgb(155, 156, 157);--tertiary:rgb(65, 66, 68);--content:rgb(196, 196, 197);--code-block-bg:rgb(46, 46, 51);--code-bg:rgb(55, 56, 62);--border:rgb(51, 51, 51)}.list{background:var(--theme)}.list:not(.dark)::-webkit-scrollbar-track{background:0 0}.list:not(.dark)::-webkit-scrollbar-thumb{border-color:var(--theme)}}</style></noscript><meta property="og:url" content="http://localhost:1313/technicalposts/p2prc-ast/"><meta property="og:site_name" content="Akilan"><meta property="og:title" content="Extension of P2PRC to create your own modification"><meta property="og:description" content="P2PRC as a abstraction"><meta property="og:locale" content="en"><meta property="og:type" content="article"><meta property="article:section" content="technicalposts"><meta property="article:published_time" content="2021-08-31T00:00:00+00:00"><meta property="article:modified_time" content="2021-08-31T00:00:00+00:00"><meta property="article:tag" content="P2PRC"><meta property="article:tag" content="AST"><meta property="article:tag" content="Extend"><meta name=twitter:card content="summary"><meta name=twitter:title content="Extension of P2PRC to create your own modification"><meta name=twitter:description content="P2PRC as a abstraction"><script type=application/ld+json>{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Technical Posts","item":"http://localhost:1313/technicalposts/"},{"@type":"ListItem","position":2,"name":"Extension of P2PRC to create your own modification","item":"http://localhost:1313/technicalposts/p2prc-ast/"}]}</script><script type=application/ld+json>{"@context":"https://schema.org","@type":"BlogPosting","headline":"Extension of P2PRC to create your own modification","name":"Extension of P2PRC to create your own modification","description":"P2PRC as a abstraction","keywords":["P2PRC","AST","Extend"],"articleBody":"Generate Module P2PRC is a great layer of abstraction. This means that in many cases it is not an end product but rather a tool that customized as an end product. An example would be writing your own billing module to monetize the computation power available. The generate module copies the current with the appropriate git histories and keeps only the go files which would be useful to edit. To use the generate module the user will need to have a go compiler present in his computer. Due to the introduction of this module there will 2 releases:\nRegular Release (Consists of only the build binary and cli command cannot access the generate module) Developer Release (Consists of important Go files and the cli can access the generate module) How does this work ? Struct information ###Generate.go: This file creates a local copy of P2PRC from where the CLI was called from. This go file also does various stuff like instruction of file should be ignored when copying and which of should not be. Now let’s understand this. Below is a sample code which does the following: //---------------------------------------------------------------- // Action performed: // - Ensuring main.go file exists // - Skipping all .go files apart from the ones listed above // - Skipping .idea/ directory // - Skipping Makefile file //---------------------------------------------------------------- Options.Skip = func(src string) (bool, error) { switch { case strings.HasSuffix(src, \"main.go\"): return false, nil case strings.HasSuffix(src, \".go\"): return true, nil case strings.HasSuffix(src, \".idea\"): return true, nil case strings.HasSuffix(src, \"Makefile\"): return true, nil default: return false, nil } } // Doing the copy err = copy.Copy(\"\", \"\", Options) Unfortunately currently this will have to be manually edited in the Generate.go file. When using the generate module the user also creates their own Go module which is the modified version of P2PRC. This means if the 1 modified package is using another modified package then the appropriate import have to be modified in the file where the import is called:\nEx:\n//Sample Project module name = Test //Package names: //- Test/Genius //- Test/GeGeGenuis // // When we call the generate function with the new project with the module name = MicDrop // The new package name would be: // - MicDrop/Genius // - MicDrop/GeGeGenuis // Test/Genius code depends on the package Test/GeGeGenuis import ( \"Test/GeGeGenuis\" ) // When we create a new module with the copy of the // existing project we need change: import ( \"MicDrop/GeGeGenuis\" ) To do this we have built functions which can modify import names in the Go file provided. To customize the use case of your generate module you would need to manually add your own imports which are supposed to be replaced and in which files they are supposed to be replaced in.\n// 1.0 - Test/Genius.go -\u003e GeGeGenuis module // a is struct of type NewProject a.FileNameAST = \"/Test/Genius.go\" // Get AST information of the file err := a.GetASTGoFile() if err != nil { return err } // Change the appropriate Go file err = a.ChangeImports(\"Test/GeGeGenuis\", \"MicDrop/GeGeGenuis\") if err != nil { return err } // Writes the change to the appropriate file err = a.WriteGoAst() if err != nil { return err } Higher order of execution of Generate.go:\nCopy entire P2PRC project and ignores files which are not meant to be copied The folder name will be based on the new project name and the module name based on the new module name provided. Modifies the appropriate imports in the project as instructed in the code. Creates a commit with the new changes in the new project. modifyGenerate.go: This a really simple implementation where we replace the imports in certain files as instructed from generate.go. To do we create an AST (i.e Abstract Syntax tree) from new file we want to change the imports in. AST create a tree structure of expression. To change the import we can just traverse to the appropriate expression and change the value of that expression in the case of modifying imports. This approach is more simple than using templates. Cli command p2prc --gen --mod based on PR: https://github.com/Akilan1999/p2p-rendering-computation/pull/69 ","wordCount":"702","inLanguage":"en","datePublished":"2021-08-31T00:00:00Z","dateModified":"2021-08-31T00:00:00Z","author":{"@type":"Person","name":"Akilan Selvacoumar"},"mainEntityOfPage":{"@type":"WebPage","@id":"http://localhost:1313/technicalposts/p2prc-ast/"},"publisher":{"@type":"Organization","name":"Akilan","logo":{"@type":"ImageObject","url":"http://localhost:1313/favicon.ico"}}}</script></head><body id=top><script>localStorage.getItem("pref-theme")==="dark"?document.body.classList.add("dark"):localStorage.getItem("pref-theme")==="light"?document.body.classList.remove("dark"):window.matchMedia("(prefers-color-scheme: dark)").matches&&document.body.classList.add("dark")</script><header class=header><nav class=nav><div class=logo><a href=http://localhost:1313/ accesskey=h title="Akilan (Alt + H)">Akilan</a><div class=logo-switches><button id=theme-toggle accesskey=t title="(Alt + T)" aria-label="Toggle theme"><svg id="moon" width="24" height="18" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/></svg><svg id="sun" width="24" height="18" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg></button></div></div><ul id=menu><li><a href=http://localhost:1313/about/ title=About><span>About</span></a></li><li><a href=http://localhost:1313/resume/ title=Resumé><span>Resumé</span></a></li><li><a href=http://localhost:1313/projects/ title=Projects><span>Projects</span></a></li><li><a href=http://localhost:1313/technicalposts/ title="Technical Posts"><span>Technical Posts</span></a></li><li><a href=http://localhost:1313/generalposts/ title="General Posts"><span>General Posts</span></a></li><li><a href=http://localhost:1313/papers/ title=Papers><span>Papers</span></a></li><li><a href=http://localhost:1313/chat/ title=Chat><span>Chat</span></a></li><li><a href=http://localhost:1313/hiking/ title=Hiking><span>Hiking</span></a></li></ul></nav></header><main class=main><article class=post-single><header class=post-header><div class=breadcrumbs><a href=http://localhost:1313/>Home</a> » <a href=http://localhost:1313/technicalposts/>Technical Posts</a></div><h1 class="post-title entry-hint-parent">Extension of P2PRC to create your own modification</h1><div class=post-meta><span title='2021-08-31 00:00:00 +0000 UTC'>2021-31-08</span> · 4 min · Akilan Selvacoumar | <a href=https://github.com/Akilan1999/Akilan-website/tree/master/content/technicalposts/P2PRC-AST.md rel="noopener noreferrer" target=_blank>Suggest Changes</a></div></header><div class=toc><details><summary accesskey=c title="(Alt + C)"><span class=details>Table of Contents</span></summary><div class=inner><ul><li><a href=#generate-module aria-label="Generate Module">Generate Module</a><ul><li><a href=#how-does-this-work- aria-label="How does this work ?">How does this work ?</a><ul><li><a href=#struct-information aria-label="Struct information">Struct information</a></li><li><a href=#modifygeneratego aria-label=" modifyGenerate.go:">modifyGenerate.go:</a></li></ul></li><li><a href=#cli-command aria-label="Cli command">Cli command</a><ul><ul><li><a href=#based-on-pr aria-label="based on PR: https://github.com/Akilan1999/p2p-rendering-computation/pull/69">based on PR: https://github.com/Akilan1999/p2p-rendering-computation/pull/69</a></li></ul></li></ul></ul></li></ul></div></details></div><div class=post-content><h1 id=generate-module>Generate Module<a hidden class=anchor aria-hidden=true href=#generate-module>#</a></h1><p>P2PRC is a great layer of abstraction. This means that in many cases it is not an end product but rather
|
||
a tool that customized as an end product. An example would be writing your own billing module to monetize
|
||
the computation power available. The generate module copies the current with the appropriate git histories
|
||
and keeps only the go files which would be useful to edit. To use the generate module the user will need
|
||
to have a go compiler present in his computer. Due to the introduction of this module there will 2 releases:</p><ul><li>Regular Release (Consists of only the build binary and cli command cannot access the generate module)</li><li>Developer Release (Consists of important Go files and the cli can access the generate module)</li></ul><h2 id=how-does-this-work->How does this work ?<a hidden class=anchor aria-hidden=true href=#how-does-this-work->#</a></h2><h3 id=struct-information><a href=https://github.com/Akilan1999/p2p-rendering-computation/blob/9d69aed8ce0fe5273aaff2828f7d51c3d5ac2ce4/generate/generate.go#L19>Struct information</a><a hidden class=anchor aria-hidden=true href=#struct-information>#</a></h3><ul><li>###<code>Generate.go</code>:
|
||
This file creates a local copy of P2PRC from where the CLI was called from.
|
||
This go file also does various stuff like instruction of file should be ignored when copying and
|
||
which of should not be. Now let’s understand this. Below is a sample code which does the following:</li></ul><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-go data-lang=go><span style=display:flex><span> <span style=color:#75715e>//----------------------------------------------------------------</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>// Action performed:</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>// - Ensuring main.go file exists</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>// - Skipping all .go files apart from the ones listed above</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>// - Skipping .idea/ directory</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>// - Skipping Makefile file</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>//----------------------------------------------------------------</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#a6e22e>Options</span>.<span style=color:#a6e22e>Skip</span> = <span style=color:#66d9ef>func</span>(<span style=color:#a6e22e>src</span> <span style=color:#66d9ef>string</span>) (<span style=color:#66d9ef>bool</span>, <span style=color:#66d9ef>error</span>) {
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>switch</span> {
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>case</span> <span style=color:#a6e22e>strings</span>.<span style=color:#a6e22e>HasSuffix</span>(<span style=color:#a6e22e>src</span>, <span style=color:#e6db74>"main.go"</span>):
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>return</span> <span style=color:#66d9ef>false</span>, <span style=color:#66d9ef>nil</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>case</span> <span style=color:#a6e22e>strings</span>.<span style=color:#a6e22e>HasSuffix</span>(<span style=color:#a6e22e>src</span>, <span style=color:#e6db74>".go"</span>):
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>return</span> <span style=color:#66d9ef>true</span>, <span style=color:#66d9ef>nil</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>case</span> <span style=color:#a6e22e>strings</span>.<span style=color:#a6e22e>HasSuffix</span>(<span style=color:#a6e22e>src</span>, <span style=color:#e6db74>".idea"</span>):
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>return</span> <span style=color:#66d9ef>true</span>, <span style=color:#66d9ef>nil</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>case</span> <span style=color:#a6e22e>strings</span>.<span style=color:#a6e22e>HasSuffix</span>(<span style=color:#a6e22e>src</span>, <span style=color:#e6db74>"Makefile"</span>):
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>return</span> <span style=color:#66d9ef>true</span>, <span style=color:#66d9ef>nil</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>default</span>:
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>return</span> <span style=color:#66d9ef>false</span>, <span style=color:#66d9ef>nil</span>
|
||
</span></span><span style=display:flex><span> }
|
||
</span></span><span style=display:flex><span> }
|
||
</span></span><span style=display:flex><span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>// Doing the copy </span>
|
||
</span></span><span style=display:flex><span> <span style=color:#a6e22e>err</span> = <span style=color:#a6e22e>copy</span>.<span style=color:#a6e22e>Copy</span>(<span style=color:#e6db74>"<P2PRC folder you want to copy from>"</span>, <span style=color:#e6db74>"<PATH to the directory>"</span>, <span style=color:#a6e22e>Options</span>)
|
||
</span></span></code></pre></div><p>Unfortunately currently this will have to be manually edited in the <code>Generate.go</code> file. When using the generate
|
||
module the user also creates their own Go module which is the modified version of P2PRC. This means
|
||
if the 1 modified package is using another modified package then the appropriate import have to be modified
|
||
in the file where the import is called:</p><p>Ex:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-go data-lang=go><span style=display:flex><span> <span style=color:#75715e>//Sample Project module name = Test </span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>//Package names:</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>//- Test/Genius</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>//- Test/GeGeGenuis</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>//</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>// When we call the generate function with the new project with the module name = MicDrop </span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>// The new package name would be:</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>// - MicDrop/Genius </span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>// - MicDrop/GeGeGenuis</span>
|
||
</span></span><span style=display:flex><span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>// Test/Genius code depends on the package Test/GeGeGenuis</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#f92672>import</span> (
|
||
</span></span><span style=display:flex><span> <span style=color:#e6db74>"Test/GeGeGenuis"</span>
|
||
</span></span><span style=display:flex><span> )
|
||
</span></span><span style=display:flex><span>
|
||
</span></span><span style=display:flex><span><span style=color:#75715e>// When we create a new module with the copy of the </span>
|
||
</span></span><span style=display:flex><span><span style=color:#75715e>// existing project we need change:</span>
|
||
</span></span><span style=display:flex><span><span style=color:#f92672>import</span> (
|
||
</span></span><span style=display:flex><span> <span style=color:#e6db74>"MicDrop/GeGeGenuis"</span>
|
||
</span></span><span style=display:flex><span>)
|
||
</span></span></code></pre></div><p>To do this we have built functions which can modify import names in the Go file provided.
|
||
To customize the use case of your generate module you would need to manually add your own
|
||
imports which are supposed to be replaced and in which files they are supposed to be replaced
|
||
in.</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-go data-lang=go><span style=display:flex><span><span style=color:#75715e>// 1.0 - Test/Genius.go -> GeGeGenuis module</span>
|
||
</span></span><span style=display:flex><span><span style=color:#75715e>// a is struct of type NewProject </span>
|
||
</span></span><span style=display:flex><span> <span style=color:#a6e22e>a</span>.<span style=color:#a6e22e>FileNameAST</span> = <span style=color:#e6db74>"<path to project to copy from>/Test/Genius.go"</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>// Get AST information of the file</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#a6e22e>err</span> <span style=color:#f92672>:=</span> <span style=color:#a6e22e>a</span>.<span style=color:#a6e22e>GetASTGoFile</span>()
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>if</span> <span style=color:#a6e22e>err</span> <span style=color:#f92672>!=</span> <span style=color:#66d9ef>nil</span> {
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>return</span> <span style=color:#a6e22e>err</span>
|
||
</span></span><span style=display:flex><span> }
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>// Change the appropriate Go file</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#a6e22e>err</span> = <span style=color:#a6e22e>a</span>.<span style=color:#a6e22e>ChangeImports</span>(<span style=color:#e6db74>"Test/GeGeGenuis"</span>, <span style=color:#e6db74>"MicDrop/GeGeGenuis"</span>)
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>if</span> <span style=color:#a6e22e>err</span> <span style=color:#f92672>!=</span> <span style=color:#66d9ef>nil</span> {
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>return</span> <span style=color:#a6e22e>err</span>
|
||
</span></span><span style=display:flex><span> }
|
||
</span></span><span style=display:flex><span> <span style=color:#75715e>// Writes the change to the appropriate file</span>
|
||
</span></span><span style=display:flex><span> <span style=color:#a6e22e>err</span> = <span style=color:#a6e22e>a</span>.<span style=color:#a6e22e>WriteGoAst</span>()
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>if</span> <span style=color:#a6e22e>err</span> <span style=color:#f92672>!=</span> <span style=color:#66d9ef>nil</span> {
|
||
</span></span><span style=display:flex><span> <span style=color:#66d9ef>return</span> <span style=color:#a6e22e>err</span>
|
||
</span></span><span style=display:flex><span> }
|
||
</span></span></code></pre></div><p>Higher order of execution of <code>Generate.go</code>:</p><ol><li>Copy entire P2PRC project and ignores files which are not meant to be copied</li><li>The folder name will be based on the new project name and the module name based on the new
|
||
module name provided.</li><li>Modifies the appropriate imports in the project as instructed in the code.</li><li>Creates a commit with the new changes in the new project.</li></ol><ul><li><h3 id=modifygeneratego><code>modifyGenerate.go</code>:<a hidden class=anchor aria-hidden=true href=#modifygeneratego>#</a></h3>This a really simple implementation where we replace the imports
|
||
in certain files as instructed from <code>generate.go</code>. To do we create an AST (i.e Abstract Syntax tree)
|
||
from new file we want to change the imports in. AST create a tree structure of expression. To change the
|
||
import we can just traverse to the appropriate expression and change the value of that expression in
|
||
the case of modifying imports. This approach is more simple than using templates.</li></ul><h2 id=cli-command>Cli command<a hidden class=anchor aria-hidden=true href=#cli-command>#</a></h2><pre tabindex=0><code>p2prc --gen <project name> --mod <go module name>
|
||
</code></pre><h4 id=based-on-pr>based on PR: <a href=https://github.com/Akilan1999/p2p-rendering-computation/pull/69>https://github.com/Akilan1999/p2p-rendering-computation/pull/69</a><a hidden class=anchor aria-hidden=true href=#based-on-pr>#</a></h4></div><footer class=post-footer><ul class=post-tags><li><a href=http://localhost:1313/tags/p2prc/>P2PRC</a></li><li><a href=http://localhost:1313/tags/ast/>AST</a></li><li><a href=http://localhost:1313/tags/extend/>Extend</a></li></ul><nav class=paginav><a class=prev href=http://localhost:1313/technicalposts/p2prc-automatic-ports/><span class=title>« Prev</span><br><span>Automatic ports allocation P2PRC</span>
|
||
</a><a class=next href=http://localhost:1313/technicalposts/p2prc-plugin-module-implementation/><span class=title>Next »</span><br><span>P2PRC Plugin module implementation</span></a></nav></footer></article></main><footer class=footer><span>© 2025 <a href=http://localhost:1313/>Akilan</a></span> ·
|
||
<span>Powered by
|
||
<a href=https://gohugo.io/ rel="noopener noreferrer" target=_blank>Hugo</a> &
|
||
<a href=https://github.com/adityatelange/hugo-PaperMod/ rel=noopener target=_blank>PaperMod</a></span></footer><a href=#top aria-label="go to top" title="Go to Top (Alt + G)" class=top-link id=top-link accesskey=g><svg viewBox="0 0 12 6" fill="currentcolor"><path d="M12 6H0l6-6z"/></svg>
|
||
</a><script>let menu=document.getElementById("menu");menu&&(menu.scrollLeft=localStorage.getItem("menu-scroll-position"),menu.onscroll=function(){localStorage.setItem("menu-scroll-position",menu.scrollLeft)}),document.querySelectorAll('a[href^="#"]').forEach(e=>{e.addEventListener("click",function(e){e.preventDefault();var t=this.getAttribute("href").substr(1);window.matchMedia("(prefers-reduced-motion: reduce)").matches?document.querySelector(`[id='${decodeURIComponent(t)}']`).scrollIntoView():document.querySelector(`[id='${decodeURIComponent(t)}']`).scrollIntoView({behavior:"smooth"}),t==="top"?history.replaceState(null,null," "):history.pushState(null,null,`#${t}`)})})</script><script>var mybutton=document.getElementById("top-link");window.onscroll=function(){document.body.scrollTop>800||document.documentElement.scrollTop>800?(mybutton.style.visibility="visible",mybutton.style.opacity="1"):(mybutton.style.visibility="hidden",mybutton.style.opacity="0")}</script><script>document.getElementById("theme-toggle").addEventListener("click",()=>{document.body.className.includes("dark")?(document.body.classList.remove("dark"),localStorage.setItem("pref-theme","light")):(document.body.classList.add("dark"),localStorage.setItem("pref-theme","dark"))})</script></body></html> |