#blog ![|700](Writing%20Code%20In%20A%20Decentralized%20Way.png) > [!quote] A programmer should program the way s/he programs. For now I am doing almost all work in my Obsidian vault, which is named [Markcode](https://github.com/shuxueshuxue/Markcode-engine). It's a knowledge base, a research engine, also an IDE capable of full stack software engineering. Mostly importantly, it develops itself. In this blog I will cover its essence: *writing code in a decentralized way*. ## The Folder Structure ``` Markcode - Notes - Modules - Scripts - Others - Template ``` ## Block Based Programming ### From Markdown to Code Files The system binds .md files with the code files extracted from code blocks in it. For example, if you both Python and Rust implementations (as two code blocks) in the note file `euler_method.md` , then when running the code execution command, `euler_method.py`, `euler_method.rs` will both be created in the same folder. There are some simple refactor mechanism - when you rename the .md file, relevant code files will be automatically renamed. And orphans will be automatically deleted. ### Markers to Direct Execution Flow Markcode employs specific markers that guide the execution flow within the vault. These markers act as directives, instructing the system on how to handle and execute the enclosed code blocks. For example, the following marker is used to tell the code executor to only extract code blocks into code files without actually running the code. `<!-- nr -->` If you want to ignore other code blocks and run Haskell only, use this combination of markers. `<!-- nr --><!-- run haskell -->` By default, all code blocks in the vault are extracted, combined sequentially and executed together. If you want to run only some specific blocks, simply comment the first line with `#!` (The symbol to comment is dependent on the language) ### Everything Can Be Templated This is powered by the `Templater` plugin. To accelerate development and maintain consistency, Markcode incorporates templates that can be invoked across various environments. These templates act as blueprints, providing standardized structures for new modules, scripts, or components. Here's typical Template for C++ programming. ```js tR += ` \`\`\`cpp #include <iostream> extern "C" { int main(); } int main() { std::cout << "Hello!" << std::endl; return 0; } \`\`\` \`\`\`python import runcpp if __name__ == "__main__": lib = runcpp.load_dll("${tp.file.title}.dll") lib.main() \`\`\` ` ``` ## Test Flow Module Testing is the bedrock of robust software development. The Test Flow Module in Markcode automates the testing pipeline. Here's an example to test rust code with a *push* script (the first block) to interact with the local project files. ```push fold D:\Codebase\rustplay\src\main.rs ``` ```python fold import testflow testflow.rustplay() ``` ```rust fn main() { println!("HW!"); } ``` ## A Note Can Be an App In the traditional paradigm, applications are monolithic constructs with tightly coupled components. Markcode disrupts this notion by transforming individual notes within the Obsidian vault into self-contained applications. Each note encapsulates its functionality, dependencies, and execution logic, allowing for a highly modular and scalable development process. Here's a LLM chat App written this way. It supports multi-round conversation, note referencing and web browsing. It reads the Question and writes the output. ```markdown # Question # Response ---- ``` ```python fold #! import requests import json import myapikeys import fileUtils from iohelper import * API_KEY = myapikeys.anthropic def call_claude_api(prompt): url = "https://api.anthropic.com/v1/messages" headers = { "Content-Type": "application/json", "X-Api-Key": API_KEY, "anthropic-version": "2023-06-01", } data = { "model": "claude-3-5-sonnet-20240620", "max_tokens": 3000, "messages": [{"role": "user", "content": prompt}] } response = requests.post(url, headers=headers, json=data) if response.status_code == 200: return response.json()['content'][0]['text'] else: return f"Error: {response.status_code}, {response.text}" def main(): user_input = inputh("Question") clearh("Response") printh("Thinking...", "Response") # Replace links with file content processed_input = fileUtils.replace_links_with_content(user_input) if "<!-- web -->" in user_input: import webCrawler processed_input = webCrawler.replace_links_with_content(user_input) # printh(processed_input) response = call_claude_api(processed_input) clearh("Response") printh(response, "Response") chat_history = processed_input + "\n\n**Claude:**\n" + response + "\n\n---\n" with open("chat_history_temp.md", "w", encoding="utf-8") as f: f.write(chat_history) if __name__ == "__main__": main() ``` ## Implementing the Modules ### Obsidian Server The `obsidian.md ` module is the linchpin that interacts with an Obsidian server, ensuring that the vault is initialized correctly and that the server remains responsive. It encapsulates functions to run JavaScript code within the Obsidian environment, manage file operations, and handle user interactions. Here's a brief view of it. ```python # vaultBuilding.md Module import requests import os import time note_path = os.environ.get('MD_FILE') obs_vault = os.environ.get('OBS_VAULT') if note_path: note_title = os.path.splitext(os.path.basename(note_path))[0] note_directory = os.path.dirname(note_path) def initialize(max_attempts=30, delay=0.5): """ Wait for the server to launch and become responsive. :param max_attempts: Maximum number of attempts to connect to the server :param delay: Delay in seconds between each attempt :return: True if server is responsive, False otherwise """ for attempt in range(max_attempts): try: response = runjs("return 200;") if response == 200: return True except requests.RequestException: pass time.sleep(delay) print(f"Server did not become responsive after {max_attempts} attempts.") return False def runjs(js_code, vault_path=obs_vault, server_url='http://localhost:3300'): code_file = os.path.join(vault_path, 'code.js') # Write the JavaScript code to the file with open(code_file, 'w', encoding="utf-8") as f: f.write(js_code) # Send the request to the server response = requests.post(f'{server_url}/run') if response.status_code == 200: try: return response.json() except Exception: return "" else: raise Exception(f"Error: {response.status_code} - {response.text}") # Additional functions omitted for brevity ``` This module not only ensures that the Obsidian server is up and running but also provides utility functions to execute JavaScript code, interact with the file system, and facilitate user interactions within the vault. ### IO Helper Module The `iohelper.md` module serves as the intermediary between Python scripts and the Obsidian environment. It provides functions to manipulate markdown files, prompt user inputs, display notifications, and handle cursor interactions within the editor. ```python # iohelper.md Module import sys import os import obsidian import time note_path = os.environ.get('MD_FILE') obs_vault = os.environ.get('OBS_VAULT') note_title = os.path.splitext(os.path.basename(note_path))[0] note_directory = os.path.dirname(note_path) def printh(content: str, title_name: str = "", output_file: str = ""): # Function implementation... pass def inputh(title_name: str) -> str: # Function implementation... pass def clearh(title_name: str = "", target_file=None): # Function implementation... pass def input_prompt(prompt_text: str) -> str: # Function implementation... pass def notice(text: str): # Function implementation... pass def get_selection(): # Function implementation... pass def append_cursor(text: str): # Function implementation... pass def get_cursor_line(): # Function implementation... pass # Optional functions def refreshh(title_name, refresh_function): # Function implementation... pass ``` ## Modularizing Other Languages Here's the `here.md` module that implements IO operation for Haskell. ```haskell -- Here.md Module module Here ( Showable(..) , appendHere , getOutputFilename ) where import System.FilePath (takeBaseName, replaceExtension) import System.Environment (getProgName) class Showable a where toString :: a -> String instance Showable String where toString = id instance Showable Int where toString = show instance Showable Integer where toString = show instance Showable Double where toString = show instance Showable Bool where toString = show -- Add more instances as needed appendHere :: Showable a => a -> IO FilePath appendHere obj = do outputFile <- getOutputFilename appendFile outputFile ("\n" ++ toString obj) return outputFile getOutputFilename :: IO FilePath getOutputFilename = do mainScriptName <- getProgName let baseName = takeBaseName mainScriptName return $ replaceExtension baseName ".md" main :: IO () main = do putStrLn "Successfully extracted." ``` In this module, the `Showable` typeclass and its instances facilitate the conversion of various data types to strings, enabling seamless integration with markdown outputs. Functions like `appendHere` and `getOutputFilename` provide utility methods to manage file operations, ensuring that each module remains focused and maintainable. ## Being Self-contained - Bootstrapping The system is written by itself, and thus can improve itself iteratively. - Literal Programming As a form of literal programming, each module documents itself by simply putting markdown content between code blocks. # More Lots of things are not covered in this blog but also crucial for this vault: Version control system, AI integration, scripts to interact with the internet and local file system, project management system, academic resource management, code highlight, reference tracing, vim editor... #softwareEngineering #decentralization #Obsidian #Markcode #programming #modulardevelopment