[{"content":"A New Project I play lots of video games, mostly on the Switch. And this is mostly a programming blog so I must like that too. I should especially like combining these two.\nThe obvious overlap is programming a game. I\u0026rsquo;ve made a couple small toy games:\nSnake Hangman In both cases though, the game just happened to be the type of application I was writing. For Snake, it was my first GUI-from-scratch project and Tkinter graphics were the focus. For Hangman, it was my first non-trivial TUI and TUIs in general (plus the framework I used, bubbletea,) were the focus.\nI decided it was time for a project where the game itself was the focus. Something more complex than my previous projects but not too complex ;) and I am not a creative person, so copying an existing game felt like the best route. I leafed through some old arcade games and existing projects on GitHub. I almost did Space Invaders, then started liking the idea of Pac-Man, then noticed the lack of love for Ms. Pac-Man in hobby ports. Behind every great man is an even greater woman so\u0026hellip;\nMy, My, Ms. Pac-Man Having landed on a palatable project idea (not all are!), I needed to figure out my high-level approach. What tools? What would my Ms. Pac-Man game look and feel like?\nLanguage: Go, my new language love Most games are built using a Game Engine. My quick trade study showed ebiten to be a solid choice for Go. Look: I am no artist. I tried drawing Pacman in a pixel editor one night and disliked the process and what I made. I could programmatically draw things but that\u0026rsquo;s a lot of work too. It\u0026rsquo;s an old enough game. Could I just use the original assets? Sound: Yes, there should be sound. Behavior: Let\u0026rsquo;s reproduce, as faithfully as possible, the original game logic. So, I\u0026rsquo;ll be setting out to make as close a clone to the original Ms. Pac-Man as I can. Along the way, I plan to make tons of errors. I\u0026rsquo;ll probably do everything the hard way until I discover the right approach to use. There will be things I will be stuck on for weeks that a high-level game engine like Godot or Unity makes trivial. Such is the journey.\nThe First Steps: Research Blank IDE in front of me, it was time to take the first step\u0026hellip;\nWhich was close the IDE and open a web browser because I needed more materials before I got started, like:\nOriginal art assets, including sprites and levels Original sound effects Original game logic Everyone in the Pac-Man family is really popular so it was easy to find stuff:\nThis sprite sheet contains every graphic used. The zip file hosted here contains all sounds effects as separate WAV files.1 Extensive articles on game logic2: The Pac-Man Dossier Understanding Pac-Man Ghost Behavior This was enough fruit to get juicing with.\nThe First Squeeze: Representing a Level Back in front of the IDE, it seemed to me the first thing I should try to do is draw a level. This would introduce the basics of the game engine and get something satisfying on the screen. It was at this point I also started thinking about how the player sprite would move and interact with the level. This all led me to settle on a tile-based approach.\nI need tiles of the levels but all I had was one big PNG file. I also need individual sprites dissected from this sheet.\nSlicing and Dicing I started with the easier operation: splitting the sprites into individual PNG files. I used Krita, an open-source graphics editor, to cut all characters into a separate PNG:\nI then wrote a script with ChatGPT that would take this image and split it even further. It starts at the top left corner and, knowing that each sprite is the same size (32x32 pixels in this case), it uses ImageMagick\u0026rsquo;s convert command and math(!) to walk the file sprite-by-sprite, row-by-row, and slice them into separate files. The files the script creates need to be named something so I made a list of names that are chosen as the chunks are split. For example, the 4th sprite in the picture above is a cherry, so \u0026ldquo;cherry\u0026rdquo; is the 4th name in the list.\nA very similar approach soon had the first level in 16x16 pixel tile pieces. So this:\nWas broken down into pieces like this:\nWall Left, Wall Upper Left Corner: Inner, and Pellet tile The careful reader will note the sprites are twice the size of the tiles for the level. Another note on size: the original PNG tile sheet used 8x8 pixel tiles for the level but that size was comically small on modern HD displays so I doubled the PNG resolution before doing all this.\nPutting the Pieces Back Together Now that I had a bunch of tiles, I needed to load them in the game engine and stich them back together in the correct order to make a coherent level. I do this by \u0026ldquo;drawing\u0026rdquo; the level in Unicode in a text file. By taking advantage of the vertical symmetry of the level, I only need to \u0026ldquo;draw\u0026rdquo; half the level and can use maths to render the rest.\nFor example, here\u0026rsquo;s the Unicode representing most of the top left quadrant:\n⌜------⎤⎡----- ⎸••••••][••••• ⎸⦾⌌⎽⎽⌍•][•⌌⎽⎽⎽ ⎸•⌎⎺⎺⌏•⌎⌏•⌎⎺⎺⎺ ⎸••••••••••••• ⌞_⌍•⌌⌍•⌌⎽⎽⎽⌍•⌌ ⎸•][•]▓▓▓[•] --⌏•][•⌎⎺⎺⎺⌏•] •][•••••••] __⌍•]⎩⎽⎽⌍ ⌌⎽⎽⎭ ⎸•⌎⎺⎺⎺⌏ ⌎⎺⎺⎺ ⎸• ⎸•⌌⎽⎽⎽⌍ 「_\u0026gt;* ⎸•]⎧⎺⎺⌏ ⎹ In my asset loading code, I maintain a mapping of Unicode symbols to file name:\nvar tileLookup = map[rune]string{ \u0026#39;⌜\u0026#39;: \u0026#34;W_ULcorner_thin\u0026#34;, \u0026#39;⌞\u0026#39;: \u0026#34;W_DLcorner_thin\u0026#34;, \u0026#39;-\u0026#39;: \u0026#34;W_U\u0026#34;, \u0026#39;_\u0026#39;: \u0026#34;W_D\u0026#34;, \u0026#39;⎡\u0026#39;: \u0026#34;W_ULcorner_thick\u0026#34;, \u0026#39;⎤\u0026#39;: \u0026#34;W_URcorner_thick\u0026#34;, \u0026#39;⎸\u0026#39;: \u0026#34;W_L\u0026#34;, \u0026#39;⎹\u0026#39;: \u0026#34;W_R\u0026#34;, \u0026#39;•\u0026#39;: \u0026#34;pellet\u0026#34;, It\u0026rsquo;s useful for the game to know what type of tile it may be dealing with because a lot of decisions can be made by asking the question \u0026ldquo;Is this tile a wall?\u0026rdquo;. My naming scheme for level tile files let me do something cheesy where I set the tile type by checking the text in the file name:\nswitch tileName { case \u0026#34;blank\u0026#34;: tileType = TileTypeBlank case \u0026#34;gate\u0026#34;: tileType = TileTypeGate case \u0026#34;pellet\u0026#34;: tileType = TileTypePellet case \u0026#34;power_pellet\u0026#34;: tileType = TileTypePowerPellet default: tileType = TileTypeWall } To summarize, in order to load a level from tiles, the algorithm does this:\nLoad from disk the Unicode text file that defines the level layout. For each Unicode character in the layout file, use the Unicode name map to load the appropriate tile Draw the tile file at the correct location, determined by the order the tile is loaded Mirror the tile across the Y-axis and draw it again Give the final image to Ebiten to draw And here\u0026rsquo;s the final result of that operation:\nThe game engine code for all this is tiny (for now):\n// This function starts the game loop func main() { ebiten.SetWindowSize(game.Config.Width, game.Config.Height) ebiten.SetWindowTitle(\u0026#34;Ms. Pacman\u0026#34;) // Load all the tiles from disk and stich together the first level. // This is where the algorithm I describe above is implemented. img, err := assets.CreateLevelImage(\u0026#34;level1\u0026#34;) if err != nil { panic(err) } if err := ebiten.RunGame(\u0026amp;game.Game{CurrentLevelImage: img}); err != nil { log.Fatal(err) } } // This is then called every frame and draws the level image func (g *Game) Draw(screen *ebiten.Image) { screen.DrawImage(g.CurrentLevelImage, \u0026amp;ebiten.DrawImageOptions{}) } And that\u0026rsquo;s enough progress for this post! Next time, I want to get Ms. Pac-Man on the screen and maybe move her around a bit.\nCheck out this sweet tune from the intro:\nThey Meet Act 1 \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nYes, these are both Pac-Man references, not Ms. Pac-Man. As far as my research can tell, they implement identical game logic.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://braheezy.github.io/posts/ms-pacman-dev-log-1/","summary":"A New Project I play lots of video games, mostly on the Switch. And this is mostly a programming blog so I must like that too. I should especially like combining these two.\nThe obvious overlap is programming a game. I\u0026rsquo;ve made a couple small toy games:\nSnake Hangman In both cases though, the game just happened to be the type of application I was writing. For Snake, it was my first GUI-from-scratch project and Tkinter graphics were the focus.","title":"Ms. Pac-Man: Devlog 1"},{"content":"The Best MP3 Encoder is LAME When it comes to MP3 encoding, there is one de-facto leader in the open-source industry: LAME. It\u0026rsquo;s been given the attention of software and audio engineers for years which shows in the extensive features it comes with. If you want to encode audio data to MP3 with professional performance and quality, you either use LAME or something built on top of it.\nIf you\u0026rsquo;re a programmer and want to encode audio to MP3, you likely search your language\u0026rsquo;s ecosystem to piggyback off someone else\u0026rsquo;s work. Let\u0026rsquo;s look at the state of MP3 encoding in some languages and tools:\nThe lauded FFmpeg: ffmpeg is the ubiquitous program for processing video and audio. If you\u0026rsquo;ve ever watched a YouTube video, you consumed something that ffmpeg touched. For MP3 encoding, it uses LAME underneath the hood Python: LAME bindings: This provides a Python API to the libmp3lame C library. So you need libmp3lame installed for this to work. PyAudio Tools but it still requires libmp3lame to be for MP3 encoding, specifically. Rust: LAME bindings, but Rust flavored. Java: Nice! A native port1 of LAME. Go: More LAME bindings. JavaScript: Another port!\u0026hellip;well, it\u0026rsquo;s a port of the jump3r-code Java port of LAME. It\u0026rsquo;s LAME all the way down.\nDuring my play with the Quite OK Audio (QOA) format, I had tons of fun converting QOA files, something that no tool or library knows about, into various audio formats. That included covering .qoa files to .mp3.\nThat\u0026rsquo;s when I met LAME for the first time, one dark, stormy night. The go-lame project introduced us and, like so many projects above, provides Go language bindings to LAME. With all my Go projects, I like to support Linux, Mac, and Windows platforms, but go-lame doesn\u0026rsquo;t explicitly state it will support Windows platforms. It was up to me! And maybe ChatGPT.\nNo matter what I tried with C cross compilers, native Windows environments, and the slew of magic-build-everything-containers out there claiming to have all the tools bundled, I couldn\u0026rsquo;t get go-lame to build for Windows.\nExasperated, I pushed out with development on my QOA program by not supporting MP3 on Windows. But this left a bad taste in my mouth\u0026hellip;if only I had a Go library that didn\u0026rsquo;t rely on LAME. Maybe I should write one?2\nWhere The Encoding At? Full of hubris, I started researching deeper into MP3 theory. How difficult could this be?\nNarrator: It\u0026rsquo;s very difficult.\nThere\u0026rsquo;s no official MPEG-1 standard for how the encoder should behave. There are only official specifications of the file format and how an encoded MP3 bitstream should be structured. This lets decoders all behave the same but how an encoder creates that bitstream is up to the author. This created a wide variety in quality of 90s-00s encoding implementations. The ISO documents describing the nitty-gritty technical details one might use to implement an encoder are tucked behind paywalls. The practice of hiding and gating knowledge is offensive and stifles interest and innovation. The basic encoding/decoding technology for MP3 was under a patent for all of the 90s and 00s. It wasn\u0026rsquo;t patent-free in the US until 2017. Open-source and hobbyist developers like myself are going to avoid anything involving patents. The very algorithms that MP3 uses are not for the faint of heart. Check out this great diagram from a PDF I found: I\u0026rsquo;ll draw your attention to one block: the psychoacoustic model. Beyond just being a really cool word, it means the encoding algorithm will analyze the characteristics of the audio wave and throw out parts of the sound that humans can\u0026rsquo;t hear that well anyway. As a simple example, humans can only hear frequencies between 20 Hz and 20 kHz, so throw away any frequencies above and below those limits.\nIf you want more details on the MP3 encoding algorithm, this PDF is the best introduction I found.\nAccepting that I won\u0026rsquo;t be writing a new MP3 implementation from scratch, I next considered porting the C LAME library to Go. I opened a shell to check if that was reasonable and ran this cloc command that counts the lines of code in a project:\n$ cloc . 62 text files. 62 unique files. 11 files ignored. github.com/AlDanial/cloc v 1.90 T=0.04 s (1381.9 files/s, 735490.1 lines/s) ------------------------------------------------------------------------------ Language files blank comment code ------------------------------------------------------------------------------ C 21 3130 3427 16610 C/C++ Header 24 483 726 1670 Bourne Shell 1 64 224 503 make 3 47 14 164 Windows Resource File 1 3 1 46 IDL 1 1 0 31 ------------------------------------------------------------------------------ SUM: 51 3728 4392 19024 ------------------------------------------------------------------------------ 16k lines of obscure C, riddled with pointer magic and arcane memory manipulations? No thanks!\nCan Other MP3 Encoders Shine? I had to remember I was only here on a side quest to resolve a bug in my QOA program. MP3 is no one special. I needed a quicker path to success. I don\u0026rsquo;t need no fancy MP3 encoder.\nLAME is nice enough to mention alternative encoders, bragging when they can about their superior implementation (and they should!). One entry on the list caught my eye:\nShine is a featureless, but clean and readable MP3 encoder by Gabriel Bouvigne of LAME fame. Great as a starting point or learning tool. Also probably the only open source, fixed point math MP3 encoder.\nThis sounds exactly what I\u0026rsquo;m looking for:\nOpen source Featureless i.e. not overly complex Readable Great as a starting point A quick internet search led me to the apparent modern form of Shine. With FAR less code to reason about, I had found an MP3 encoder to port.\nPoof: Turning C to Go This took my two serious attempts. Which was unfortunate but it did give me a better understanding of the program as a whole =]\nIn my first attempt, I sat down file by file and wrote each C function to it\u0026rsquo;s Go counterpart. I\u0026rsquo;ll admit it, a lot of it I just threw at ChatGPT and asked it to convert. Here\u0026rsquo;s an example exchange:\nEventually, I had something that would compile\u0026hellip;but the MP3 files it made were 5x larger than reference MP3 files I was comparing against. Audio players refused to play them, which told me they were not even the right format. I troubleshot for a few days but based on my output files, I had a feeling I was way off-base. In despair, I searched the Internet for C to Go help\u0026hellip;\nAnd found cxgo! It claims to transpile C to Go. Despite it\u0026rsquo;s experimental warnings, it offered a glimmer of hope and I quickly downloaded it. I tested it on a small C file and the result looked good. I threw it at the whole Shine library and to my surprise, there were no errors! I had a full Go program in seconds (this is where the sting of failing the first attempt hit).\nHere\u0026rsquo;s an example. This C code:\n/* * shine_putbits: * -------- * write N bits into the bit stream. * bs = bit stream structure * val = value to write into the buffer * N = number of bits of val */ void shine_putbits(bitstream_t *bs, unsigned int val, unsigned int N) { #ifdef DEBUG if (N \u0026gt; 32) printf(\u0026#34;Cannot write more than 32 bits at a time.\\n\u0026#34;); if (N \u0026lt; 32 \u0026amp;\u0026amp; (val \u0026gt;\u0026gt; N) != 0) printf(\u0026#34;Upper bits (higher than %d) are not all zeros.\\n\u0026#34;, N); #endif if (bs-\u0026gt;cache_bits \u0026gt; N) { bs-\u0026gt;cache_bits -= N; bs-\u0026gt;cache |= val \u0026lt;\u0026lt; bs-\u0026gt;cache_bits; } else { if (bs-\u0026gt;data_position + sizeof(unsigned int) \u0026gt;= bs-\u0026gt;data_size) { bs-\u0026gt;data = (unsigned char *)realloc(bs-\u0026gt;data, bs-\u0026gt;data_size + (bs-\u0026gt;data_size / 2)); bs-\u0026gt;data_size += (bs-\u0026gt;data_size / 2); } N -= bs-\u0026gt;cache_bits; unsigned int shift = val \u0026gt;\u0026gt; N; bs-\u0026gt;cache |= shift; #ifdef SHINE_BIG_ENDIAN *(unsigned int *)(bs-\u0026gt;data + bs-\u0026gt;data_position) = bs-\u0026gt;cache; #else *(unsigned int *)(bs-\u0026gt;data + bs-\u0026gt;data_position) = SWAB32(bs-\u0026gt;cache); #endif bs-\u0026gt;data_position += sizeof(unsigned int); bs-\u0026gt;cache_bits = 32 - N; if (N != 0) bs-\u0026gt;cache = val \u0026lt;\u0026lt; bs-\u0026gt;cache_bits; else bs-\u0026gt;cache = 0; } } Becomes this Go code:\nimport ( \u0026#34;github.com/gotranspile/cxgo/runtime/libc\u0026#34; \u0026#34;unsafe\u0026#34; ) func shine_putbits(bs *bitstream_t, val uint64, N uint64) { if uint64(bs.Cache_bits) \u0026gt; N { bs.Cache_bits -= int64(N) bs.Cache |= val \u0026lt;\u0026lt; uint64(bs.Cache_bits) } else { if bs.Data_position+int64(unsafe.Sizeof(uint64(0))) \u0026gt;= bs.Data_size { bs.Data = (*uint8)(libc.Realloc(unsafe.Pointer(bs.Data), int(bs.Data_size+bs.Data_size/2))) bs.Data_size += bs.Data_size / 2 } N -= uint64(bs.Cache_bits) bs.Cache |= val \u0026gt;\u0026gt; N *(*uint64)(unsafe.Pointer((*uint8)(unsafe.Add(unsafe.Pointer(bs.Data), bs.Data_position)))) = (bs.Cache \u0026gt;\u0026gt; 24) | ((bs.Cache \u0026gt;\u0026gt; 8) \u0026amp; 0xFF00) | (bs.Cache\u0026amp;0xFF00)\u0026lt;\u0026lt;8 | bs.Cache\u0026lt;\u0026lt;24 bs.Data_position += int64(unsafe.Sizeof(uint64(0))) bs.Cache_bits = int64(32 - N) if N != 0 { bs.Cache = val \u0026lt;\u0026lt; uint64(bs.Cache_bits) } else { bs.Cache = 0 } } } Sure, we\u0026rsquo;ve got this libc import now and I was trying to avoid C, but it works. After more tweaks, I again arrived at a compilable program. This time it produced MP3 files that were byte-for-byte identical to Shine-produced MP3s. Success!! 🎉\nGraduating to a Pure Go Library The last task was to hunt through the code and remove every use of cxgo/runtime/libc. As a bonus, I\u0026rsquo;d also try to remove the unsafe package because safe is better than unsafe, right?\nPruning libc ended up being easy work of replacing mathematical functions with their appropriate math counterpart. Making the library safe proved more challenging. As of this writing, there is an open issue on the library to remove the last usage of the unsafe package. There\u0026rsquo;s only one more. Can you figure it out?\nThere\u0026rsquo;s no C left so mission accomplished! Now Windows folks can encode MP3 files in pure Go. Yay?\nFor non-programmers, a port is software written in a different language.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nYes I know there are quicker ways to accomplish this than writing a whole library. Get off my lawn!\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://braheezy.github.io/posts/what-i-learned-about-mp3-encoding/","summary":"The Best MP3 Encoder is LAME When it comes to MP3 encoding, there is one de-facto leader in the open-source industry: LAME. It\u0026rsquo;s been given the attention of software and audio engineers for years which shows in the extensive features it comes with. If you want to encode audio data to MP3 with professional performance and quality, you either use LAME or something built on top of it.\nIf you\u0026rsquo;re a programmer and want to encode audio to MP3, you likely search your language\u0026rsquo;s ecosystem to piggyback off someone else\u0026rsquo;s work.","title":"What I Learned About MP3 Encoding"},{"content":"A Cloudy Challenge I can\u0026rsquo;t remember where I originally found the link but there\u0026rsquo;s a website that challenges aspiring cloud engineers to host a publicly accessible HTML version of their resume on a cloud platform. The website outlines high-level steps on how to accomplish this, including steps tailored to popular cloud platforms. As someone always looking to expand my portfolio, I eagerly embraced this opportunity.\nIndeed, it turned out to be an excellent experience! You can check out my final resume hosted on AWS here.\nThis blog post aims to chronicle my journey in completing this challenge. Throughout the post, I will highlight interesting aspects and obstacles I encountered along the way. While I won\u0026rsquo;t provide a comprehensive tutorial, as the challenge is meant to be a learning process, I might share enough information to offer some guidance if you find yourself stuck in your own cloud resume challenge.\nDas Code If you just want to see the code, look no further:\nhttps://github.com/braheezy-resume: The GitHub Organization I established to house all the projects for this effort. cloudy-resume: The \u0026ldquo;resume-as-code\u0026rdquo; project. I make edits to the resume in Markdown (my preferred markup language), push the results to GitHub, and voilà! The resume website is automatically updated with my latest accomplishments. resumerk: A poorly named Go project I wrote to convert Markdown files into HTML and PDF formats. terraresume: The Terraform project that deploys the entire AWS infrastructure powering the cloudy resume. resume-analytics: Additional Go code I wrote to collect metrics (e.g. visitor count) about the website. Rules and Regulations To begin my project, I acquired the specific steps for AWS—my chosen cloud provider—and condensed them into a handy checklist to guide me:\nCertification: The resume must showcase the AWS Cloud Practitioner certification. HTML: The resume needs to be written in HTML. CSS: The resume needs to be styled with CSS. Static Website: Deploy the resume as an Amazon S3 static website. HTTPS: The S3 website URL should use HTTPS for enhanced security. DNS: Associate a custom DNS domain name with the website. JavaScript: The resume webpage should include a visitor counter that displays how many people have accessed the site. Write a bit of JavaScript to make this happen. Database: Set up a database to store and update the visitor counter. API: Avoid direct communication between the JavaScript code and the database. Instead, create an API that handles requests from the resume and communicates with the database. Go: Write Go code to process the API requests. Tests: Include tests for the written code. Infrastructure as Code: Utilize Terraform to manage the cloud resources, rather than relying solely on the AWS Management Console. Source Control: Create a GitHub repository for the backend code. CI/CD (Back end): Employ GitHub Actions for continuous integration and deployment of the backend code. CI/CD (Front end): Utilize GitHub Actions for continuous integration and deployment of the resume and its code. Blog post: Document the journey. Yes, right now you are enjoying the last part of the challenge: this blog post!\nGiven that the 13th item on the list is Source Control, I don\u0026rsquo;t think this was written for people with software engineering experience. Consequently, I didn\u0026rsquo;t solve these \u0026ldquo;in order\u0026rdquo; e.g. all projects used Source Control and CI/CD from the beginning.\nAWS Cloud Practitioner Certification I actually did do this one first. The entry level certificate proving AWS knowledge is the Cloud Practitioner Certification. It validates one\u0026rsquo;s knowledge of AWS and requires familiarity with common AWS services. It also covers topics such as AWS pricing and the dynamics of the relationship between cloud engineers and cloud providers.\nTo prepare for the certification, I enrolled in a course on one of the Massive Open Online Course (MOOC) platforms, experimented with free AWS services, and repeatedly took practice exams. Once I consistently achieved passing scores, I knew I was ready. During a winter break, I took the remote proctored exam and obtained the certification. Being a software engineer already proved advantageous as I had prior exposure to technical aspects like APIs and selecting appropriate database types for different scenarios.\nOverall, the experience was highly rewarding, and it bolstered my confidence in discussing and contemplating cloud-related computing concepts.\nHTML/CSS Resume Maintaining a resume in HTML is actually great advice that everybody should do regardless of field. My old method of using an invisible table to line up content feels archaic now. Compared to traditional word processing documents, HTML provides greater flexibility and ease of styling. These days, web browsers make it effortless to convert HTML to PDF, which is often a requirement for submitting job applications.\nI didn\u0026rsquo;t really want to write raw HTML when I\u0026rsquo;m trying to focus on my resume content. My preferred markup language is Markdown which is not directly compatible with static websites or PDF submissions. After rewriting my resume in Markdown, I faced my first real challenge: converting Markdown to HTML and PDF.\nConverting Markdown to HTML is a trivial task since Markdown is designed to convert seamlessly to valid HTML. Many popular programming languages offer libraries or bindings for this purpose:\nPython\nimport markdown md_text = \u0026#34;# Heading\\n\\nThis is some **bold** and *italic* text.\u0026#34; html_text = markdown.markdown(md_text) print(html_text) JavaScript\nimport marked from \u0026#39;marked\u0026#39;; const mdText = \u0026#34;# Heading\\n\\nThis is some **bold** and *italic* text.\u0026#34;; const htmlText = marked(mdText); console.log(htmlText); Rust\nextern crate pulldown_cmark; use pulldown_cmark::{html, Parser}; fn main() { let md_text = \u0026#34;# Heading\\n\\nThis is some **bold** and *italic* text.\u0026#34;; let mut html_text = String::new(); html::push_html(\u0026amp;mut html_text, Parser::new(\u0026amp;md_text)); println!(\u0026#34;{}\u0026#34;, html_text); } All of these simple examples produce the following output:\n\u0026lt;h1\u0026gt;Heading\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;This is some \u0026lt;strong\u0026gt;bold\u0026lt;/strong\u0026gt; and \u0026lt;em\u0026gt;italic\u0026lt;/em\u0026gt; text.\u0026lt;/p\u0026gt; From there, I can open the local HTML file in a web browser and use the built-in tools to print the page to PDF. I pat myself heartily on the back, \u0026ldquo;Congratulations! You converted a Markdown file to HTML and PDF\u0026rdquo;. But this is a fairly manual process and I need it to be quicker.\nResumerk I desired a program that could take my resume.md file as input and in return, produce resume.html and resume.pdf files, ensuring the content remained consistent across all formats.\nWriting software is expensive and I\u0026rsquo;m always willing to integrate a third party tool if it works. I was using this project from mikepqr but I wanted to pad my portfolio with another programming project. At this point, I had seen a fair amount of resume convertor projects on GitHub, written in Python and Go, and all of them required 3rd party tools installed to do the job. I recently started to [learn more about Go]({% post_url 2022-08-09-learn-go-i %}), a great language for static binaries, and decided I would try my hand at a dependency-less resume convertor.\nAs I mentioned earlier, converting Markdown to HTML is easy and Go was no different. gomarkdown/markdown provides a pure Go library to make this simple:\nimport ( \u0026#34;os\u0026#34; \u0026#34;github.com/gomarkdown/markdown\u0026#34; \u0026#34;github.com/gomarkdown/markdown/html\u0026#34; \u0026#34;github.com/gomarkdown/markdown/parser\u0026#34; ) func writeMdToHtml(markdownFile string) { // Read the Markdown file into the program mdData, _ := os.ReadFile(markdownFile) // Force all line endings to Unix line endings mdData = markdown.NormalizeNewlines(mdData) // Enable extra Markdown features when parsing // CommonExtensions: Sane defaults expected in most Markdown docs // AutoHeadingIDs: Create the heading ID from the text extensions := parser.CommonExtensions | parser.AutoHeadingIDs // Define the parser p := parser.NewWithExtensions(extensions) // Run the parser on the Markdown data, generating the AST, // an abstract representation of the information doc := p.Parse(mdData) // Similar to the parser, setup a renderer with certain features (flags) enabled htmlFlags := html.CommonFlags | html.HrefTargetBlank opts := html.RendererOptions{Flags: htmlFlags} renderer := html.NewRenderer(opts) // Turn the AST into HTML html := markdown.Render(doc, renderer) } So HTML is covered. Next is to convert it to PDF and this is where most projects start to rely on third-party CLI tools. Those projects won\u0026rsquo;t run unless extra software is installed. Here\u0026rsquo;s what most of them were depending on:\npandoc: The self-proclaimed universal document convertor. It\u0026rsquo;s a Haskell library that\u0026rsquo;s also built as a binary for distribution. wkhtmltopdf: Another project that uses the Qt WebKit rendering engine perform conversions. Comes with a C library or a binary. Headless Chrome: Still using a web browser to do the conversion but running it GUI-less from the command line. Chrome, being the most modern of the three, comes with a pure Go implementation of running Chrome on the command line in the chromedp library. I use that package:\nimport ( \u0026#34;github.com/chromedp/cdproto/page\u0026#34; \u0026#34;github.com/chromedp/chromedp\u0026#34; ) func writeHtmlToPdf(htmlFile string, pdfFile string) { // create context ctx, cancel := chromedp.NewContext(context.Background()) defer cancel() // capture pdf var buf []byte if err := chromedp.Run(ctx, printToPDF(fmt.Sprintf(\u0026#34;file://%v\u0026#34;, htmlFile), \u0026amp;buf)); err != nil { log.Fatal(err) } if err := os.WriteFile(pdfFile, buf, 0644); err != nil { log.Fatal(err) } } // print a specific pdf page. func printToPDF(url string, res *[]byte) chromedp.Tasks { return chromedp.Tasks{ chromedp.Navigate(url), chromedp.ActionFunc(func(ctx context.Context) error { buf, _, err := page.PrintToPDF().WithPrintBackground(false).Do(ctx) if err != nil { return err } *res = buf return nil }), } } With this core logic in place, I had an initial version of resumerk up and running. It allowed me to convert Markdown files into HTML and PDF using a single binary and without any system dependencies. Nice!\nSubsequently, I added the ability to inject custom CSS and JavaScript into the HTML file, enabling rich styling and dynamic content. I achieved this by utilizing a Go Template for generating the desired HTML file:\n{% raw %} const htmlTemplate = ` \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;{{.Title}}\u0026lt;/title\u0026gt; \u0026lt;script\u0026gt; {{.JSContent}} \u0026lt;/script\u0026gt; \u0026lt;style\u0026gt; {{.CSSContent}} \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div id=\u0026#34;resume\u0026#34;\u0026gt; {{.Resume}} \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt;` {% endraw %} CSS and JS files can be provided and their content will replace the variables CSSContent and JSContent in the template, respectively. The converted resume HTML content, which on its own does not constitute a complete HTML webpage document, replaces the Resume variable within the \u0026lt;body\u0026gt; element. After adding a few convenience features, resumerk was nearly feature complete and passing all unit tests.\nFor building and releasing the project, I have Makefiles and GoReleaser configs that I copy/paste for Go projects and edit as needed. These tools provided the following benefits:\nSimplified build commands that remain consistent, such as make build. Cross-compiled binaries right from the start of the project. Automatic creation of GitHub releases, requiring minimal manual intervention. I incorporated GitHub workflows that trigger builds and tests for resumerk as I make updates to the program. When new tags are created, a Release is automatically created in GitHub and the program is distributed from there in an archive format.\nResume as Code In a \u0026ldquo;Resume as Code\u0026rdquo; paradigm, the resume is treated as software engineers treat code. Here\u0026rsquo;s how I implemented it:\nSource control: I track changes made to my resume over time using GitHub. Testing: I ensure that spelling, grammar, and formatting are correct by utilizing a third-party GitHub action called crate-ci/typos. Building: The edited resume is converted to different formats using resumerk. Deployment: The version of my resume that\u0026rsquo;s saved to the GitHub repository is automatically deployed to AWS (as HTML) and is also made available as a GitHub Release. All of the above actions are automatically applied by GitHub workflows. I edit my resume on my local computer in Markdown, push the change to the GitHub repository, and the HTML website eventually updates.\nStatic Website A static website is the simplest type of web deployment. It involves placing web files (e.g. HTML, CSS, JS) in a specific location and configuring a web server to serve the HTML file when requested. Unlike dynamic websites, there is no backend server generating additional web content that the viewer needs to download. To update a static website, the owner simply needs to modify the existing files. In the case of a resume, serving a single resume.html file is an ideal use case for a static website.\nAWS offers a storage service called Simple Storage Service (S3) that allows you to store files. Hosting static websites is such a common activity that S3 includes built-in support for hosting static websites, and a comprehensive guide showing everything that needs to be done.\nWhen working with AWS services for an extended period or involving multiple services, I prefer not to rely on the AWS Management Console website to manage the infrastructure. It\u0026rsquo;s crucial to have the ability to tear down everything if needed. Careless clicks in the Management Console can inadvertently lead to substantial expenses, and leveraging the free tier is always a good practice.\nEnter Terraform: the programmatic way of defining cloud infrastructure. By filling out a configuration file and running the terraform program, you can do nearly everything the Management Console allows.\nFor example, S3 has what they call \u0026ldquo;buckets\u0026rdquo; where you store files. In the Management Console, you navigate to S3 service, click through the \u0026ldquo;Create bucket\u0026rdquo; button, and fill out a form.\nIn Terraform, you could do this:\nresource \u0026#34;aws_s3_bucket\u0026#34; \u0026#34;example\u0026#34; { bucket = \u0026#34;my-bucket\u0026#34; } Putting a file in an S3 bucket does not a static website make. Users would have to visit the insecure, autogenerated URL for your bucket directly. It would be best if users can just go to my-resume.com or something similar.\nDNS, CDN, HTTPS, and More Acronyms If you want to serve a webpage at a custom name that you pick, you need to fork over cash to those that dole out domain names. They will provide you with access to a domain and records that you can use to prove to the rest of the world that the domain is indeed yours. AWS provides the Route 53 service for domain management. There, I bought a domain braheezy.net and yes, this was a manual task I did while building the infrastructure. One should be careful combining automation and actions that cost money.\nNow that I could assign an easy-to-share name as the S3 bucket URL, I had another problem at hand: scalability. Surely a in-demand engineer like myself will garner hundreds and thousands of visitors to my resume website, right? Right?? I doubt S3, a storage solution, is optimized to handle many concurrent read requests (okay fair point, I wouldn\u0026rsquo;t actually be hitting the limit. Maybe one day!). Additionally, the physical computer in the cloud that is storing the resume file is somewhere on the U.S. West Cost. European visitors may have a slow viewing experience. That won\u0026rsquo;t do.\nThe modern solution to address these challenges is remarkably simple: create copies of the file and distribute them across computers closer to the users. As more users visit the site, more copies can be put on more computers. These computers, known as edge locations, facilitate quicker downloads due to their proximity to the users. This simplified explanation describes a Content Delivery Network (CDN), which is another service offered by AWS known as CloudFront.\nUpon configuration, CloudFront serves the files specified (in this case, the S3 bucket I told it about) to edge locations. Similar to S3, CloudFront generates an auto-generated URL that, when used, will route users to the closest copy of the website. So it\u0026rsquo;s not the S3 bucket URL that should be replaced with a custom domain name, it\u0026rsquo;s this CloudFront URL.\nSo far, everything appears to be progressing well: implement CDN technology to serve the resume file from a bucket and replace the URL with the custom domain name. I quickly get that working but my browser is complaining about untrusted certificates when visiting the site. Oh yeah, what about HTTPS?\nThe Troubles with TLS Modern web browsers often display warnings to users when visiting an HTTP website, potentially discouraging visitors from enjoying your content. To avoid this issue, support for HTTPS is essential and Transport Layer Security (TLS) is a critical component secure HTTPS.\nAt the core of TLS is the exchange of cryptographic certificate files. These are files containing random letters and numbers and when certain types of arcane maths are performed on those random letters and numbers, interesting outcomes happen. Smart mathematicians and computer scientists used these interesting outcomes to create systems that allow for communication safe from prying eyes.\nThe intricate cryptographic details that go into these interesting outcomes and safe communication systems can (and do) fill textbooks. When it comes to TLS and HTTPS, the basic concept involves the creation and trading of certificates. These certificates are placed on computers all over the world, and most network communication software is designed to check and verify these certificates. Certificates possess important attributes, like providing the ability to prove who signed the certificate and what it\u0026rsquo;s for (e.g., Google signed it, and it is intended for google.com). As certificates are verified, they can be combined with other already-trusted certificates, forming a so-called \u0026ldquo;chain of trust.\u0026rdquo; To ensure that my custom domain braheezy.net participates in this global certificate trust exchange, I need to generate certificates specifically for braheezy.net. But anyone with a computer can generate certificates. I also need a way to prove to the world that the certificates I generate were indeed generated by me, the owner of braheezy.net.\nAnother helpful AWS service in this regard is the Amazon Certificate Manager (ACM). They handle the generation of certificates, and since I purchased the domain from AWS, they provide a convenient method for verifying my ownership of the specified domain.\nWhen you purchase a domain from Route 53 (or any domain name registrar), they update their servers with your domain information. Registrars operate the DNS servers used by the entire Internet, enabling the public resolution of your domain name by strangers. Route 53 specifically does this by assigning dedicated name servers to your domain (name servers are responsible for storing domain name relationships) that respond with your domain when queried.\nReturning to the topic of certificates, one way to prove domain ownership is by placing a secret code on the assigned name servers (a privilege reserved for domain owners). I can then share the secret code and instruct someone to request it from the name server. If the name server responds with the correct code, it verifies my ownership. Here\u0026rsquo;s an example exchange:\nMe: \u0026ldquo;If you ask the server for the special code, it will respond with 5.\u0026rdquo; You: \u0026ldquo;Hey server, what\u0026rsquo;s the secret code?\u0026rdquo; Server: \u0026ldquo;5\u0026rdquo; You: \u0026ldquo;Wow\u0026hellip;\u0026rdquo; Since Route 53 and ACM operate on the same Amazon infrastructure, they simplify this secret code exchange for you. Once the certificates are successfully generated and verified, they can be added to CloudFront, enabling visits to https://resume.braheezy.net without any warnings.\nA Simple Visitor Counter(?) The goal: use JavaScript to display a number on the page that reflects how many unique users have visited the site. This seemingly simple problem statement quickly spiraled into the most complicated part of the website, a multi-faceted challenge spanning at least 5 different technology domains.\nTo View, or Not, View the Count Physically showing the count on the site means adding an HTML element. This is an element that should really only be in the HTML version of the resume because having it anywhere else makes no sense. There\u0026rsquo;s a few different ways to solve this problem and I accidentally landed on a lazy solution that I recommend to no one.\nI use JS to dynamically inject the visitor counter HTML element into the page. This happens after a network call lookup the count in a database and takes a moment to load in. This window of time is large enough that resumerk can open the page and convert it to PDF before the JS loads.\nWhen is a Visit a Visit? The next challenge was to determine when to increment the visitor counter. Since the goal specifies unique visitors, my initial thought was to use IP addresses. However, considering that IP addresses are often dynamically assigned and can change, it might lead to counting multiple visits from the same user.\nAfter conducting some research, I found a better solution using cookies. Cookies are small pieces of data that websites can store in a user\u0026rsquo;s browser. By using a cookie to mark users as they visit, I can intelligently increment the counter only when a new user (one without the cookie) arrives. Here\u0026rsquo;s an example implementation in JavaScript:\n// Read the current cookies const userCookies = document.cookie if (cookies) { console.log(\u0026#34;user has already visited!\u0026#34;) } else { document.cookie = \u0026#34;visited=yes\u0026#34; incrementVisitorCounter() } Remembering Visits For this counter to work across multiple visitors, the count needs to be stored somewhere outside the website. This requires persistent storage that can be consulted for the latest count and incremented as necessary. In modern technology terms, this means storing the count in a database. So, I turned to AWS and a service called DynamoDB, which provides simple key-value storage.\nTo me, key-value storage implies a simple structure like users: 10 and if that\u0026rsquo;s all you need, that\u0026rsquo;s literally all that\u0026rsquo;s in the database. However, DynamoDB introduces the concept of a partition key used for managing scaling operations in the background. In applications where it makes sense, the partition key could be something meaningful like a userID. However, for this project, I wasn\u0026rsquo;t sure what to use as the partition key. Perhaps it could make sense to have the key as site and the value as braheezy.net, allowing the table to support other websites (that I don\u0026rsquo;t plan on creating).\nSince I only wanted to store a single number, it turned out to be most effective to treat the count as an Attribute in the Table:\nDynamoDB Table\nProperty Value Table Name site-analytics Partition Key metrics Attributes\nAttribute Type visitorCount Number Now I have a visitorCount that my website can query and increment as needed.\nInteracting with DynamoDB Although the JavaScript on the website is capable of directly making calls to the database, it is considered best practice to offload that processing task to another computer. This approach adds complexity to the website architecture, but it allows the frontend to focus solely on displaying the visitor count without concerning itself with how the count is saved.\nThis other computer, known as the backend server, will listen for requests to either retrieve the current count or increment it. It will then make calls to DynamoDB to read the visitorCount variable or update it. Finally, it will respond to the frontend with the result.\nProcessing Visitor Counts: APIs and Lambdas As mentioned, the backend server listens for requests to perform tasks. This is accomplished using an API, which describes how programs interact with each other. AWS offers a service called AWS API Gateway specifically designed for providing HTTP APIs. It provides a URL and various API endpoints to handle different types of HTTP requests. In this project, I created the /count endpoint and configured different HTTP request methods to perform specific actions from the website using JavaScript calls:\nAPI Endpoints\nEndpoint Request Action ${API_URL}/count GET Retrieve the current visitor count ${API_URL}/count PUT Increment the current visitor count The $API_URL is generated by AWS API Gateway and must be shared with the website frontend so it knows where to send requests.\nNow that we have a way to listen for requests on another computer, we need a mechanism to actually do something and process those requests. The AWS approach is to deploy software on yet another computer and configure AWS API Gateway to forward the requests to that computer. I developed a Go program that accepts HTTP requests from the API Gateway, interacts with DynamoDB, and provides the necessary response with visitor counter information.\nAt the 10,000ft level, it looks like this:\nfunc handleRequest(req events.APIGatewayProxyRequest) (*events.APIGatewayProxyResponse, error) { var body string var err error switch req.HTTPMethod { case \u0026#34;GET\u0026#34;: body, err = getCount() case \u0026#34;PUT\u0026#34;: body, err = updateCount() } // Return body } func getCount() (string, error) { // Connect to data, get current count, and return it. } func updateCount() (string, error) { // Connect to database, update count, return new count. } func main() { // Connect to DB dbClient = dynamodb.NewFromConfig(cfg) // Handle lambda request from API gateway lambda.Start(handleRequest) } The aforementioned backend task only runs when it is called, which the website does the first time it loads. While the API Gateway always needs to be running, it is inefficient to have the processing task continuously running on a computer. The modern solution to this problem is to spin up a computer only when the task needs to be performed. This concept is commonly known as Serverless, and AWS provides their own offering called AWS Lambda. By providing your code in a zip file and specifying the programming language, AWS takes care of the rest of creating a machine with the tools to run it. They will do spin up a machine every time the AWS Lambda function is called.\nLike other components in this project, the Site Analytics code uses GitHub workflows: All these components come together to form the backend system, allowing for the seamless processing of visitor counts:\ngraph LR A01(API Gateway):::api --HTTP Requests--\u003e B01(Lambda:Function):::lam B01 --Database operations--\u003e E01[(DynamoDB)]:::dyn classDef s3 fill:#4A902A,stroke:#333,stroke-width:4px,color:#fff; classDef lam fill:#dc6c11,stroke:#333,stroke-width:4px,color:#fff; classDef dyn fill:#3c48cc,stroke:#333,stroke-width:4px,color:#fff; classDef api fill:#8450e1,stroke:#333,stroke-width:4px,color:#fff; Wrap Up That\u0026rsquo;s the whole project!\nhttps://resume.braheezy.net\nOverall, this project has been a worthwhile investment of time. I gained new knowledge about both new and existing technologies. Even without the website aspect, transitioning to a Markdown/HTML-based resume was an excellent decision that I should have made long ago. The ease of writing and the enhanced features provided by web technologies are powerful.\nHaving never hosted anything publicly before, AWS made it easy with their comprehensive guides and helpful forums where questions were promptly answered. The most challenging parts were configuring HTTPS and setting up DynamoDB correctly. I struggled for a significant amount of time to establish trust and verification for the custom certificate. Ultimately, the difficulty was due to Terraform\u0026rsquo;s handling of DNS servers. As for DynamoDB, despite being a newcomer to it, my use case was almost too simple for its capabilities and that posed problems for my implementation.\nThank you for reading this far! If you\u0026rsquo;re eager for more technical details, you can find them for each project:\ncloudy-resume resumerk terraresume resume-analytics ","permalink":"https://braheezy.github.io/posts/my-cloudy-resume/","summary":"A Cloudy Challenge I can\u0026rsquo;t remember where I originally found the link but there\u0026rsquo;s a website that challenges aspiring cloud engineers to host a publicly accessible HTML version of their resume on a cloud platform. The website outlines high-level steps on how to accomplish this, including steps tailored to popular cloud platforms. As someone always looking to expand my portfolio, I eagerly embraced this opportunity.\nIndeed, it turned out to be an excellent experience!","title":"My Cloudy Resume"},{"content":"The next item in my Go curriculum is to start writing code. At this point, I haven\u0026rsquo;t installed Go or configured an IDE and I really don\u0026rsquo;t want to. It\u0026rsquo;s a commitment I\u0026rsquo;m not ready to make yet. Instead, I hop on the internet to find a Coding challenge site.\nThere are many websites that provide a straightforward and comfortable environment for eager students to get their hands dirty with actual code and actual coding problems. Early in my software education, these sites were instrumental in (making me feel like I was) sharpening my skills at problem solving and language knowledge. The big ones like LeetCode and HackerRack have gotten so popular that large MAANG companies use these sites to weed out interviewees, like me. They usually have a similar format:\nThe problem is described and illustrated with examples A text editor with function declaration is given. You must provide the function body Automated test cases so you know when you got the problem right. Good sites will have hidden test cases that ensure you meet a certain space and time complexity. There was one site I particularly enjoyed a few years ago and I grew several Python fluency levels while using it. There was a pleasant UI and different portions of the site to explore with different learning tracks. Navigating the site to find challenges based on skill was intuitive and usually curated. The coding area itself was great and I remember liking the tests and witty edge cases. But alas, in 2022 I can\u0026rsquo;t find the site anymore, or can\u0026rsquo;t remember the name. Either way, I had to find a new one.\nI settled on CodeWars. It met all my requirements and it was quick enough to get started with. This would be a productive area to be given elementary programming problems that in another language I knew would be a breeze, but in Go, I would have to look up syntax on how to express the solution. It was a constant recurrence to think \u0026ldquo;Okay, in Python I would do this, so to convert to Go I need to write it like this\u0026hellip;\u0026rdquo;. As I worked through problems, it was interesting to work through this translation penalty.\nThe site says I\u0026rsquo;ve completed 31 kata, the word they chose for problems. This comes from the idea that one way of becoming a better programmer is to practice solving small challenges daily. While this does work to a point, I ultimately disagree with the philosophy when it comes to programming. Kata in martial arts is about learning muscle memory and perfect physical movements. The efficacy of one\u0026rsquo;s fingers moving across the keyboard is typically not the limiting factor in an aspiring engineer\u0026rsquo;s education. There are other domains where the kata learning technique is more applicable, like learning a musical instrument.\nI wrote this post several weeks after doing the katas but here are a couple interesting ones I ran into.\nMultiples of Index Return a new array consisting of elements which are multiple of their own index in input array (length \u0026gt; 1).\nThis has classic array operations all over it. Array iteration, working with indexes and values, and creating a new array. There\u0026rsquo;s probably some map way of solving this but as a Go noob, I arrived at the following:\nfunc multipleOfIndex (ints []int) (result []int) { // Ignore the first element of the `ints` array b/c it\u0026#39;s index is 0. i := 1 // As Gophers know, there\u0026#39;s multiple way to write a for loop. I like this `range` way for _, val := range ints[1:] { // No remainder leftover from modulo division means // val is a multiple of i. if val % i == 0 { result = append(result, val) } i++ } return result } Shortest Word Simple, given a string of words, return the length of the shortest word(s).\nString will never be empty and you do not need to account for different data types.\nIt\u0026rsquo;s common to work with sentences that need to be broken down into words and then some operation done on those words. This problem gives a chance to look at Go string handling in the standard library.\nimport ( \u0026#34;strings\u0026#34; ) func FindShort(s string) (shortestWordLen int) { // `Fields` is a great function that breaks the sentence by whitespace, giving us our words. for i, word := range strings.Fields(s) { word_length := len(word) // If it\u0026#39;s the first word, it\u0026#39;s the longest we\u0026#39;ve seen so far. Otherwise, do the check if i == 0 || word_length \u0026lt; shortestWordLen { shortestWordLen = word_length } } return shortestWordLen } Mumbling This time no story, no theory. The examples below show you how to write function accum: Examples:\naccum(\u0026quot;abcd\u0026quot;) -\u0026gt; \u0026quot;A-Bb-Ccc-Dddd\u0026quot; accum(\u0026quot;RqaEzty\u0026quot;) -\u0026gt; \u0026quot;R-Qq-Aaa-Eeee-Zzzzz-Tttttt-Yyyyyyy\u0026quot; accum(\u0026quot;cwAt\u0026quot;) -\u0026gt; \u0026quot;C-Ww-Aaa-Tttt\u0026quot; The parameter of accum is a string which includes only letters from a..z and A..Z.\nHere\u0026rsquo;s one where in Python, I immediately would be looking at the itertools library to see what functional programming trick could be used to solve the problem. It\u0026rsquo;s a good opportunity to see what functional programming Go offers.\nNot much! The standard library is small compared to Python\u0026rsquo;s and because Generic type support in Go was only just added, generic helpers like itertools aren\u0026rsquo;t here yet.\nimport ( \u0026#34;strings\u0026#34; \u0026#34;fmt\u0026#34; ) func Accum(s string) string { // I learned cool kids use strings.Builder to make strings instead of `+=` var sb strings.Builder // For each character in the string... for i, c := range s { // Create a string that repeats the character by the amount of it\u0026#39;s index repeated_string := strings.Repeat(strings.ToLower(string(c)), i + 1) // Add string to builder, making sure to Title case it first fmt.Fprintf(\u0026amp;sb, \u0026#34;%s-\u0026#34;, strings.Title(repeated_string)) } return strings.TrimRight(sb.String(), \u0026#34;-\u0026#34;) } NthEven Return the Nth Even Number\nExample(Input \u0026ndash;\u0026gt; Output)\n1 --\u0026gt; 0 (the first even number is 0) 3 --\u0026gt; 4 (the 3rd even number is 4 (0, 2, 4)) 100 --\u0026gt; 198 1298734 --\u0026gt; 2597466 The input will not be 0.\nOkay this one I just wanted to show off 😁\nThought process:\nEvery other number is odd, so to get to nth even number, you need: n * 2 Oh, but the first two numbers don\u0026rsquo;t count, 0 and 1, so subtract: (n * 2) - 2 My Algebra bone is tickling. That can be refactored by pulling the 2 out: 2 * (n - 1) I know a little obfuscation technique in that x / 2 == x \u0026gt;\u0026gt; 1. That means I can switch the binary shift if I want to multiply: (n - 1) * 2 becomes (n - 1) \u0026lt;\u0026lt; 1 To be clear, this would never go in production code unless you wanted comrade programmers to come a-hunting with pitchforks.\nfunc NthEven(n int) int { return (n - 1) \u0026lt;\u0026lt; 1 } Final Thoughts CodeWars has been fun but like I spewed earlier, single focused problems like katas and their ilk can only take the student so far. The graduation to actual Projects needs to take place. Install the language and a build toolchain. Configure a development environment. Write an entire program that does a Whole Thing, not just a single function to a very specific prompt. If you feel stalled in your programming journey and haven\u0026rsquo;t left the curated air conditioned halls of sites like HackerRank, try branching into Projects.\n","permalink":"https://braheezy.github.io/posts/learn-go-iii/","summary":"The next item in my Go curriculum is to start writing code. At this point, I haven\u0026rsquo;t installed Go or configured an IDE and I really don\u0026rsquo;t want to. It\u0026rsquo;s a commitment I\u0026rsquo;m not ready to make yet. Instead, I hop on the internet to find a Coding challenge site.\nThere are many websites that provide a straightforward and comfortable environment for eager students to get their hands dirty with actual code and actual coding problems.","title":"Learn Go: III"},{"content":"There are two main problem domains you tackle when sitting down to write code:\nThe real world dilemma being solved with software How to explain the solution to the computer The first problem takes most of the brain power. Eliciting requirements for the software from complex shifting sands. Coming up with the right design and architecture for robust and reliable systems. How to reverse a linked list. Before anything can be written in code, the author must be able to wrap their heads around the solution, at least to some degree. Sometimes, experimentation is needed to get across the finish line.\nOnce the coder can wrap their head around what must be done, they face the arduous obstacle of convincing a metal box to do it. They\u0026rsquo;ll often use a programming language to express their needs to the machine. This is a compromise the machines and humans have agreed upon, to both do a bit of work to learn how to speak a common language. There\u0026rsquo;s a translation cost. Humans must convert the solution from their native spoken tongue to the code language, which the computer must take and eventually convert to 1s and 0s (unless you code in binary, you monster). As the language is used more and more, it becomes easier for both parties to communicate.\nSince we\u0026rsquo;re talking languages, when is fluency reached in a programming language? Is that a real thing?\nThe answer may depend on who you ask. Once you know all keywords? Some of the standard library? All of it? Are you fluent when you can cite the language spec and parse the language grammar into a fancy AST?\nI suggest that fluency is reached when the second problem domain goes away. It\u0026rsquo;s no longer a challenge to express what one wants to the machine. The brain has internalized and/or done away with the cost of translation. Thoughts are casted in molds whose shape is influenced by what the language can express and only the Main Problem is battled, not communication. It\u0026rsquo;s similar to spoken languages: You don\u0026rsquo;t need to know every English word, every obscure grammar rule, or every idiom to be an excellent English writer.\nOnce fluency is reached in a first language, productivity with software will dramatically increase. I urge other programmers to try and fully learn one language at some point, as opposed to lightly dabbling and being useful in several. Take the time to dig deep!\n","permalink":"https://braheezy.github.io/posts/language-fluency/","summary":"There are two main problem domains you tackle when sitting down to write code:\nThe real world dilemma being solved with software How to explain the solution to the computer The first problem takes most of the brain power. Eliciting requirements for the software from complex shifting sands. Coming up with the right design and architecture for robust and reliable systems. How to reverse a linked list. Before anything can be written in code, the author must be able to wrap their heads around the solution, at least to some degree.","title":"Language Fluency"},{"content":"We\u0026rsquo;ll finish the rest of the Tour of Go this time, which was started in the [previous post]({% post_url 2022-08-09-learn-go-i %}).\nMethoding Around The Tour\u0026rsquo;s next lesson is called Methods. But wait\u0026hellip;there\u0026rsquo;s no classes, what are you calling methods on?\nTypes! A method is a specific type of function that can be identified by the way they are declared. A receiver argument appears after the keyword func and before the method name:\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;math\u0026#34; ) type Vertex struct { X, Y float64 } //Abs method has a receiver of type Vertex named v func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} fmt.Println(v.Abs()) } Methods also can:\nBe defined on primitive types like type MyFloat float64 Receive pointers, allowing them to act on the original entity, or be more efficient if the receiver is e.g. a large struct Next up is Interfaces, defined as a collection of method signatures. This immediately sticks out as Go\u0026rsquo;s approach to Abstract types, a powerful OOP concept. You specify the functions you want children to implement before they can call your interface a parent:\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;math\u0026#34; ) type Abser interface { Abs() float64 } func main() { var a Abser f := MyFloat(-math.Sqrt2) v := Vertex{3, 4} a = f // a MyFloat implements Abser a = \u0026amp;v // a *Vertex implements Abser // In the following line, v is a Vertex (not *Vertex) // and does NOT implement Abser. a = v // This results in an error b/c Vertex (the type) doesn\u0026#39;t // implement Abser b/c Abs() is not defined for Vertex, // only *Vertex fmt.Println(a.Abs()) } type MyFloat float64 func (f MyFloat) Abs() float64 { if f \u0026lt; 0 { return float64(-f) } return float64(f) } type Vertex struct { X, Y float64 } func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } The next several notes get into specific behavior about interfaces and type assertions that I skim through. I\u0026rsquo;ll come back to them when I need these language constructs.\nThey do mention the ubiquitous error interface. It\u0026rsquo;s very idiomatic to call a function, get return values, and handle errors by seeing if error is nil or not:\ni, err := strconv.Atoi(\u0026#34;42\u0026#34;) if err != nil { fmt.Printf(\u0026#34;couldn\u0026#39;t convert number: %v\\n\u0026#34;, err) return } fmt.Println(\u0026#34;Converted integer:\u0026#34;, i) Finally there are several lesson at the end, trying to show off the Reader and Image interfaces from the standard library. I can only assume this Tour was aimed at REAL PROGRAMMERZ and not scrubs like me because I had no idea what the related exercises were asking and had no interest in figuring it out. I figured the Tour was a gentle tutorial for beginners but it seems to assume prior programming experience. Whatever\u0026hellip;\nGeneral Generic I remember seeing on the interwebs all the Go-hate because it didn\u0026rsquo;t support \u0026ldquo;generics\u0026rdquo; and the subsequent rejoicing when it finally did. As someone who spent a lot of time with Python, I understand the power of not caring about what type you\u0026rsquo;re working with, you just wanna do the thing with it. I\u0026rsquo;m glad I got into Go when I did.\nFunctions and Types can be generic. Special syntax and keywords are used to indicate genericity:\npackage main import \u0026#34;fmt\u0026#34; // Index returns the index of x in s, or -1 if not found. func Index[T comparable](s []T, x T) int { for i, v := range s { // v and x are type T, which has the comparable // constraint, so we can use == here. if v == x { return i } } return -1 } // List represents a singly-linked list that holds // values of any type. type List[T any] struct { next *List[T] val T } func main() { // Index works on a slice of ints si := []int{10, 20, 15, -10} fmt.Println(Index(si, 15)) // Index also works on a slice of strings ss := []string{\u0026#34;foo\u0026#34;, \u0026#34;bar\u0026#34;, \u0026#34;baz\u0026#34;} fmt.Println(Index(ss, \u0026#34;hello\u0026#34;)) } Con Concurrency This is where Go stands out. Goroutines are a lightweight thread managed by the Go runtime and there is the special go keyword for running them:\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func say(s string) { for i := 0; i \u0026lt; 5; i++ { time.Sleep(1000 * time.Millisecond) fmt.Println(s) } } func main() { go say(\u0026#34;world\u0026#34;) say(\u0026#34;hello\u0026#34;) } $ run main.go hello world world hello hello world world hello hello Channels are a special conduit to send and receive values. Like maps and slices, they must be created before they can be used. make() does that too. Reading and writing values to a channel has it\u0026rsquo;s own operator \u0026lt;-. These have properties that allow goroutines to synchronize without locking. This is another language feature I need to use before understanding fully:\npackage main import \u0026#34;fmt\u0026#34; func sum(s []int, c chan int) { sum := 0 for _, v := range s { sum += v } c \u0026lt;- sum // send sum to c } func main() { s := []int{7, 2, 8, -9, 4, 0} c := make(chan int) go sum(s[:len(s)/2], c) go sum(s[len(s)/2:], c) x, y := \u0026lt;-c, \u0026lt;-c // receive from c fmt.Println(x, y, x+y) } $ run main.go -5 17 12 Channels can be closed with the close() function. No more values can be sent to a closed channel. It\u0026rsquo;s idiomatic to use range to iterate over channel values until it\u0026rsquo;s closed.\nThere\u0026rsquo;s a select keyword that allows a goroutine to wait for some condition before operating. It uses the case keyword and looks like a switch statement:\nfunc main() { tick := time.Tick(100 * time.Millisecond) boom := time.After(500 * time.Millisecond) for { select { case \u0026lt;-tick: fmt.Println(\u0026#34;tick.\u0026#34;) case \u0026lt;-boom: fmt.Println(\u0026#34;BOOM!\u0026#34;) return default: fmt.Println(\u0026#34; .\u0026#34;) time.Sleep(50 * time.Millisecond) } } } $ run main.go . . tick. . . tick. . . tick. . . tick. . . BOOM! Next Steps And that\u0026rsquo;s essentially the end of the Tour of Go! Overall, Go looks to be an interesting, modern language with some cool constructs built-in. And I haven\u0026rsquo;t even seen the stuff that brought me to it: the build/deploy/management of Go binaries. I\u0026rsquo;m slightly disheartened by my struggles at certain points in the tutorial but more practice with the language should fix that.\nArmed with a thorough introduction to language syntax and design, actually writing code is the next move.\n","permalink":"https://braheezy.github.io/posts/learn-go-ii/","summary":"We\u0026rsquo;ll finish the rest of the Tour of Go this time, which was started in the [previous post]({% post_url 2022-08-09-learn-go-i %}).\nMethoding Around The Tour\u0026rsquo;s next lesson is called Methods. But wait\u0026hellip;there\u0026rsquo;s no classes, what are you calling methods on?\nTypes! A method is a specific type of function that can be identified by the way they are declared. A receiver argument appears after the keyword func and before the method name:","title":"Learn Go: II"},{"content":"In the [last post]({% post_url 2022-07-25-new-language-first-steps %}), I went over why wanted to learn Go. In this post I put my money where my mouth is.\nReadying Up Coffee poured and hip-hop music on, I sit down at my computer to learn. In the most naive fashion, I decide to start by finding and going to the Go website.\nThis turns out to be the best choice. The Tour of Go found under the Get Started page is an \u0026ldquo;interactive introduction to Go in three sections\u0026rdquo;. A curated tutorial to the language by the language creators. Okay Google. Making it too easy.\nSetting Out The Tour starts pleasantly and introduces basic syntax. Often, links are provided out to the Go blog to color in the background of certain topics. These were thoroughly appreciated, like this one on Declaration Syntax (I had never heard of the Clockwise/Spiral Rule before. That would have helped a ton when I wrote C).\nAfter working with Python for so long, the first lesson makes a point to remind me of types (int, float64, etc.) and all the related business of defining them and converting between them. I find the rune type, which holds a single character value, and the complex128 type, which can hold complex numbers, a delightful decision by the language designers. The other cool language feature that sticks out is named return values, allowing you to define the name of the return variable in the function declaration.\n// x and y are variables defined in the declaration, used in the body, // and returned. Less typing! func split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return x, y } Going In The next lesson introduces how Go lets the programmer control the flow of execution within their program. Stuff like if and loops.\nGo has one loop: for. But they provided a bunch of flexibility in how it\u0026rsquo;s used. There\u0026rsquo;s the normal for from many languages:\npackage main import \u0026#34;fmt\u0026#34; func main() { sum := 0 for i := 0; i \u0026lt; 10; i++ { sum += i } fmt.Println(sum) } But the first and last statements are optional.\npackage main import \u0026#34;fmt\u0026#34; func main() { // This is a while loop. sum := 1 for sum \u0026lt; 10 { sum += sum } fmt.Println(sum) } Go ifs have a nice convenience of letting you run a short inline statement:\n// v has scope for the if block if v := math.Pow(x, n); v \u0026lt; lim { return v } Switch statements are next. Python didn\u0026rsquo;t have these until recently so it\u0026rsquo;s nice to see again. It\u0026rsquo;s easy and quick to use in Go, like this idiomatic way of writing long if-else chains:\nfunc main() { t := time.Now() switch { case t.Hour() \u0026lt; 12: fmt.Println(\u0026#34;Good morning!\u0026#34;) case t.Hour() \u0026lt; 17: fmt.Println(\u0026#34;Good afternoon.\u0026#34;) default: fmt.Println(\u0026#34;Good evening.\u0026#34;) } } The last concept is one I\u0026rsquo;ve never seen before and am too lazy to research to see if another language has anything like it. The defer keyword lets you run a function when the outer function exits:\nfunc main() { defer fmt.Println(\u0026#34;world\u0026#34;) fmt.Println(\u0026#34;hello\u0026#34;) } The obvious use case is using defer to run cleanup functions in more complicated programs. Go docs mention this and point out how it eases the burden of cleanup on the programmer by allowing resource cleanup to be defined right when resources are created.\nStill Going Pointers. They\u0026rsquo;re back (I know, they were never really gone to begin with). Go provides direct access to pointer magic with familiar *(dereference) and \u0026amp;(address) operators. Thankfully, there\u0026rsquo;s no pointer arithmetic. I suspect that results in less C-like pointer shenanigans.\nStructs are here too. Go has no class keyword so it looks like structs will carry a heavy workload when programmers want to employ object-orientated techniques. The syntax is as expected and members of structs can be access with . notation:\ntype Vertex struct { X int Y int } func main() { v := Vertex{1, 2} fmt.Println(v.X) } Arrays can be used too but one works with Slices much more often. They are \u0026ldquo;views\u0026rdquo; into an underlying array. Edit a value in them and the underlying array changes too and you betcha, you can make slices of slices.\nfunc main() { // An array initialized with some values primes := [6]int{2, 3, 5, 7, 11, 13} // Creating a slice by accessing the array with [:] syntax var s []int = primes[1:4] fmt.Println(s) } The Tour talks of slices having a length and capacity. These are intuitive: length is how large the slice is, and capacity is how much room the slice could grow based on the underlying array.\nTwo useful operations are shown: append and range.\nfunc main() { // Initialize an array with values to copy from var source []int{1, 2, 3} // Initialize an empty array to copy to var target []int // range iterates over each value in source and returns it. // It also returns the index of the value in the array. for index, value := range source { target = append(target, value) } fmt.Printf(target) } Faltering The Tour includes Exercises that require actual code implementation to complete. There\u0026rsquo;s one on slices that says this:\nImplement Pic. It should return a slice of length dy, each element of which is a slice of dx 8-bit unsigned integers. When you run the program, it will display your picture, interpreting the integers as grayscale (well, bluescale) values.\nThe choice of image is up to you. Interesting functions include (x+y)/2, x*y, and x^y.\n(You need to use a loop to allocate each []uint8 inside the [][]uint8.)\n(Use uint8(intValue) to convert between types.)\nI admit it. I didn\u0026rsquo;t really know what this prompt was asking for\u0026hellip;ahem\u0026hellip;humbling. I found solutions online and the implementations made sense but I still didn\u0026rsquo;t get what was going on. I wiped the tears from my eyes and continued to\u0026hellip;\nMaps! Hashmaps, key-values tables, dictionaries. Whatever you want to call them. These I understand and can regain confidence on. Maps in Go are friendly to work with like in Python\nfunc main() { m := make(map[string]int) m[\u0026#34;Answer\u0026#34;] = 42 fmt.Println(\u0026#34;The value:\u0026#34;, m[\u0026#34;Answer\u0026#34;]) m[\u0026#34;Answer\u0026#34;] = 48 fmt.Println(\u0026#34;The value:\u0026#34;, m[\u0026#34;Answer\u0026#34;]) delete(m, \u0026#34;Answer\u0026#34;) fmt.Println(\u0026#34;The value:\u0026#34;, m[\u0026#34;Answer\u0026#34;]) v, ok := m[\u0026#34;Answer\u0026#34;] fmt.Println(\u0026#34;The value:\u0026#34;, v, \u0026#34;Present?\u0026#34;, ok) } Functions are first-class citizens in Go. They can be passed around and saved to variables like everybody else. This usually means the language supports powerful functional programming solutions (many lines of Go code later, I\u0026rsquo;m finding that might not be the case here).\nThere\u0026rsquo;s also Closures that essentially bind a function to an external variable. These take me some time to digest properly:\n// The adder function returns a closure. Each closure is bound to its own `sum` variable. func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } func main() { pos, neg := adder(), adder() for i := 0; i \u0026lt; 10; i++ { fmt.Println( pos(i), neg(-2*i), ) } } Using a foreign language construct in an actual program you write is the best way to really understand it and that quickly became true for me.\nBut on day one in the Tour, I don\u0026rsquo;t have that practice and their Fibonacci Exercise stumps me too:\nLet\u0026rsquo;s have some fun with functions.\nImplement a fibonacci function that returns a function (a closure) that returns successive fibonacci numbers (0, 1, 1, 2, 3, 5, \u0026hellip;).\nThe code template they provided:\npackage main import \u0026#34;fmt\u0026#34; // fibonacci is a function that returns // a function that returns an int. func fibonacci() func() int { } func main() { f := fibonacci() for i := 0; i \u0026lt; 10; i++ { fmt.Println(f()) } } I am NOT having fun with functions :/\n","permalink":"https://braheezy.github.io/posts/learn-go-i/","summary":"In the [last post]({% post_url 2022-07-25-new-language-first-steps %}), I went over why wanted to learn Go. In this post I put my money where my mouth is.\nReadying Up Coffee poured and hip-hop music on, I sit down at my computer to learn. In the most naive fashion, I decide to start by finding and going to the Go website.\nThis turns out to be the best choice. The Tour of Go found under the Get Started page is an \u0026ldquo;interactive introduction to Go in three sections\u0026rdquo;.","title":"Learn Go: I"},{"content":"During my work on a Catppuccin-ified image, I wrote an Ansible playbook to automate the installation and configuration of Firefox. The playbook is used to build an image from a known state, so it\u0026rsquo;s not 100% idempotent. But it was still just enough research that it warranted sharing.\nInstallation Installing is easy. It\u0026rsquo;s so trivial, I shouldn\u0026rsquo;t even show it:\n- name: Ensure firefox is installed become: yes yum: name: firefox state: present Best. Ansible. Dev. Ever.\nConfiguration I\u0026rsquo;m installing Firefox so that I can ultimately apply a Catppuccin theme to it. These themes are available as addons from the Firefox store. Super easy to install if you\u0026rsquo;re sitting at the web browser. But we won\u0026rsquo;t be.\nResearch I don\u0026rsquo;t know anything useful about Firefox addons or how themes work. I open a bunch of browser tabs and figure out the following:\nThe Catppuccin assets appear to be JSON manifest files describing which UI elements to apply which hex codes to. The firefox command on Linux has a bunch of useful command line options. When you install Firefox, the useful stuff lives in ~/.mozilla/firefox. Firefox has profiles that can be controlled by a config file. This is enough information to start automating some stuff.\nApproach At a high-level, the following must be done:\nCreate Firefox profile Download/install/marry theme to profile Ensure profile is used when Firefox is run Some playbook snippets for creating a Firefox profile:\n# Use the Firefox CLI to create a new profile. - name: Create new Firefox profile environment: # Seems to expect a graphical session, so trick it into thinking there is one with $DISPLAY env var. DISPLAY: :0 command: firefox -no-remote -CreateProfile {{ profile_name }} # Get a reference to the profile config location on disk. # The command above created a folder in the firefox config directory named \u0026lt;random string\u0026gt;.\u0026lt;profile name\u0026gt;/. We need to find it. - name: Find profile directory find: paths: \u0026#39;{{ ansible_env.HOME }}/.mozilla/firefox\u0026#39; patterns: \u0026#39;*.{{ profile_name }}\u0026#39; file_type: directory register: profile_dir # Pull the thing we care about out: the path to the profile directory. - name: Parse result for profile path set_fact: profile_path: \u0026#34;{{ profile_dir.files[0].path }}\u0026#34; To apply the theme, I\u0026rsquo;d like to download the theme file to disk and put it in the same spot(s) it would go if installed through the browser. To get the download link, I try going directly to the addon store and seeing what link the \u0026ldquo;Install Theme\u0026rdquo; button gives me.\nCool, it\u0026rsquo;s an .xpi file1. But that 3954898 in the URL smells like a randomly generated build number that\u0026rsquo;s probably going to change often. I don\u0026rsquo;t want to update this playbook every time that happens. Can I ask Firefox for the information?\nYes! Enter into the arena: Mozilla Addons API. After much noodling through API docs and doing some light testing with Postman, the Detail endpoint seems to meet my needs and I arrive at something useful:\n# theme_name: exact name of theme to get details on e.g. catppuccin-mocha-mauve - name: Call Firefox Addons API for theme info uri: url: https://addons.mozilla.org/api/v5/addons/addon/{{ theme_name }} return_content: yes register: api_call # Get the bits of info we need: the current download link for the addon and it\u0026#39;s unique identifier - name: Parse result for download link set_fact: theme_download_link: \u0026#34;{{ api_call.json.current_version.file.url }}\u0026#34; # The actual extension on disk gets renamed to \u0026lt;guid\u0026gt;.xpi when installed. - name: Parse result for guid set_fact: theme_guid: \u0026#34;{{ api_call.json.guid }}\u0026#34; It\u0026rsquo;s about time to actually download the extension, making sure we put it in the right location:\n- name: Ensure extensions directory exists file: path: \u0026#39;{{ profile_path }}/extensions\u0026#39; state: directory mode: 0755 - name: Download theme extension file get_url: url: \u0026#34;{{ theme_download_link }}\u0026#34; dest: \u0026#39;{{ profile_path }}/extensions/{{ theme_guid }}.xpi\u0026#39; mode: 0644 Feeling confident, I start testing. Odd dialogs from Firefox appear, speaking of default profiles, last used profiles, and ultimately the theme isn\u0026rsquo;t applied when I get to the browser. Hmm\u0026hellip;\nThe problem is that while I\u0026rsquo;ve created a profile and installed a theme, I didn\u0026rsquo;t introduce them to each other and tell Firefox about it. Creating the correct profiles.ini file should solve this. After reading docs and comparing various working and broken configs, I land on this:\n- name: \u0026#39;Set [General] options in profiles.ini\u0026#39; ini_file: path: \u0026#39;{{ config_dir }}/profiles.ini\u0026#39; section: General option: \u0026#39;{{ item.option }}\u0026#39; value: \u0026#39;{{ item.value }}\u0026#39; mode: 0644 create: yes no_extra_spaces: yes loop: # Suppress the dialog that asks for which profile to use. - { option: StartWithLastProfile, value: 1 } # Needs to be here, probably spec version of ini file. - { option: Version, value: 2 } loop_control: label: \u0026#34;{{ item.option }}\u0026#34; - name: Define custom profile and set as default ini_file: path: \u0026#39;{{ config_dir }}/profiles.ini\u0026#39; section: Profile0 option: \u0026#39;{{ item.option }}\u0026#39; value: \u0026#39;{{ item.value }}\u0026#39; no_extra_spaces: yes loop: # All the required settings to enable/set the profile. # Mozilla docs good! - { option: Name, value: \u0026#39;{{ profile_name }}\u0026#39; } - { option: IsRelative, value: 1 } - { option: Path, value: \u0026#39;{{ profile_path | basename }}\u0026#39; } - { option: Default, value: 1 } - { option: Locked, value: 1 } loop_control: label: \u0026#34;{{ item.option }}\u0026#34; One more full test and success! Firefox launches like normal but now has a theme applied.\nFull Role The whole thing all at once:\n# defaults/main.yml --- # Name of Firefox profile that will be created profile_name: catppuccin # Config location for Firefox config_dir: \u0026#39;{{ ansible_env.HOME }}/.mozilla/firefox\u0026#39; # The specific Catppuccin theme to use theme_name: catppuccin-mocha-mauve # tasks/main.yml --- - name: Ensure firefox is installed become: yes yum: name: firefox state: present - name: Create new Firefox profile environment: DISPLAY: :0 command: firefox -no-remote -CreateProfile {{ profile_name }} - name: Find profile directory find: paths: \u0026#39;{{ config_dir }}\u0026#39; patterns: \u0026#39;*.{{ profile_name }}\u0026#39; file_type: directory register: profile_dir - name: Parse result for profile path set_fact: profile_path: \u0026#34;{{ profile_dir.files[0].path }}\u0026#34; - name: Call Firefox Addons API for theme info uri: url: https://addons.mozilla.org/api/v5/addons/addon/{{ theme_name }} return_content: yes register: api_call - name: Parse result for download link set_fact: theme_download_link: \u0026#34;{{ api_call.json.current_version.file.url }}\u0026#34; - name: Parse result for guid set_fact: theme_guid: \u0026#34;{{ api_call.json.guid }}\u0026#34; - name: Ensure extensions directory exists file: path: \u0026#39;{{ profile_path }}/extensions\u0026#39; state: directory mode: 0755 - name: Download theme extension file get_url: url: \u0026#34;{{ theme_download_link }}\u0026#34; dest: \u0026#39;{{ profile_path }}/extensions/{{ theme_guid }}.xpi\u0026#39; mode: 0644 # http://kb.mozillazine.org/Profiles.ini_file - name: Set general options in profiles.ini ini_file: path: \u0026#39;{{ config_dir }}/profiles.ini\u0026#39; section: General option: \u0026#39;{{ item.option }}\u0026#39; value: \u0026#39;{{ item.value }}\u0026#39; mode: 0644 create: yes no_extra_spaces: yes loop: - { option: StartWithLastProfile, value: 1 } - { option: Version, value: 2 } loop_control: label: \u0026#34;{{ item.option }}\u0026#34; - name: Define custom profile and set as default ini_file: path: \u0026#39;{{ config_dir }}/profiles.ini\u0026#39; section: Profile0 option: \u0026#39;{{ item.option }}\u0026#39; value: \u0026#39;{{ item.value }}\u0026#39; no_extra_spaces: yes loop: - { option: Name, value: \u0026#39;{{ profile_name }}\u0026#39; } - { option: IsRelative, value: 1 } - { option: Path, value: \u0026#39;{{ profile_path | basename }}\u0026#39; } - { option: Default, value: 1 } - { option: Locked, value: 1 } loop_control: label: \u0026#34;{{ item.option }}\u0026#34; This still amazes me. Turns out, most random file extensions you might find are probably some type of archive file. Big Zip doesn\u0026rsquo;t want you to know this.\nHere, a quick unzip *.xpi shows inside: A META/ folder containing metadata files and the manifest JSON theme file noted earlier.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://braheezy.github.io/posts/automating-firefox-with-ansible/","summary":"During my work on a Catppuccin-ified image, I wrote an Ansible playbook to automate the installation and configuration of Firefox. The playbook is used to build an image from a known state, so it\u0026rsquo;s not 100% idempotent. But it was still just enough research that it warranted sharing.\nInstallation Installing is easy. It\u0026rsquo;s so trivial, I shouldn\u0026rsquo;t even show it:\n- name: Ensure firefox is installed become: yes yum: name: firefox state: present Best.","title":"Automating Firefox with Ansible"},{"content":"I recently found out about the esteemed Shamus Young and exploring his work has been gratifying and eye-opening. His skill at writing, transparency, and technical prowess makes one want to be a better engineer and person. One of the nuggets of knowledge found in his volumes is the benefit the act of writing itself has. And so I\u0026rsquo;ve explained why this series exists.\nAt this point in my software career, I\u0026rsquo;m fluent in Python and can deal with some C (technically got paid for C development for 2 years, but in reality I didn\u0026rsquo;t write much). I\u0026rsquo;m literate in JavaScript and spent quite some time with Solidity, the language of Ethereum smart contracts.\nPython is the only one I enjoyed.\nDeciding to learn a computer language is not a light endeavor. Choosing the right language is especially tricky. Zealots will yell from corners, hawking their language as the Supreme Language and try to seduce you from trying others. The student should pick a language that balances the various tradeoffs for their situation. I want a powerful, mature, fast, marketable language that\u0026rsquo;s fun to use.\nGo: Go (okay that\u0026rsquo;s the only one).\nCreated by Google in 2009 by a team that had Ken Thompson on it, the language has matured generously over the past decade and whatever years. It\u0026rsquo;s been strongly adopted by the DevOps community and has several attractive features to explore.\nGo, as a modern language, can benefit from mistakes of predecessors. Things not often thought about, like tooling support, are first-class citizens in the Go ecosystem. Specifically what caught my eye:\nCompile down to single statically-linked binary. No more slugging around dependencies with a lockfile and hoping users install them correctly Fast and elegant CLIs. CLI apps are the type of program I\u0026rsquo;ve probably built the most It\u0026rsquo;s typed and compiled. Only being fluent in a interpreted language makes me feel like my toolbox is missing a tool. The first steps are complete. I\u0026rsquo;ve thought long about which language to learn and feel confident in my reasons. The rest of the series will document my struggles.\n","permalink":"https://braheezy.github.io/posts/new-language-first-steps/","summary":"I recently found out about the esteemed Shamus Young and exploring his work has been gratifying and eye-opening. His skill at writing, transparency, and technical prowess makes one want to be a better engineer and person. One of the nuggets of knowledge found in his volumes is the benefit the act of writing itself has. And so I\u0026rsquo;ve explained why this series exists.\nAt this point in my software career, I\u0026rsquo;m fluent in Python and can deal with some C (technically got paid for C development for 2 years, but in reality I didn\u0026rsquo;t write much).","title":"New Language: First Steps"},{"content":"This post hopes to explain how to enable a shared folder with a Windows guest on Libvirt/QEMU using Virtiofs.\nAn official Windows Guide is provided that generally covers everything needed. This post has snippets that help automate the process.\nVirtiofs A newer approach to shared file systems between virtual machines and hosts. From the main site:\nVirtiofs is a shared file system that lets virtual machines access a directory tree on the host. Unlike existing approaches, it is designed to offer local file system semantics and performance.\nVirtiofs was started at Red Hat and is being developed in the Linux, QEMU, FUSE, and Kata Containers open source communities.\nI liked the idea of a dedicated solution instead of network based ones like NFS or Samba that have historically been repurposed for such things. Plus the boast of better performance.\nThere are minimum requirements for a host computer to use Virtiofs:\nSoftware Version Check Method Linux 5.4 uname -r QEMU 5.0 qemu-kvm --version Libvirt 6.2 libvirtd --version How To Setup Virtiofs on Windows We\u0026rsquo;ll install some tools to the Windows guest and then configure the VM to use the shared filesystem.\nWinFsp As explained in Windows Guide, WinFsp is needed inside the guest.\nDownload and install the application.\nPowerShell\nPS C:\\\u0026gt; Invoke-WebRequest https://github.com/winfsp/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi -OutFile winfsp-1.10.22006.msi PS C:\\\u0026gt; winfsp-1.10.22006.msi /qn /norestart Ansible\n- name: Check services win_service: name: WinFsp.Launcher register: winfspservice_result - name: Install winfsp block: - name: Download winfsp win_get_url: url: \u0026#34;https://github.com/winfsp/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi\u0026#34; dest: \u0026#34;{{ ansible_env.TEMP }}\\\\winfsp-1.10.22006.msi\u0026#34; - name: Install winfsp win_package: path: \u0026#34;{{ ansible_env.TEMP }}\\\\winfsp-1.10.22006.msi\u0026#34; state: present when: winfspservice_result.state is not defined or winfspservice_result.name is not defined Viofs Driver and Service The virtual filesystem is attached to the guest as device so a proper driver is required.\nVirtIO drivers for Windows deserve their own post. I\u0026rsquo;ll quickly point out the ISO containing everything can be downloaded from the Github page and that it needs to be mounted somewhere so you can grab the viofs drivers within.\nAssuming it\u0026rsquo;s been mounted on the D: drive in the Windows VM, install the driver:\nPS C:\\\u0026gt; pnputil /install /add-driver D:\\viofs\\w10\\amd64\\viofs.inf Let\u0026rsquo;s also install the virtiofs.exe service.\nPowerShell\nPS C:\\\u0026gt; cp D:\\viofs\\w10\\amd64\\virtiofs.exe C:\\Windows\\virtiofs.exe PS C:\\\u0026gt; New-Service -Name \u0026#34;VirtioFsSvc\u0026#34; -BinaryPathName \u0026#34;C:\\Windows\\virtiofs.exe\u0026#34; -DisplayName \u0026#34;Virtio FS Service\u0026#34; -Description \u0026#34;Enables Windows virtual machines to access directories on the host that have been shared with them using virtiofs.\u0026#34; -StartupType Automatic -DependsOn \u0026#34;WinFsp.Launcher\u0026#34; Ansible\n- name: Create virtiofs service win_service: name: VirtioFsSvc path: C:\\Windows\\virtiofs.exe description: Enables Windows virtual machines to access directories on the host that have been shared with them using virtiofs. display_name: Virtio FS Service start_mode: auto dependencies: - WinFsp.Launcher Define Synced Folder As of this writing, it seems you can only share 1 folder at a time. If you define multiple, it appears to use the one is defined last.\nManual method:\nIn virt-manager, in the details section for the Windows guest, click Add Hardware. Choose Filesystem and supply a Source path and Target path. The XML snippet should look something like this:\n\u0026lt;filesystem type=\u0026#34;mount\u0026#34; accessmode=\u0026#34;passthrough\u0026#34;\u0026gt; \u0026lt;driver type=\u0026#34;virtiofs\u0026#34;/\u0026gt; \u0026lt;binary path=\u0026#34;/usr/libexec/virtiofsd\u0026#34;/\u0026gt; \u0026lt;source dir=\u0026#34;/tmp\u0026#34;/\u0026gt; \u0026lt;target dir=\u0026#34;tmp\u0026#34;/\u0026gt; \u0026lt;address type=\u0026#34;pci\u0026#34; domain=\u0026#34;0x0000\u0026#34; bus=\u0026#34;0x07\u0026#34; slot=\u0026#34;0x00\u0026#34; function=\u0026#34;0x0\u0026#34;/\u0026gt; \u0026lt;/filesystem\u0026gt; The virt-manager GUI interface may not offer the ability to set the passthrough as an access mode, or to set driver and binary information for virtiofs. The XML can be manually edited with these settings.\nVagrant (automated) way:\nYou\u0026rsquo;ll need the vagrant-libvirt plugin.\nThe virtiofs blurb in the synced folders section of the documentation should work.\nconfig.vm.provider :libvirt do |l, override| l.driver = \u0026#34;kvm\u0026#34; ... l.memorybacking :access, :mode =\u0026gt; \u0026#34;shared\u0026#34; override.vm.synced_folder \u0026#34;.\u0026#34;, \u0026#34;/vagrant\u0026#34;, disabled: false, type: \u0026#34;virtiofs\u0026#34; ... end If everything is working, you\u0026rsquo;ll find a new mapped drive for your shared folder.\n","permalink":"https://braheezy.github.io/posts/some-things-virtiofs-and-windows/","summary":"This post hopes to explain how to enable a shared folder with a Windows guest on Libvirt/QEMU using Virtiofs.\nAn official Windows Guide is provided that generally covers everything needed. This post has snippets that help automate the process.\nVirtiofs A newer approach to shared file systems between virtual machines and hosts. From the main site:\nVirtiofs is a shared file system that lets virtual machines access a directory tree on the host.","title":"Some Things Virtiofs and Windows"},{"content":"Figuring It All Out Consider you have the following needs/limitations:\nA Windows host A desire to use Packer to build Windows VMs A desire to use Ansible to provision Windows VMs No support for nested virtualization In my personal case, I\u0026rsquo;m using a Windows 10 Home edition which lacks certain virtualization features you might find in the Pro version (like running Hyper-V). So I\u0026rsquo;m using VirtualBox as the hypervisor. I also have Ubuntu running on WSL.\nReady to get started, I installed packer and ansible. I know Packer directly calls VBoxManage.exe (Windows) or vboxmanage (Linux) underneath, and that was installed on my Windows side. I verified I could see it in WSL:\nbraheezy@homebase:~/windows-pipeline$ VBoxManage.exe --version 6.1.32r149290 Nice! Microsoft\u0026rsquo;s promise holds up. Indeed, we can see in it our path:\nbraheezy@homebase:~/windows-pipeline$ echo $PATH blahblahblah:/mnt/c/Program Files/Oracle/VirtualBox To get on the same page, here\u0026rsquo;s what the working directory looks like.\nbraheezy@homebase:~/windows-pipeline$ ls floppy template.pkr.hcl windows10.iso floppy/: Directory of files Packer will upload as a floppy drive to help bootstrap Windows template.pkr.hcl: The Packer template windows10.iso: The Windows 10 install disc Cool. Let\u0026rsquo;s invoke the packer build command and see what happens.\nbraheezy@homebase:~/windows-pipeline$ packer build template.pkr.hcl virtualbox-iso.test: output will be in this color. Build \u0026#39;virtualbox-iso.test\u0026#39; errored after 43 milliseconds 621 microseconds: Failed creating VirtualBox driver: exec: \u0026#34;VBoxManage\u0026#34;: executable file not found in $PATH ==\u0026gt; Wait completed after 43 milliseconds 753 microseconds ==\u0026gt; Some builds didn\u0026#39;t complete successfully and had errors: --\u0026gt; virtualbox-iso.test: Failed creating VirtualBox driver: exec: \u0026#34;VBoxManage\u0026#34;: executable file not found in $PATH ==\u0026gt; Builds finished but no artifacts were created. Oh\u0026hellip;Fine. Let\u0026rsquo;s create a symlink and trick packer into calling the executable.\nbraheezy@homebase:~/windows-pipeline$ sudo ln -s /mnt/c/Program\\ Files/Oracle/VirtualBox/VBoxManage.exe /usr/local/bin/VBoxManage braheezy@homebase:~/windows-pipeline$ packer build template.pkr.hcl ==\u0026gt; virtualbox-iso.test: Retrieving ISO ==\u0026gt; virtualbox-iso.test: Trying windows10.iso ==\u0026gt; virtualbox-iso.test: Trying windows10.iso?checksum=md5%3A3e62ce57edcad33f89f1edb4f55fe24a ==\u0026gt; virtualbox-iso.test: windows10.iso?checksum=md5%3A3e62ce57edcad33f89f1edb4f55fe24a =\u0026gt; /home/braheezy/z_drive/windows-pipeline/windows10.iso ==\u0026gt; virtualbox-iso.test: Creating floppy disk... virtualbox-iso.test: Copying files flatly from floppy_files virtualbox-iso.test: Copying directory: floppy virtualbox-iso.test: Adding file: floppy/Autounattend_bios.xml virtualbox-iso.test: Adding file: floppy/Autounattend_efi.xml virtualbox-iso.test: Adding file: floppy/ConfigureRemotingForAnsible.ps1 virtualbox-iso.test: Adding file: floppy/fixnetwork.ps1 virtualbox-iso.test: Done copying files from floppy_files virtualbox-iso.test: Collecting paths from floppy_dirs virtualbox-iso.test: Resulting paths from floppy_dirs : [] virtualbox-iso.test: Done copying paths from floppy_dirs virtualbox-iso.test: Copying files from floppy_content virtualbox-iso.test: Done copying files from floppy_content ==\u0026gt; virtualbox-iso.test: Creating ephemeral key pair for SSH communicator... ==\u0026gt; virtualbox-iso.test: Created ephemeral SSH key pair for communicator ==\u0026gt; virtualbox-iso.test: Creating virtual machine... ==\u0026gt; virtualbox-iso.test: Creating hard drive output-test/packer-test-1651809462.vdi with size 40000 MiB... ==\u0026gt; virtualbox-iso.test: Mounting ISOs... ==\u0026gt; virtualbox-iso.test: Deleting any current floppy disk... ==\u0026gt; virtualbox-iso.test: Attaching floppy disk.. ==\u0026gt; virtualbox-iso.test: Creating forwarded port mapping for communicator (SSH, WinRM, etc) (host port 3288) ==\u0026gt; virtualbox-iso.test: Starting the virtual machine... ==\u0026gt; virtualbox-iso.test: Waiting 5s for boot... ==\u0026gt; virtualbox-iso.test: Typing the boot command... ==\u0026gt; virtualbox-iso.test: Using WinRM communicator to connect: 127.0.0.1 ==\u0026gt; virtualbox-iso.test: Waiting for WinRM to become available... Success! At least for Packer. Here\u0026rsquo;s hoping the answer file is correct\u0026hellip;\nOther Failures On my Windows computer, I have a C: drive on one physical SSD and another Z: drive on a different physical SSD. The Z: drive usually holds development projects, including the windows-pipeline project above originally.\nVirtualBox did not like all this nonsense with different drive letters and WSL. It threw errors like this:\n==\u0026gt; virtualbox-iso.test: Error attaching ISO: VBoxManage error: VBoxManage.exe: error: Could not find file for the medium \u0026#39;Z:\\mnt\\z\\windows-pipeline\\windows10.iso\u0026#39; (VERR_PATH_NOT_FOUND) Agreed, that path does not exist.\nEven after doing more \u0026ldquo;tricks\u0026rdquo; to get VirtualBox to see the ISO, it would later fail to find the floppy.vfd that Packer had made in the /tmp directory. This led me down the roundabout rabbit hole of finding/modifying this script and eventually I getting everything to work on my beloved Z: drive. But all I had to do the whole time was copy it somewhere to the local WSL filesystem.\nDue to terrible performance of the Windows VM, I thought about setting up GPU passthrough to give it a boost. That research let me find out it\u0026rsquo;s impossible to do that on my Windows 10 Home computer and subsequently, re-installing Linux to that second SSD.\nAnd so ends a naive attempt at virtualization at home with Windows. Just use Linux.\n","permalink":"https://braheezy.github.io/posts/packer-on-wsl-just-use-linuxpacker-on-wsl-just-use-linux/","summary":"Figuring It All Out Consider you have the following needs/limitations:\nA Windows host A desire to use Packer to build Windows VMs A desire to use Ansible to provision Windows VMs No support for nested virtualization In my personal case, I\u0026rsquo;m using a Windows 10 Home edition which lacks certain virtualization features you might find in the Pro version (like running Hyper-V). So I\u0026rsquo;m using VirtualBox as the hypervisor. I also have Ubuntu running on WSL.","title":"Packer on WSL: Just Use Linux"},{"content":"Some of the things I\u0026rsquo;ve made. Most of the repositories have more demo materials.\nexplore zpix Pure Zig image decoding library supporting JPEG, PNG, and QOI formats with comprehensive color space handling and format features.\ngraphics systems Zig source znit A minimal init system written in Zig, based on Tini. Handles process supervision, zombie reaping, and signal forwarding in containers.\nsystems devops Zig source zoleco A fully functional ColecoVision emulator written in Zig using SDL for graphics, accurately emulating the original hardware.\nsystems graphics emulation Zig source shine-mp3 A pure Go implementation of MP3 encoding, perhaps the only one in existence. Ported from the Shine MP3 project.\naudio systems Go source hobby-spline An interactive demonstration of Hobby\u0026#39;s algorithm for smooth curve generation, featuring a WASM-powered interface.\ngraphics web Go, WASM, HTML/CSS source demo space-invaders Cycle-accurate emulation of the 1978 Space Invaders arcade hardware, including full 8080 CPU emulation.\nsystems graphics emulation Go source chip-8 A complete CHIP-8 interpreter with support for multiple quirks and both GUI/TUI modes.\nsystems graphics emulation Go source bubblelife 3D visualization of Conway\u0026#39;s Game of Life using thousands of bubbles, featuring HDRi lighting and custom shaders.\ngraphics systems Go, OpenGL source goqoa CLI tool for converting and playing QOA audio files, supporting multiple input formats.\naudio tools Go source QOA Preview VS Code extension enabling playback of QOA audio files with a native-looking audio player.\naudio ui/ux web TypeScript, HTML/CSS source wavvy Web-based WAV file player using Go compiled to WebAssembly for audio processing.\naudio web ui/ux Go, WASM, HTML/CSS source demo violet A colorful TUI frontend for Vagrant, making VM management more intuitive.\ntools ui/ux devops Go source kilo Minimal terminal text editor in about 1000 lines of code, capable of editing its own source.\nsystems ui/ux Go source gravlax Complete implementation of a tree-walk interpreter for the Lox language, with extra language features.\ncompilers systems Go source zig-lox Bytecode VM implementation of the Lox language, featuring a custom garbage collector.\ncompilers systems Zig source ","permalink":"https://braheezy.github.io/projects/","summary":"Some of the things I\u0026rsquo;ve made. Most of the repositories have more demo materials.\nexplore zpix Pure Zig image decoding library supporting JPEG, PNG, and QOI formats with comprehensive color space handling and format features.\ngraphics systems Zig source znit A minimal init system written in Zig, based on Tini. Handles process supervision, zombie reaping, and signal forwarding in containers.\nsystems devops Zig source zoleco A fully functional ColecoVision emulator written in Zig using SDL for graphics, accurately emulating the original hardware.","title":"hobby projects"}]