diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 45c1505..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -_site -.sass-cache -.jekyll-metadata diff --git a/CNAME b/CNAME deleted file mode 100644 index a8a2398..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -dmitrym.online \ No newline at end of file diff --git a/categories/test/index.html b/categories/test/index.html deleted file mode 100644 index fd71d53..0000000 --- a/categories/test/index.html +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - test · Dmitry M - Veridis Quo - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
-

test

- - - - - - - - -
- - -
- - - -
- - - - - - diff --git a/categories/test/index.xml b/categories/test/index.xml deleted file mode 100644 index 7f1ad36..0000000 --- a/categories/test/index.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - test on Dmitry M - Veridis Quo - https://dmitrym0.github.io/categories/test/ - Recent content in test on Dmitry M - Veridis Quo - Hugo -- gohugo.io - en - Fri, 12 Jul 2019 00:00:00 -0700 - - test post 99 - https://dmitrym0.github.io/posts/post-title-in-slug-form-3/ - Fri, 12 Jul 2019 00:00:00 -0700 - - https://dmitrym0.github.io/posts/post-title-in-slug-form-3/ - this is a post - - - - diff --git a/categories/test/page/1/index.html b/categories/test/page/1/index.html deleted file mode 100644 index c7b9d12..0000000 --- a/categories/test/page/1/index.html +++ /dev/null @@ -1 +0,0 @@ -https://dmitrym0.github.io/categories/test/ \ No newline at end of file diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..b9c7a5c --- /dev/null +++ b/config.toml @@ -0,0 +1,86 @@ +baseurl = "https://dmitrym.online" +title = "Dmitry M - Veridis Quo" +theme = "coder" +languagecode = "en" +defaultcontentlanguage = "en" + +paginate = 20 +canonifyurls = true + +pygmentsstyle = "bw" +pygmentscodefences = true +pygmentscodefencesguesssyntax = true + +disqusShortname = "dmitry-m-viridis-quo" + +[params] + author = "Dmitry M" + info = "General Nerdery" + description = "Dmitry's personal website" + keywords = "blog,developer,personal" + avatarurl = "images/me.png" + + favicon_32 = "/img/favicon-32x32.png" + favicon_16 = "/img/favicon-16x16.png" + + footercontent = "General Nerdery." + + hidecredits = false + hidecopyright = false + + rtl = false + + math = true + custom_css = ["css/custom.css", "images/syntax.css"] + +# Social links +[[params.social]] + name = "Github" + icon = "fab fa-github fa-2x" + weight = 1 + url = "https://github.com/dmitrym0/" +[[params.social]] + name = "Gitlab" + icon = "fab fa-gitlab fa-2x" + weight = 2 + url = "https://gitlab.com/dmitrym0/" +[[params.social]] + name = "Twitter" + icon = "fab fa-twitter fa-2x" + weight = 3 + url = "https://twitter.com/dmitrym/" + +# Menu links +[[menu.main]] + name = "Blog" + weight = 1 + url = "/posts/" + +[[menu.main]] + name = "TIL" + weight = 9 + url = "https://til.dmitrym.online/" + + +[[menu.main]] + name = "Tags" + weight = 10 + url = "/tags/" + + + +[markup] + [markup.goldmark] + [markup.goldmark.renderer] + unsafe = true + + [markup.highlight] + codeFences = true + guessSyntax = false + lineNos = true + lineNumbersInTable = true + noClasses = false + noHl = false + style = 'emacs' + tabWidth = 4 + pygmentsUseClassic=true diff --git a/content/pages/about.md b/content/pages/about.md new file mode 100644 index 0000000..6fb396b --- /dev/null +++ b/content/pages/about.md @@ -0,0 +1,20 @@ ++++ +title = "About" +author = ["Dmitry Markushevich"] +date = 2001-12-07 +lastmod = 2023-05-22T12:33:08-07:00 +tags = ["General"] +draft = false +weight = 2001 +noauthor = true +nocomment = true +nodate = true +nopaging = true +noread = true +[menu] + [menu.main] + weight = 2001 + identifier = "about" ++++ + +I like building stuff. diff --git a/content/pages/projects.md b/content/pages/projects.md new file mode 100644 index 0000000..351143e --- /dev/null +++ b/content/pages/projects.md @@ -0,0 +1,46 @@ ++++ +title = "Projects" +author = ["Dmitry Markushevich"] +date = 2001-12-07 +lastmod = 2023-05-22T12:33:08-07:00 +draft = false +weight = 2002 +noauthor = true +nocomment = true +nodate = true +nopaging = true +noread = true +[menu] + [menu.main] + weight = 2002 + identifier = "projects" ++++ + +## org-hyperscheduler {#org-hyperscheduler} + +[org-hyperscheduler](https://github.com/dmitrym0/org-hyperscheduler) is an Emacs package that uses a calendar web component to visualize org-mode agenda. + +More information is available on [GitHub](https://github.com/dmitrym0/org-hyperscheduler). + +Also on [MELPA](https://melpa.org/#/org-hyperscheduler). + + +## simple-lets-encrypt-docker-compose-sample {#simple-lets-encrypt-docker-compose-sample} + +[simple-lets-encrypt-docker-compose-sample](https://github.com/dmitrym0/simple-lets-encrypt-docker-compose-sample) is a sample `docker-compose.yml` that shows how to leverage [docker-letsencrypt-nginx-proxy-companion](https://github.com/jwilder/docker-letsencrypt-nginx-proxy-companion) to automatically terminate SSL connections. + + +## org-reminders-importer {#org-reminders-importer} + +A SwiftUI iOS app to grab reminders and dump them into a text file on Dropbox. + + +## paratroopers {#paratroopers} + +An [SDL](https://www.libsdl.org) based iOS game: + + + +Here's what the original looked like: + + diff --git a/content/posts/back-to-personal-agile.md b/content/posts/back-to-personal-agile.md new file mode 100644 index 0000000..d490664 --- /dev/null +++ b/content/posts/back-to-personal-agile.md @@ -0,0 +1,32 @@ ++++ +title = "Back to Personal Agile" +author = ["Dmitry Markushevich"] +date = 2023-11-06 +lastmod = 2023-11-06T17:30:49-08:00 +tags = ["Productivity", "OpenSource", "Life"] +draft = false ++++ + +Life's been kinda busy. Between family and work, hobbies take a back seat. The quote that applies is "A goal without a plan is just a wish". So I want to make a plan. + +> A goal without a plan is just a wish. + +In IT world, "Agile" is an invective. Rightfully so. I've never seen it properly implemented. Usually it's a way to mask the lack of any process. "Oh we are agile". Nope, no real plan, permanent firefighting mode. + +I'm not going to be any different. I'm going to steal a couple of things that I feel are beneficial and call it "agile". + +First, I'm going to do sprint planning. Sprint planning is the process of identifying the next set of steps that align with my goals (see "a goal without the plan" above). The important detail that I see frequently ignored or glosses over, is capacity. My goal for this Personal Agile Reboot is to commit 3 hours a week. That's the entirety of my capacity. + +Second, I'm going to commit to the work I set out to do. This is another gap I see frequently. We plan an iteration out, but the scope of work is not protected. Outside influences frequently distabilize the prioritized list of tasks and what is delivered at the end of the iteration does not match the goal set at the outset. Frequent course changes disrupt productivity and reduce velocity. Task thrashing also reduces trust in the original planning exercise. If the task list is about to change, there's no point in committing any effort to the planning exercise. + +Finally, I will perform retrospectives. The goal of a retrospective is to analyze the performance of the team and derive team velocity. How productive is the team on average? The goal is to be able to go back to step 1 and generate a task list for the next iteration that is doable. + +These 3 practices are self-reinforcing. + +If you don't properly plan your sprint, you can neither adequately judge your success (or failure) nor derive a proper velocity. + +If your sprint remains open to changes, this devalues the planning exercise and reduces it's perceived value in following iterations. Without a stable iteration it's also impossible to generate correct spring velocity. + +Finally, if no retrospectives are done it's impossible to course correct ("we took on more work last sprint that we could handle"). + +I'll let you know how it goes. diff --git a/content/posts/book-review-first-90-days.md b/content/posts/book-review-first-90-days.md new file mode 100644 index 0000000..8c67a85 --- /dev/null +++ b/content/posts/book-review-first-90-days.md @@ -0,0 +1,102 @@ ++++ +title = """ + Book review: "First 90 days" + """ +author = ["Dmitry Markushevich"] +date = 2020-04-28T00:00:00-07:00 +lastmod = 2023-05-22T12:33:09-07:00 +draft = false ++++ + +The "First 90 days" lays out a comprehensive plan on how to become productive in a new role, whether it's a promotion or a position in a new company. + +> It’s a mistake to believe that you will be successful in your new job by continuing to do what you did in your previous job, only more so + +The advice is wide ranging. What's useful about this book is that there's nearly a step by step process for every category of advice. + +**Chapter 1** is all about doing homework and understanding the business. Ideally this happens before the actual transition. It's all about identifying cultural norms, understanding your place within the business and engaging with your new team and boss + +> No matter how well you think you understand what you’re expected to do, be sure to check and recheck expectations once you formally join your new organization. Why? Because understandings that are developed before you join—about “mandates, support, and resources—may not prove to be fully accurate once you’re in the job. It isn’t that you’ve been actively misled; rather, it’s because recruiting is like romance, and employment is like marriage. + +**Chapter 2** lays out a set of steps to understand the business: + +> The first task in making a successful transition is to accelerate your learning. Effective learning gives you the foundational insights you need as you build your plan for the next 90 days + +This includes establishing a learning agenda, compiling a list of questions, understanding vision and strategy, processes, future plans: + +> - How has this organization performed in the past? How do people in the organization think it has performed? +> - How were goals set? Were they insufficiently or overly ambitious? +> - Were internal or external benchmarks used? +> - What measures were employed? What behaviors did they encourage and discourage? +> - What happened if goals were not met? + +There's advice on how to structure introductions with the team to understand what's going on: + +> Suppose you decide to meet with your direct reports one-on-one. In what order will you meet with them? And how will you avoid being excessively influenced by what the first couple of people say? One approach is to keep to the same script in all your meetings. You might start with brief opening remarks about yourself and your approach, followed by questions about the other person (background, family, and interests) and then a standard set of questions about the business. This approach is powerful, because the responses you get are comparable. You can line them up side by side and analyze what is consistent and inconsistent +> +> Ask them essentially the same five questions: +> +> - What are the biggest challenges the organization is facing (or will face in the near future)? +> - Why is the organization facing ” “or going to face) these challenges? +> - What are the most promising unexploited opportunities for growth? +> - What would need to happen for the organization to exploit the potential of these opportunities? +> - If you were me, what would you focus attention on? + +The author advocates creating a "learning plan" and adhere to it carefully: + +> Your learning agenda defines what you want to learn. Your learning plan defines how you will go about learning it. It translates learning goals into specific sets of actions—identifying promising sources of insight and using systematic methods—that accelerate your learning. Your learning plan is a critical part of your overall 90-day plan. In fact, as you will discover later, learning should be a primary focus of your plan for your first 30 days on the job (unless, of course, there is a disaster in progress). + +**Chapter 3** identifies a set of common business situations, STARS. + +> “!STARS! is an acronym for five common business situations leaders may find themselves moving into: +> +> - start-up +> - turnaround +> - accelerated growth - +> - realignment, and +> - sustaining success. + +One's responsibilities will differ dependening on the scenario: + +> In a realignment, your challenge is to revitalize a unit, product, process, or project that has been drifting into danger. + + + +> In sustaining-success situations, you must invent the challenge by finding ways to keep people motivated, combat complacency, and find new direction for growth—both organizational and personal + +The book provides a way to identify the current situation and provides guidance on how to proceed: + +> Armed with insight into your STARS portfolio and the key challenges and opportunities, +> you will adopt the right strategies for leading change. Doing so means, however, adopting the approaches laid out in this book for creating momentum in your next 90 days. Specifically, you must establish priorities, define strategic intent, identify where you can secure early wins, build the right leadership team, and create supporting alliances. + +**Chapter 4** is about negotiating success with your boss. In this context it means arriving at a consensus regarding expectations, deliverables and communication styles. + +> Negotiating success means proactively engaging with your new boss to shape the game so that you have a fighting chance of achieving desired goals. Many new leaders just play the game, reactively taking their situation as given—and failing as a result. The alternative is to shape the game by negotiating with your boss to establish realistic expectations, reach consensus, and secure sufficient resources. + +There are specific recommendations. + +DON'T: + +> - Don’t stay away. If you have a boss who doesn’t reach out to you, or with whom you have uncomfortable interactions, you will have to reach out yourself. Otherwise, you risk potentially crippling communication gaps. +> - Don’t surprise your boss. It’s no fun bringing your boss bad news. However, most bosses consider it a far greater sin not to report emerging problems early enough. Worst of all is for your boss to learn about a problem from someone else.! It’s usually best to give your new boss at least a heads-up as soon as you become aware of a developing problem. +> - Don’t approach your boss only with problems. That said, you don’t want to be perceived as bringing nothing but problems for your boss to solve. You also need to have plans for how you will proceed. This emphatically does not mean that you must fashion full-blown solutions: the outlay of time and effort to generate solutions can easily lure you down the rocky road to surprising your boss. The key here is to give some thought to how to address the problem—even if it is only gathering more information—and to your role and the help you will need (This is a good thing to keep in mind in dealing with direct reports, too. It can be dangerous to say, “Don’t bring me problems, bring me solutions.” Far better is, “Don’t just bring me problems, bring me plans for how we can begin to address them” + +DO: + +> - Clarify expectations early and often. Begin managing expectations from the moment you consider taking a new role. ” +> - Take 100 percent responsibility for making the relationship work. This is the flip side of “Don’t stay away.” +> - Don’t expect your boss to reach out or to offer you the time and support you need!. It’s best to begin by assuming that it’s on your shoulders to make the relationship work. If your boss meets you partway, it will be a welcome surprise +> - Negotiate time lines for diagnosis and action planning. Don’t let yourself get caught up immediately in firefighting or be pressured to make calls before you’re ready!. Buy yourself some time, even if it’s only a few weeks, to diagnose the new organization and come up with an action plan. It worked for Michael in his dealings with Vaughan, and it can work for you. The 90-day plan discussed at the end of this chapter is an excellent vehicle. +> - Aim for early wins in areas important to the boss!. Whatever your own priorities, figure out what your boss cares about most. What are his priorities and goals, and how do your actions fit into this picture? Once you know, aim for early results in those areas. ” +> - Pursue good marks from those whose opinions your boss respects. Your new boss’s opinion of you will be based in part on direct interactions and in part on what she hears about you from trusted others. ” +> - Simply be alert to the multiple channels through which information and opinion about you will reach your boss.” + +Furthermore, having these **5 conversation is a must**: + +> 1. The situational diagnosis conversation. In this conversation, you seek to understand how your new boss sees the STARS portfolio you have inherited. Are there elements of start-up, turnaround, accelerated growth, realignment, and sustaining success? How did the organization reach this point? What factors—both soft and hard—make this situation a challenge? What resources within the organization can you draw on? Your view may differ from your boss’s, but it is essential to grasp how she sees the situation. +> 2. The expectations conversation. Your goal in this conversation is to understand and negotiate expectations. What does your new boss need you to do in the short term and in the medium term? What will constitute success? Critically, how will your performance be measured? When” “your boss’s expectations are unrealistic and that you need to work to reset them. Also, as part of your broader campaign to secure early wins, discussed in the next chapter, keep in mind that it’s better to underpromise and overdeliver.” +> 3. The resource conversation. This conversation is essentially a negotiation for critical resources. What do you need to be successful? What do you need your boss to do? The resources need not be limited to funding or personnel. In a realignment, for example, you may need help from your boss to persuade the organization to confront the need for change. Key here is to focus your boss on the benefits and costs of what you can accomplish with different amounts of resources. +> 4. The style conversation. This conversation is about how you and your new boss can best interact on an ongoing basis. What forms of communication does he prefer, and for what? Face-to-face? Voice, electronic? How often? What kinds of decisions does he want to be consulted on, and when can you make the call on your own? How do your styles differ, and what are the implications for the ways you should +> 5. The personal development conversation. Once you’re a few months into your new role, you can begin to discuss how you’re doing and what your developmental priorities should be. Where are you doing well? In what areas do you need to improve or do things differently? Are there projects or special assignments you could undertake (without sacrificing focus)? + +"First 90 days" is ostensibly a book about onboarding executives, but it's a useful resource for any role. It outlines a great strategy for the first months of being in a new role and supplements it with direct, actionable advice. diff --git a/content/posts/caddy2-and-tcp-proxying.md b/content/posts/caddy2-and-tcp-proxying.md new file mode 100644 index 0000000..09e9985 --- /dev/null +++ b/content/posts/caddy2-and-tcp-proxying.md @@ -0,0 +1,79 @@ ++++ +title = "Caddy 2 and TCP Proxying" +author = ["Dmitry Markushevich"] +date = 2022-03-14 +lastmod = 2023-05-22T12:33:10-07:00 +tags = ["DevOps"] +draft = false ++++ + +How to configure Caddy2 to proxy TCP streams. + +To illustrate the architecture, I have the following entities running in Docker. + +{{< figure src="/ox-hugo/gateway-overview-v1.png" >}} + +I'm using [Caddy 2](https://caddyserver.com) as a reverse proxy, but it looks like it supports many more scenarios. Previously I used nginx, or traeffic. The appeal of Caddy is that it supports TLS (with LetsEncrypt) out of the box and integrates with [Consul](https://caddyserver.com/docs/modules/caddy.storage.consul) for an eventual clustering solution with Nomad. + +Startup with Caddy was very simple. Configuration with `Caddyfile` is quite straightforward. I was up and proxying internal HTTP services in no time. I did hit a snag when I needed to proxy non HTTP, in this case an MQTT stream. Turns out it's not very difficult but there's no good description of how to do it. + +First, you need to build caddy with [caddy-l4](https://github.com/mholt/caddy-l4) plugin: + +```dockerfile +FROM caddy:2-builder AS builder + +RUN xcaddy build \ + --with github.com/mholt/caddy-l4 + +FROM caddy:2 + +COPY --from=builder /usr/bin/caddy /usr/bin/caddy +``` + +Since `caddy-l4` does not support `Caddyfile` s, the configuration needs to be in JSON. Since I already started with a `Caddyfile`, I converted it to JSON: + +```sh +caddy adapt —config Caddyfile —adapter Caddyfile +``` + +I then merged the output with the following manually crafted JSON: + +```json +"layer4": { + "servers": { + "servername": { + "listen": ["0.0.0.0:1883"], + "routes": [ + { + "handle": [ + {"handler": "tls"}, + {"handler": "proxy", "upstreams": [{"dial": ["mqtt-server:1883"]}]} + ] + } + ] + } + } +} +``` + +This JSON directs Caddy to: + +1. establish a connection on port 1883 +2. terminate the TLS +3. pass the unencrypted connection to `mqtt-server` (this hostname is provided and resolved by Docker) + +Finally, I had to modify the default execution string for the caddy container in my `docker-compose.yml`: + +```yml +caddy: + image: falcon-caddy:2 + restart: unless-stopped + ports: + - "80:80" + - "443:443" + - "1883:1883" # mqtt + volumes: + - ./caddy/caddyfile.json:/etc/caddy/caddyfile.json + environment: + command: ['caddy', 'run', '-config', '/etc/caddy/caddyfile.json'] +``` diff --git a/content/posts/diy-lithium-battery-part-1.md b/content/posts/diy-lithium-battery-part-1.md new file mode 100644 index 0000000..75e72ea --- /dev/null +++ b/content/posts/diy-lithium-battery-part-1.md @@ -0,0 +1,50 @@ ++++ +title = "Building a lithium ion battery, part 1: “Why”" +author = ["Dmitry Markushevich"] +date = 2019-03-12 +lastmod = 2023-05-22T12:33:08-07:00 +draft = false ++++ + +Why build a battery? Why build a battery from scratch? I’ll tell you. + + +## Why build a battery from scratch? {#why-build-a-battery-from-scratch} + + + +Battery technology is becoming very important. Cell phones, laptops and now cars rely on batteries for power. More importantly, I needed a battery unit for reasons (more on this later). + +I could’ve purchased a battery off the shelf, but consumer units are quite expensive. For example [[Goal Zero Yeti 400 Lithium]() is $800. I thought I could build one for about a 1/3 of the price. + +I was also curious to understand how modern lithium batteries work. So really, the combination of wanting to save money and nerd out and learn something about electronics/battery tech were the two driving reasons behind this experiment. + + +## Why do I need a battery at all? {#why-do-i-need-a-battery-at-all} + + + +I’d like to have a massive power bank for when we go camping. I also have a small portable projector for watching movies when we’re car camping. I know purists will disagree, but here we are. Also nerding out. + + +## Why lithium batteries? {#why-lithium-batteries} + + + +Briefly, there are two types of batteries: + +- lead acid - like the starter battery in your car +- lithium ion- like the one in your cell phone + +Lead acid tend to be easier to maintain, while lithium ion are quite finicky. However lead acid battery are typically 3-5 times heavier than an equivalent lithium ion battery. Lithium ion batteries have a better cycle life and are therefore more energy dense. + +I also thought I could go on craigslist and purchase some reclaimed lithium ion cells to bring the overall cost down. + +Overall cost to performance ratio is in favour of lithium ion cells. + +In the next instalment I’ll describe my prototyping efforts. Here’s the teaser, a functional 4S6P 18650 battery. + +{{< figure src="/ox-hugo/screenshot_2019-07-12_11-06-27.png" >}} + +\#blog +\#public diff --git a/content/posts/how-to-check-for-x-sendfile.md b/content/posts/how-to-check-for-x-sendfile.md new file mode 100644 index 0000000..cb895f1 --- /dev/null +++ b/content/posts/how-to-check-for-x-sendfile.md @@ -0,0 +1,37 @@ ++++ +title = "How to check for X-Send-File (or X-Accel-Redirect)" +author = ["Dmitry Markushevich"] +date = 2018-09-11 +lastmod = 2023-05-22T12:33:09-07:00 +tags = ["Development"] +draft = false ++++ + +Some web requests should not be handled by the application framework. Requests hitting a dynamic API endpoint should be processed by application (in my case typically Ruby on Rails). Static assets (such as files) should be served by the webserver, bypassing Ruby on Rails completely. + +Why? + +There are a couple of reasons. + +The biggest one is performance. NGINX and Apache are significantly better and faster at serving files than a Ruby process. It’s the whole reason for their existence. + +Secondly, leaving Ruby processes out of serving static files frees that process up to serve dynamic requests. + +For example, here Ruby is serving a font file. It ties the ruby process up for over half a second for no reason. + +```bash { hl_lines=["8"] } +I, [2018-09-02T22:50:21.284547 #33169] INFO — : Sent file ff133.ttf (0.3ms) +I, [2018-09-02T22:50:21.284839 #33169] INFO — : Completed 200 OK in 633ms (ActiveRecord: 61.4ms) +``` + +It should be noted, that webservers typically have hundreds of threads serving requests, whereas the application (Ruby on Rails) has on the order of tens of processes. So tying a ruby process up to serve files is wasteful. + +The alternative is [XSendfile](). Once the application determines that it does not need to serve the file it can signal the webserver to take over. + +I always struggle to remember how to identify requests served by Rails vs requests served by the web server. + +Turns out it’s pretty simple. + +If the response contains \`X-Request-ID\` header, then it was served by Rails. Otherwise it was served by the webserver. + +[Matt Bricston]() has more info. diff --git a/content/posts/journaling-prompts-with-emacs.md b/content/posts/journaling-prompts-with-emacs.md new file mode 100644 index 0000000..6758ae0 --- /dev/null +++ b/content/posts/journaling-prompts-with-emacs.md @@ -0,0 +1,51 @@ ++++ +title = "Journaling prompts in Emacs" +author = ["Dmitry Markushevich"] +date = 2022-07-17 +lastmod = 2023-05-22T12:33:08-07:00 +tags = ["Emacs", "Misc"] +draft = false ++++ + +One of my daily rituals is journaling. It's been shown to have positive impact on quality of life (citation needed). I find it helps me decompress but it's also a permanent record of my thoughts and my life's minutiae. + +I usually set a timer for 5 minutes and go to town. One particular challenge I have, is that some times I don't really know what to write about. Recently I came upon the idea of journaling prompts. So far, journaling prompts come in the form of questions: + +> What do I know to be true that I didn’t know a year ago? +> +> What distractions get in the way of being my most productive? + +In Emacs, it's trivial to automate prompt generation. I used org-mode's capture templates and here's what I came up with: + +A function to grab a prompt: + +```elisp +(defun dm/get-journaling-prompt () + "Returns a single line from journaling prompts." + (save-window-excursion + (find-file (concat org-roam-directory "journaling_prompts.org")) + (goto-char (point-max)) + (let* ((number-of-prompts (- (line-number-at-pos) 10))) + (goto-line (+ 10 (random number-of-prompts))) + (s-chomp (thing-at-point 'line t))))) +``` + +and then in my capture templates: + +```elisp +("dj" "Journal" entry + (file+olp+datetree ,(concat org-directory "/personal-daily-2022.org")) + "* Entered on %U + + Prompt: %(dm/get-journaling-prompt) + +%?") +``` + +It's wonderfully simple, the `%(dm/get-journaling-prompt)` simply executes the expression and returns the result. As usual, more in the [docs](https://orgmode.org/manual/Template-expansion.html). + +The result looks like this: + +{{< figure src="/ox-hugo/2022-07-17_13-10-28_screenshot.png" >}} + +It's an easy way to get journaling if nothing else comes up. diff --git a/content/posts/managing-secrets-in-ios-apps.md b/content/posts/managing-secrets-in-ios-apps.md new file mode 100644 index 0000000..9fd42a2 --- /dev/null +++ b/content/posts/managing-secrets-in-ios-apps.md @@ -0,0 +1,70 @@ ++++ +title = "Managing secrets in open source iOS apps" +author = ["Dmitry Markushevich"] +date = 2020-01-23 +lastmod = 2023-05-22T12:33:09-07:00 +tags = ["Development", "OpenSource", "iOS"] +draft = false ++++ + +One of the issues that open source authors have to deal with is secrets management. The small utility app I'm working on relies on Dropbox API. Before you can work with Dropbox API though, you have to generate an API key. +The API key identifies the application to Dropbox and needs to remain mostly secret. A bad actor could impersonate the application author by stealing the API key. + +This is a long winded way of saying that API keys must be kept out of public repositories. How can this be achieved? + +Turns out it's fairly straightforward. Xcode supports configuration schemes, via `xcconfig` files. + +The process is straightforward. + +1. Create a new configuration settings file (via File > New File, use the filter box) +2. Add the settings to file the (the format is `key = value`) +3. Specify the configuration in project target +4. Configure the `Info.plist` file (see below) +5. Use in code. + + +## Config file and settings {#config-file-and-settings} + + + +{{< figure src="/ox-hugo/screenshot_2020-01-24_11-48-51.png" >}} + + +## Contents of the config file {#contents-of-the-config-file} + + + +{{< figure src="/ox-hugo/screenshot_2020-01-24_11-54-09.png" >}} + + +## Info.plist {#info-dot-plist} + + + +{{< figure src="/ox-hugo/screenshot_2020-01-24_11-55-34.png" >}} + +qq\*\*\*\* Using the variables in code + + + +`Info.plist` values are going to be set at compile time. To retrieve them something like this can be used: + +`let dropBoxApiKey = Bundle.main.object(forInfoDictionaryKey:"DROPBOX_API_KEY")` + + +## References {#references} + + + +There's some additional info in the following posts: + +- [Managing secrets within an iOS app | Lord Codes](https://www.lordcodes.com/posts/managing-secrets-within-an-ios-app?utm_source=medium&utm_medium=article&utm_campaign=ios_app_development) +- [Keeping secrets out of Git in iOS](https://medium.com/ios-os-x-development/keeping-secrets-out-of-git-in-your-ios-app-c01a357e824b) +- [Using Xcode Configuration (.xcconfig) to Manage Different Build Settings](https://www.appcoda.com/xcconfig-guide/) + + +## PS: Removing sensitive data from git history {#ps-removing-sensitive-data-from-git-history} + + + +I would be remiss if I didn't include instructions [on how to remove sensitive data from you repository's history](https://medium.com/bam-tech/remove-sensitive-information-from-your-git-repository-10cb421f1b84). diff --git a/content/posts/ncdu-ncurses-disk-usage.md b/content/posts/ncdu-ncurses-disk-usage.md new file mode 100644 index 0000000..6eeabaa --- /dev/null +++ b/content/posts/ncdu-ncurses-disk-usage.md @@ -0,0 +1,20 @@ ++++ +title = "ncdu - ncurses disk usage" +author = ["Dmitry Markushevich"] +date = 2016-12-07 +lastmod = 2023-05-22T12:33:09-07:00 +tags = ["Linux"] +draft = false ++++ + +{{< figure src="/ox-hugo/screenshot_2019-07-12_11-15-18.png" >}} + +At some point harddrive space was cheap, but with the advent of SSDs and cheap “Cloud VMs” that is no longer the case. For example, the cheapest VM on VULTR is $5/month and has a 15 gig SSD drive. It's suddenly very important to maximize drive usage again. This is where [ncdu]() comes in. It displays a nice ncurses interface that visualizes the usage breakdown. + +So useful key bindings: + +- \`?\` - help +- \`g\` - to switch display modes +- \`d\` - to kill a subdirectory tree + +There's a commercial app for Mac that I use and love (because my Mac only has a 256 SSD) called [DaisyDisk](). diff --git a/content/posts/note-taking-and-discovery.md b/content/posts/note-taking-and-discovery.md new file mode 100644 index 0000000..777edcb --- /dev/null +++ b/content/posts/note-taking-and-discovery.md @@ -0,0 +1,47 @@ ++++ +title = "Note taking and discovery" +author = ["Dmitry Markushevich"] +date = 2022-03-09 +lastmod = 2023-05-22T12:33:09-07:00 +tags = ["Orgmode", "Emacs", "Productivity"] +draft = false ++++ + +I've been taking notes consistently, probably starting in university. The challenge for me was never capture but retrieval. I suspect that's true for most people. + +The interesting thing about zettelcasten is how Luhramn optimized his sytem for retrieval. Related subjects were proximate, he used a linking system of sorts that allowed his cards to be filed under number of different topics. + +In digital note taking, proximity is difficult to simulate. Hyperlinking is the first step of course. My problem with hyperlinks is that they become lost in the content of the note. The only way to discover them is by scanning the text. This is very slow. + +A fairly recent development within note taking application is the "graph view". + +{{< figure src="/ox-hugo/2022-03-10_13-30-32_display.png.png" >}} + +I can't recall where I saw this first; it's not really that important. The fact is more and more note taking tools support this functionality. It's an improvement on "simple" hyperlinking, because it arranges concepts within a virtual space. In a way, very similar to how Luhrman did it originally with his zettels. This enhances retrievability and discoverability in a huge way. + +I'm reading a book, "The Jakarta Method" about the US approach to the communist thread after World War 2. At the top of my notes file, I have meta link to: + +- books +- current year + +and other relevant metadata. + +Then within the notes themselves I may have links to my other notes: + +- a link to Indonesia, since this is primarily the book about this country. +- a link to Communism +- a link to United States + +and many more links to all the relevant topics. + +So the concept map may looks like this: + +{{< figure src="/ox-hugo/2022-03-10_13-46-31_screenshot.png" >}} + +All the nodes are clickable. If I click on Indonesia, I then get an expanded view of how Indonesia fits into my knowledge base: + +{{< figure src="/ox-hugo/2022-03-10_13-48-15_screenshot.png" >}} + +I see that I have a "travel destination" node linked to Indonesia so I likely found somewhere interesting to visit there. It's also linked to Buddhism and World indicating further relationships. + +This is a big deal for data discoverability. Now instead of getting bogged down in details, I can get a high level overview of my concepts. Just as important I can find related concepts and link them. diff --git a/content/posts/omnifocus-review-with-org-mode.md b/content/posts/omnifocus-review-with-org-mode.md new file mode 100644 index 0000000..4426cd0 --- /dev/null +++ b/content/posts/omnifocus-review-with-org-mode.md @@ -0,0 +1,44 @@ ++++ +title = "Omnifocus style reviews with org-mode" +author = ["Dmitry Markushevich"] +date = 2020-01-07T00:00:00-08:00 +lastmod = 2023-05-22T12:33:08-07:00 +tags = ["Emacs", "orgmode"] +draft = false ++++ + +I started using Emacs and org-mode in earnest in the middle of 2019. At that point in time I was using Omnifocus to track my tasks and Bearapp for notes. I was missing +plaintext functionality. Since then I've reproduced most of the functionality that Omnifocus offerred except one particular feature: reviews. + +In GTD, reviews occupy a pretty important niche. Without regular task and project reviews task lists tend to grow out of control and become polutted. One important consideration for a +task list is for it to closely resemble the actual state of what you have to do. + +I went looking for a way to do automated reviews in org, and found [org-review](https://github.com/brabalan/org-review). + +org-review relies on the `LAST_REVIEW` property to determine when the next review (`NEXT_REVIEW`) should happen and out of the box this property is not generated. + +I wrote a bit of elisp to auto generate these properties for all `TODO` entries when a file is saved. This guarantees that the entry will bubble up for review a month after creation. Month is a default review duration +that can be configured via `REVIEW_DELAY` property. + +Add this somewhere in `~/.emacs` or similar. + +```elisp +(defun my/org-add-next-review-property () + "Add NEXT_REVIEW property to headlines that don't have one" + (interactive) + (org-map-entries '(unless (org-entry-get nil "NEXT_REVIEW") (org-review-insert-last-review)) "TODO=\"TODO\"") +) + + +(add-hook 'org-mode-hook + (lambda () + (add-hook 'before-save-hook 'my/org-add-next-review-property 'local))) +``` + +The process will look something like this: + +1. Add a `TODO` task to inbox +2. Once a file is saved the `*_REVIEW` properties are generated. +3. ... some time passess ... +4. Hit `C-c a R` to start the review mode. +5. Hit `C-c C-r` to review selected items. diff --git a/content/posts/open-source-contribution.md b/content/posts/open-source-contribution.md new file mode 100644 index 0000000..be61737 --- /dev/null +++ b/content/posts/open-source-contribution.md @@ -0,0 +1,28 @@ ++++ +title = "Achievement Unlocked: Open Source Contribution" +author = ["Dmitry Markushevich"] +date = 2022-07-24 +lastmod = 2023-05-22T12:33:09-07:00 +tags = ["Development", "Emacs", "OpenSource"] +draft = false ++++ + +I did it. My Emacs package, [org-hyperscheduler](https://github.com/dmitrym0/org-hyperscheduler), is now live on MELPA, [here](https://melpa.org/#/org-hyperscheduler). + +I'm overjoyed. It's a big deal for me, because I've been a long time beneficiary of open source software and I'm glad to finally be able to give back. It's the first piece of software that I started out writing for myself and got to a state where it's useable for other folks. + +Once version 1 was stable, I submitted a [pull request](https://github.com/melpa/melpa/pull/8057) to MELPA. Prior to that I ran through their [checklist](https://github.com/melpa/melpa/blob/master/CONTRIBUTING.org). The checklist ensures that style guides are followed, deployment recipe works correctly, etc. Even though I went through the checklist twice, I still managed to miss a couple of things. The reviewer kindly pointed it out: + +{{< figure src="/ox-hugo/2022-07-25_10-41-45_screenshot.png" >}} + +A few more rounds of back and forth and: + +{{< figure src="/ox-hugo/2022-07-25_10-42-37_screenshot.png" >}} + +As of July 25, MELPA shows 37 downloads. There are 85 stars on github (and 5 forks): + +{{< figure src="/ox-hugo/2022-07-25_10-45-20_screenshot.png" >}} + +There is an ocassional [issue](https://github.com/dmitrym0/org-hyperscheduler/issues/19) being submitted via GitHub, so the package is being used. + +As a software engineer this makes me very happy. diff --git a/content/posts/org-noter-official-maintainer.md b/content/posts/org-noter-official-maintainer.md new file mode 100644 index 0000000..20c6e30 --- /dev/null +++ b/content/posts/org-noter-official-maintainer.md @@ -0,0 +1,32 @@ ++++ +title = "org-noter: I'm an official maintainer!" +author = ["Dmitry Markushevich"] +date = 2023-05-22T00:00:00-07:00 +lastmod = 2023-05-28T09:52:18-07:00 +tags = ["OpenSource", "org-noter"] +draft = false ++++ + +I haven't contributed to open source software much until last year. It's been an interesting and rewarding process. + +My first large contribution was last year when I released my scheduling package for Emacs, [org-hyperscheduler.](https://github.com/dmitrym0/org-hyperscheduler) Later in 2022 I found a fantastic package for taking notes, [org-noter](https://github.com/org-noter/org-noter). + +When I dicovered org-noter in the summer of 2022, it was not actively maintained. The last update was at the end of 2019. I asked if [anyone wanted to collaborate on a fork](https://github.com/weirdNox/org-noter/pull/129#issuecomment-1181977664) on github and made a post on reddit. + +The first feature I wanted to add to org-noter was highlighting. I made a [youtube video ](https://www.youtube.com/watch?v=HCOXva1Ndfk)about it and posted it in /r/emacs and /r/orgmode. Around that time, Peter Mao reached out to me. He has been using org-noter extensively and wanted to collaborate on maintaining it. We spent some time chatting about what our goals are for the project. + +Since then we have: + +- added precise highlighting to PDF mode +- standardized the note taking shortcuts +- fixed a few bugs +- added unit tests +- added a continuous integration workflow (via github actions). + +The biggest change has been taking over maintenance from the original author, Gonçalo Santos. Both myself and Peter have tried contacting him via various means with no success. The goal was to continue updating org-noter's current users via MELPA. In the end Peter contacted MELPA admins and they have assigned the ownership to us. + +The feedback from the existing community has been positive. + +Not only am I "[scratching my own itch](https://37signals.com/podcast/scratch-your-own-itch/)" but I'm also giving back to the OSS community. I've been enjoying the open source development process. Though "[The Cathedral and Bazaar](https://en.wikipedia.org/wiki/The_Cathedral_and_the_Bazaar)" is controversial, there is a lot of truth in it. org-noter is growing and becoming better through no effort of a centralized authority. Rather, people are contributing to it in a decentralized fashion. + + diff --git a/content/posts/personal-agile-iteration-1.md b/content/posts/personal-agile-iteration-1.md new file mode 100644 index 0000000..1a428e0 --- /dev/null +++ b/content/posts/personal-agile-iteration-1.md @@ -0,0 +1,47 @@ ++++ +title = "Personal Agile: Iteration 1" +author = ["Dmitry Markushevich"] +date = 2020-01-25 +lastmod = 2023-05-22T12:33:08-07:00 +tags = ["Agile", "Productivity", "Orgmode"] +draft = false ++++ + +There's lots to love about agile, but my personal faves are two things: + +1. timeboxed iterations +2. retrospectives + +The ultimate goal of agile is to shorten feedback loops. If something is not working it's easier to identify and correct the issue at the point of occurence rather than months down the line. This of it as a `REPL` +loop for productivity. + +I've been quite successful doing this at work, so I figured why not try it for personal productivity as well? I've had a couple of projects on the go for a while. I've made little progress so I wanted to focus and get things truely completed. + +Here's what my plan for the first iteration looked like: + +{{< figure src="/ox-hugo/screenshot_2020-01-25_09-49-33.png" >}} + +Now, looking at it, I wish I followed more of the agile methodology and properly worded the tasks/stories I wanted to do. However I did do some things right: + +- I identified specific things I wanted to do. +- I did identify completion criteria: + - In the case of the reading goal it was "half a book". + - For my app it was 3 hours. + - For my fitness goal it was 10 times. +- I did provide rough estimates for each activity to ensure that the plan was doable. + +I also made an attempt at time tracking with `org-pomodoro`: + +{{< figure src="/ox-hugo/screenshot_2020-01-25_09-56-19.png" >}} + +I'm not sure how to use these results just yet, but they do provide an intersting insight. The bulk of the coding project work was done early AM before work/family stuff on weekdays. I will continue with this practice. + + +## Conclusions {#conclusions} + + + +Overall, I've achieved the goals I was striving for. This list allowed me to focus on the projects I wanted to push forward. Waking up at 5:30am on weekdays left me with enough mindspace and energy to work on personal projects, while chores and other tasks could be done post work. +The fact that I eyeballed the amount of work I could actually complete allowed me to be successful. I did not get overly amibitious. I wanted to see how consistent I could be with this and overall I was. + +One of the goals for next iteration is to develop better tracking to better understand how much work I can truely commit to and still be productive. diff --git a/content/posts/personal-agile-iteration-2.md b/content/posts/personal-agile-iteration-2.md new file mode 100644 index 0000000..1773e9c --- /dev/null +++ b/content/posts/personal-agile-iteration-2.md @@ -0,0 +1,33 @@ ++++ +title = "Personal Agile: Iteration 2" +author = ["Dmitry Markushevich"] +date = 2020-02-09 +lastmod = 2023-05-22T12:33:08-07:00 +tags = ["Agile", "Productivity", "Orgmode"] +draft = false ++++ + +The goal for this iteration was to: + +- work on my little SwiftUI app +- finish reading a book ("The first 90 days") +- maintain my fitness habits + +{{< figure src="/ox-hugo/screenshot_2020-02-09_21-06-26.png" >}} + + +## Conclusions {#conclusions} + +Overall the iteration went well. In the screenshot above, you can tell that I wildly underestimated how much time summarizing the current book would take: I estimated an hour, but it ended up being 3 times as much (3 hours). + +Then, I spent way too much on my app, 10 pomodoros (roughly 5 hours) vs 3 hours initially estimated. + +The big fitness goals (workouts) were met, but smaller (morning stretches) didn't pan out. Oh well. + +I also forgot to add time to plan for the next iteration, although I did include a task to create a template (which I didn't do). + +So, moving forward: + +- create an iteration template (that should include: estimation task and postmortem task). +- record the velocity (current sprint is somewhat unusual: I'm on vacation). +- be very careful of over-committing. Err on the side of not having enough work (to avoid guilt etc). diff --git a/content/posts/personal-agile-iteration-4.md b/content/posts/personal-agile-iteration-4.md new file mode 100644 index 0000000..0d10c5a --- /dev/null +++ b/content/posts/personal-agile-iteration-4.md @@ -0,0 +1,41 @@ ++++ +title = "Personal Agile: Iteration 4" +author = ["Dmitry Markushevich"] +date = 2020-03-09 +lastmod = 2023-05-22T12:33:08-07:00 +tags = ["Agile", "Productivity", "Orgmode"] +draft = false ++++ + +Interesting iteration. Overall agile approach has been a net positive. I likely would've have been approximately as successful, but deliberate approach to planning and reviews yields interesting artifacts (of which this blog post is one) and provides for an interesting trajectory overview. + +I made good progress on my tasks this iteration: + +- adding two additional chapters reviewed to "First 90 days". It's becoming a bit tedious, but I feel it's critical to finish this book. +- moving forward with my `org-friend` SwiftUI project. + +In terms of my application, I feel like I've committed two cardinal sins of software engineering + +1. I started gold plating. +2. I added more work to an iteration that's already in progress. + + +## Scope creep and gold plating {#scope-creep-and-gold-plating} + +When I initially started this project, the goal was a really simple utility application. Which means minimal UI. However as things progressed, it became apparent that there needs to be a splash screen, a settings screen and a "log view". Even though the fundamentally the application has been working since the second iteration, I haven't been happy with the overall visual appearance. So I'm working on that now. I reached out to some folks that I know, but they are busy. I approach some contractors on Fiverr with some success. In any case, the takeaway here is that suddenly I'm spending more time on look and feel for an application that's supposed to spend most of it's time in the background. The justification I use is that since this is a showcase for my work, a portfolio piece, I want it to look nice. First impressions and all. + +Here's a UI designed by a developer. No good. + +{{< figure src="/ox-hugo/screenshot_2020-03-09_11-07-09.png" >}} + + +## Adding work to an iteration in progress {#adding-work-to-an-iteration-in-progress} + +I got mixed feelings about this one. Personally I don't think this is a huge problem if negotiated properly. In a team of 1 it's really easy. In a larger team this would have to be managed very carefully and be an exception. I'm doing this for fun largely so I roll with it. + + +## Conclusion {#conclusion} + +Overall a healthy and productive iteration. I made progress on both of my little projects. One area where I hope to improve is time tracking. I know I'm missing time here. This is largely the function of learning Emacs. + +{{< figure src="/ox-hugo/screenshot_2020-03-09_11-09-27.png" >}} diff --git a/content/posts/personal-agile-iteration-5.md b/content/posts/personal-agile-iteration-5.md new file mode 100644 index 0000000..5d51e43 --- /dev/null +++ b/content/posts/personal-agile-iteration-5.md @@ -0,0 +1,19 @@ ++++ +title = "Personal Agile: Iteration 5 (COVID-19 edition)" +author = ["Dmitry Markushevich"] +date = 2020-03-24 +lastmod = 2023-05-22T12:33:08-07:00 +tags = ["Agile", "Productivity", "Orgmode", "UIUX"] +draft = false ++++ + +It's been pretty hard to focus on anything other than what's going on in the world the last two weeks. I didn't really successfully complete the iteration. We've been sent home from work about a week ago which freed up +a lot of time that's been allocated to the commute. Theoretically. Unfortunately focus has been hard to come by. + +The only take away I have from this iteration is that I've significantly reversed my position on UI/UX. + +I've always been of the opinion that functionality should be prioritized over look and feel. Now I'm not sure that it's so cut and dry any more. I don't know when this happened but looking at the last 3 iterations; the vast majority of work has been in the UIUX area. + +Why? One of the goals of this application is to showcase my abilities as a developer, and first impressions are really important. I did not want to deliver and ugly app. I'm still processing this. UI work is definitely not easy. Working on UI reduces the number of hours I can spend on other things. + +Previously, I'd make an argument that I'd rather use an ugly app that does a lot rather than app that is pleasant that does only a little bit. I'm still not sure where to draw that line, but it's interesting to think about. diff --git a/content/posts/raid5-multiple-disk-recovery.md b/content/posts/raid5-multiple-disk-recovery.md new file mode 100644 index 0000000..0bec43d --- /dev/null +++ b/content/posts/raid5-multiple-disk-recovery.md @@ -0,0 +1,28 @@ ++++ +title = "RAID5: Recovering from 2-drive failure during rebuild" +author = ["Dmitry Markushevich"] +date = 2015-07-13T00:00:00-07:00 +lastmod = 2023-05-22T12:33:08-07:00 +tags = ["Linux"] +draft = false ++++ + +RAID is amazing technology. It lets us take a bunch of cheap disks an arrange them in various configurations that present these disks as one large disk. A particularly popular type of RAID for home users is RAID5. A RAID5 array is typically comprised of 3 disks, however only 2/3 of the disk space is available since 1/3 of the space is used for recovery purposes if one of the disks fail. This is in fact the configuration I’ve arranged my home RAIDi in. I have a RAID5 that consists of 3 disks, 2TB each. The usable space is 4TB with the remaining 2TB used for redundancy (parity). + +By design, RAID5 can easily handle a single drive failure but it turns out that single drive failure frequently leads to second drive failure which is not something that RAID5 can handle. Let me explain. + +With a single drive failure, you simply purchase a new disk with similar physical characteristics and replace it in the array. When the RAID management system detects a new disk, it attempts to rebuild the array using the redundant data available on the rest of the array. This process touches every piece of data on the existing drives and sometimes causes a second drive failure. The common wisdom is that with the second drive gone it is no longer possible to rebuild the array. However depending on the actual failure type this is not necessarily so. I know because I recently went through this exact scenario. + +One of my drives started throwing S.M.A.R.T. errors a few months back so I decided to replace it before it failed catastrophically. I inspected the other two drives in the array and they showed no errors. I replaced the malfunctioning disk, initiated array rebuild, and went to sleep with the rebuild process progressing nicely. In the morning, I saw that the rebuild has failed with linux kernel reporting hardware errors trying to access an existing drive in the array. + +At this point in time my RAID5 looked like this: + +- Disk 1: Healthy (from original array) +- Disk 2: Hardware failure #2 (from original array) +- Disk 3: Blank (new disk to replace hardware failure #1) + +At this point I despaired and gave up on the whole affair. I knew I had backups for the most important data (family pictures) I really didn’t save anything other than that: school projects, music. While it wasn’t catastrophic, the idea of trying to recover all of this data manually was too heartbreaking so I wanted to give my RAID one more chance. I started googling. + +Turns out that hard drive failures may be localized and a wonderful tool called [ddrescue](http://www.gnu.org/software/ddrescue) may be the way out. During the rebuild RAID software reads the data from the drive sequentially and aborts when it encounters an error. `ddrescue` attempts to avoid the damaged sectors by reading the data forwards and backwards. So, I purchased another empty disk and managed to copy the data off the damaged disk. At the end of the process `ddrescue` reported that it failed to copy 140kBytes or so, which everything considered is not so bad. I added the drives back to the array and it worked! The rebuild went smoothly and now I’m back to a functioning 3 disk RAID5 array. Even though 140kB are technically gone, I haven’t been able to identify what’s missing. Some sort of checksumming would be great for next time. + +So, it is possible to recover from two disk failure on Linux software RAID5 depending on the actual failure condition. In the future, I’ll be more proactive in replacing drives. Additionally it makes sense to increase the number of drives in the array to increase fault tollerance. Finally, it would be nice to have some automated monitoring, perhaps with monit and pushbullet. diff --git a/content/posts/reading-font-from-file-and-making-it-available-to-cocoa.md b/content/posts/reading-font-from-file-and-making-it-available-to-cocoa.md new file mode 100644 index 0000000..4cfea3c --- /dev/null +++ b/content/posts/reading-font-from-file-and-making-it-available-to-cocoa.md @@ -0,0 +1,38 @@ ++++ +title = "Reading a font from file an making it available to Cocoa" +author = ["Dmitry Markushevich"] +date = 2009-02-06T00:00:00-08:00 +lastmod = 2023-05-22T12:33:08-07:00 +tags = ["Cocoa", "MacOS"] +draft = false ++++ + +This is another programming related post. + +Say you have a true type font that's not part of the OS font set in a file. You've read the contents of the file into memory and now want to make it available to Cocoa. How? + +Turns out that ATSUI comes to the rescue: + +```C++ + +ATSFontContainerRef container; +OSStatus status = ATSFontActivateFromMemory((LogicalAddress)[fontData bytes], // buffer with font data + [fontData length], // size of font data + kATSFontContextLocal, // for use only in this application + kATSFontFormatUnspecified, // reserved + NULL, // reserved + kATSOptionFlagsDefault, // reserved + &container); // on output, will contain the activated font +// find the number of font references in the container (goes to numItems) +ItemCount numItems; +status = ATSFontFindFromContainer(container, kATSOptionFlagsDefault, 0, NULL, &numItems); +NSLog(@"There are %d references in the container\n", numItems); +// load the individual fonts +ATSFontRef *ioArray = malloc(numItems * sizeof(ATSFontRef)); +status = ATSFontFindFromContainer(container, kATSOptionFlagsDefault, numItems, ioArray, &numItems); +CFStringRef fontName = nil; +ATSFontGetName (ioArray[fontIndex], kATSOptionFlagsDefault, &fontName); +NSFont* myFont = [NSFont fontWithName:(NSString*)fontName size:24]; +``` + +`myFont` now contains the NSFont reference to your font. Easy. diff --git a/content/posts/safari-app-extensions-migrating-from-safariextz.md b/content/posts/safari-app-extensions-migrating-from-safariextz.md new file mode 100644 index 0000000..7dfee52 --- /dev/null +++ b/content/posts/safari-app-extensions-migrating-from-safariextz.md @@ -0,0 +1,38 @@ ++++ +title = "Safari App Extensions; migrating from .safariextz" +author = ["Dmitry Markushevich"] +date = 2018-10-08 +lastmod = 2023-05-22T12:33:09-07:00 +tags = ["Development", "MacOS"] +draft = false ++++ + +I’ve dabbled with a Safari extension for a project on and off for a while. For Safari 12, I read that .safariextz-style extensions are no longer supported. I went forth to investigate what changed and how I could port my old extension to Safari 12. + +The short story is that the packaging has changed, but the bulk of the existing extension should just work once new conventions are adopted. I imagine the new changes are spurred by Apple’s desire to unify distribution — new extensions are wrapped as a mac application, require a signature and are distributed through the Mac App Store. + +I went looking for documentation and stumbled upon the [Extending your App with Safari App Extensions - WWDC 2016 - Videos - Apple Developer]() presentation which laid everything out. + +There are three major parts: + +- Content blocking +- Modifying page behaviour +- Extending Safari UI + +The video starts out describing how to develop the new Safari App Extensions. You need Xcode and dev certificate. The new thing here is that Safari App Extensions are distributed as part of a macOS application and therefore can invoke native code from that application. Neat. + +The presenters then go into some specific examples of functionality that Safari App Extensions can have. + +They mention that **content blocking** is built upon a model at compile time (?) so there’s no run time evaluation. This is to achieve required speed. The content blocking API is consistent across macOS and iOS. One interesting tidbit is that the content blockers don’t get access to individual requests, only the “content blocking” model does. This is for privacy reasons. + +The second part was about **Modifying page behavior**, really the part I was looking for. The important info here is that CSS/JS from your old extensions simply transition to the new app. These assets need to be described in the app’s Info.plist, for example for JS you’d use \`SFSafariContentScript\`[0]. There’s a new messaging API for the JS to talk to the native code. + +Final, third demo was related to **Extending Safari UI**. Safari App Extension can extend Safari’s UI via popovers that house \`NSView\`s. It sounded like it’d be relatively easy to extend your app’s existing NSViews to show up in Safari. Not something I’m interested, but nevertheless cool. + +I went searching for more WWDC notes, and haven’t found much[1]. It’s an excellent resource. + +Looks like converting to the new format isn’t so bad. Plus Apple has an [official conversion how to](). + +[0]: More keys in the Info.plist in the [official documentation](). + +[1]: I did find this though:[WWDC 2018 Notes — Procrastinative Ninja](). diff --git a/content/posts/shared-memory-on-os-x.md b/content/posts/shared-memory-on-os-x.md new file mode 100644 index 0000000..dc295c2 --- /dev/null +++ b/content/posts/shared-memory-on-os-x.md @@ -0,0 +1,27 @@ ++++ +title = "Working with shared memory on OS X" +author = ["Dmitry Markushevich"] +date = 2010-04-05 +lastmod = 2023-05-22T12:33:09-07:00 +tags = ["Development"] +draft = false ++++ + +If you're working with Qt's [QSharedMemory](https://duckduckgo.com/?q=qsharedmemory&t=osx&ia=web), on Mac OS X you're working with System V shared memory subsystem. If your data is sizable, the first limit you'll hit in the maximum segment size, which is for some reason around 4 megs. To increase it invoke this magic incantation: + +```bash +sudo sysctl -w kern.sysv.shmmax=33554432 +``` + +and to see other shared mem related kernel variables: + +```bash +sysctl -A|grep shm +``` + +If you'd like these settings to remain after you reboot your machine follow the instruction [here](http://www.spy-hill.net/help/apple/SharedMemory.html). + +The other problem you're likely to hit is that shared memory persists between processes (as it probably should) unless you detach and then destroy the QSharedMemory object. This makes testing somewhat inconvenient. You can monitor + and remove shared segments with `ipcs` and `ipcrm` commands. The first command lists shared segments, the second deletes them. + +Again, I've only verified this on Mac OS X 10.5, but it should work on other Unix-like systems that support System V IPC. diff --git a/content/posts/still-around.md b/content/posts/still-around.md new file mode 100644 index 0000000..a51f9d0 --- /dev/null +++ b/content/posts/still-around.md @@ -0,0 +1,9 @@ ++++ +title = "Still Around" +author = ["Dmitry Markushevich"] +date = 2021-03-22 +lastmod = 2023-05-22T12:33:08-07:00 +draft = false ++++ + +Nearly a year after my previous entry, I'm trying to get into blogging yet again. I've got a huge backlog of posts I'm interested in sharing but it's been a challenge to get through it for various reasons. It's not New Year's, but here's a commitment, write one post every two weeks. Should be doable? diff --git a/content/posts/three-ways-to-context-switch-with-git.md b/content/posts/three-ways-to-context-switch-with-git.md new file mode 100644 index 0000000..b535393 --- /dev/null +++ b/content/posts/three-ways-to-context-switch-with-git.md @@ -0,0 +1,8 @@ ++++ +title = "3 ways to context switch with Git" +author = ["Dmitry Markushevich"] +date = 2022-09-02 +lastmod = 2023-05-22T12:33:09-07:00 +tags = ["Development", "Git"] +draft = false ++++ diff --git a/content/posts/tig-cheat-sheet.md b/content/posts/tig-cheat-sheet.md new file mode 100644 index 0000000..6992521 --- /dev/null +++ b/content/posts/tig-cheat-sheet.md @@ -0,0 +1,46 @@ ++++ +title = "Efficient commit workflow with tig" +author = ["Dmitry Markushevich"] +date = 2018-08-30 +lastmod = 2023-05-22T12:33:08-07:00 +tags = ["Development"] +draft = false ++++ + +One of the recommended source control practices is to [commit frequently, and often](http://stackoverflow.com/questions/107264/how-often-to-commit-changes-to-source-control). I prefer to keep my commits terse and focused, however in practice I’m working on a couple related but independent things in parallel. At commit time, I prefer to tease loosely related things apart and commit them separately. + +One way to do that is with the interactive git-add facility that can be invoked with `git add -p`: + +{{< figure src="/ox-hugo/screenshot_2020-01-07_16-16-56.png" >}} + +It’s a lovely little interface: + +- file name is shown at the top of the output +- location of the change set is shown +- the prompt is asking whether I want to stage the change (`y`), ignore the changes this file (`d`) and various other obscure options. + +This process works for the majority of my commit needs. It allows me to review every change before staging it to ensure that only necessary changes get pushed to the central repo. + +My main issue with this process is that it’s too linear. When the number of changes exceeds a certain threshold I have trouble remembering what functionality I’m currently committing. I can’t easily inspect the existing staged hunks. The process becomes cumbersome. + +This is where [tig](http://jonas.nitro.dk/tig/), a **text-mode interface for Git** comes in. I’m a huge fan of console tools because they are incredibly fast and **tig** is no exception. + +Upon launching **tig** you’re presented with the following view: + +{{< figure src="/ox-hugo/screenshot_2020-01-07_16-21-55.png" >}} + +I’m currently interested in the “unstaged changes”. Hitting `enter` shows a diff between HEAD and current working directory. + +But we’re interested more in the stage view that can be activated by hitting `S` (case matters!): + +{{< figure src="/ox-hugo/screenshot_2020-01-07_16-23-51.png" >}} + +This view should be familiar to anyone who has used `git status`. This main view can be navigated with arrow keys. Hitting `enter` on a file name will show a secondary window with a diff. This view can be navigated with the `j` and `k` keys (vi-like). + +{{< figure src="/ox-hugo/screenshot_2020-01-07_16-19-48.png" >}} + +At this point, we can select individuals lines to stage with the `1` key, or hit `u` to stage the whole file. Individual lines can be staged when there’s some cruft you don’t want to commit - such as debugging statements. + +When all changes have been staged, you can hit `C` to commit from inside **tig**, or exit (`q`) and commit as you normally would from command line. + +Here’s a small [tig cheat sheet](http://ricostacruz.com/cheatsheets/tig.html) for reference. diff --git a/content/posts/tircd-twitter-gateway.md b/content/posts/tircd-twitter-gateway.md new file mode 100644 index 0000000..3afd1ad --- /dev/null +++ b/content/posts/tircd-twitter-gateway.md @@ -0,0 +1,21 @@ ++++ +title = "tircd, irc twitter gateway" +author = ["Dmitry Markushevich"] +date = 2013-03-17T00:00:00-07:00 +lastmod = 2023-05-22T12:33:08-07:00 +tags = ["Linux"] +draft = false ++++ + +I published my [tircd branch](https://github.com/dmitrym0/tircd). If you're +unfamiliar with [tircd](https://code.google.com/p/tircd/) it's a daemon that +presents Twitter as an IRC channel. I merged [this really old branch](https://github.com/DexterTheDragon/tircd) that implements Twitter lists +as channels with the semi official [google code mirror branch](https://github.com/drags/tircd). I created a [pull request](https://github.com/drags/tircd/pull/1) but it hasn't been acted on yet. + +I consume my instant messaging via IRC (with the help of +[minbif](https://symlink.me/projects/minbif/wiki)) and now Twitter too. Quite +convenient. + +This is what my Twitter looks like right now: + +{{< figure src="/ox-hugo/screenshot_2020-01-07_15-34-53.png" >}} diff --git a/content/posts/what-is-hyperscheduling.md b/content/posts/what-is-hyperscheduling.md new file mode 100644 index 0000000..2caa09c --- /dev/null +++ b/content/posts/what-is-hyperscheduling.md @@ -0,0 +1,40 @@ ++++ +title = "What is hyper-scheduling?" +author = ["Dmitry Markushevich"] +date = 2022-03-17 +lastmod = 2023-11-06T17:36:40-08:00 +tags = ["Productivity", "Emacs"] +draft = false ++++ + +## Hyperscheduling?; TLDR {#hyperscheduling-tldr} + +Hyperscheduling is the idea of fully planning your day using a calendar. Every minute of the day is assigned to some task using a calendar. + + +## Hyperscheduling in more detail {#hyperscheduling-in-more-detail} + +Hyperscheduling is only part of a productivity system. It helps me plan my day: + +1. Select a set of tasks for today (this is done outside of hyperscheduling, using GTD or another system). +2. Prioritize your tasks in order of importance. +3. Lay your tasks out on the calendar. + +Step #3 is where the magic is. By visually moving time blocks around on a calendar, I get the following benefits: + +- evaluate whether my estimate for the task is correct. Am I really going to finish this task in 1hr? +- evaluate the order of tasks. Am I really going to be able to switch from Task A to task B with no break? +- surface appointments. I have a standing "lunch" calendar entry that reminds me that lunch time is not available work tasks. + +At the end of this daily process I end up with a list of tasks that I believe I can complete by the end of the day because: + +- I reviewed my effort estimates +- I found a time slot for all of the tasks +- I built in time for breaks and other obligations. + +Everyone is wired differently but it's been hugely successul for me, because I'm able to use realistic criteria for planning my day. Using a visual tool for this removes friction and facilitates the process. + + +## More {#more} + +I first learned about hyperscheduling from MacSparky. He has a great [FAQ](https://www.macsparky.com/blog/2018/03/hyper-scheduling-feedback-2/). diff --git a/hugo-theme-yinyang/LICENSE.md b/hugo-theme-yinyang/LICENSE.md new file mode 100644 index 0000000..624b3f3 --- /dev/null +++ b/hugo-theme-yinyang/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 YOUR_NAME_HERE + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/hugo-theme-yinyang/README.md b/hugo-theme-yinyang/README.md new file mode 100644 index 0000000..2388633 --- /dev/null +++ b/hugo-theme-yinyang/README.md @@ -0,0 +1,90 @@ +# YinYang + +[YinYang](https://en.wikipedia.org/wiki/Yin_and_yang) is a black-white theme for [Hugo](https://gohugo.io/). + +[**Demo**](https://blog.joway.io) + +## Feature + +- minimalist +- multi-language support +- [disqus](https://disqus.com) support +- [SEO Optimization](https://github.com/joway/hugo-theme-yinyang/blob/master/layouts/partials/seo.html) + +## Screenshot + +![](./images/screenshot.png) + +## Installation + +[Install and Use Theme](https://gohugo.io/themes/installing-and-using-themes/) + +## Configuration + +### Multi-Language + +``` +[languages] + [languages.en] + contentDir = "content/en" + languageName = "English" + weight = 1 + [languages.cn] + contentDir = "content/cn" + languageName = "Chinese" + weight = 2 +``` + +Then your posts files should be put into `content/en` or `content/cn`. + +### Footer + +``` +[[params.socials]] +name = "About Me" +link = "https://joway.io" +[[params.socials]] +name = "Github" +link = "https://github.com/joway" +``` + +### Extra Head + +``` +[params] +extraHead = '' +``` + +### Example + +``` +baseURL = "https://blog.joway.io/" +languageCode = "en-us" +title = "Joway's Blog" +theme = "yinyang" +DefaultContentLanguage = "cn" + +[author] + name = "Joway Wang" + homepage = "https://joway.io/" + +[languages] + [languages.en] + contentDir = "content/en" + languageName = "English" + weight = 1 + [languages.cn] + contentDir = "content/cn" + languageName = "Chinese" + weight = 2 + +[params] +disqus = "joway" # disqus account name +extraHead = '' +[[params.socials]] +name = "About Me" +link = "https://joway.io" +[[params.socials]] +name = "Github" +link = "https://github.com/joway" +``` diff --git a/hugo-theme-yinyang/archetypes/default.md b/hugo-theme-yinyang/archetypes/default.md new file mode 100644 index 0000000..ac36e06 --- /dev/null +++ b/hugo-theme-yinyang/archetypes/default.md @@ -0,0 +1,2 @@ ++++ ++++ diff --git a/hugo-theme-yinyang/exampleSite/.gitignore b/hugo-theme-yinyang/exampleSite/.gitignore new file mode 100644 index 0000000..ca4d540 --- /dev/null +++ b/hugo-theme-yinyang/exampleSite/.gitignore @@ -0,0 +1,27 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test + +/public +/themes +.DS_Store diff --git a/hugo-theme-yinyang/exampleSite/LICENSE b/hugo-theme-yinyang/exampleSite/LICENSE new file mode 100644 index 0000000..4527efb --- /dev/null +++ b/hugo-theme-yinyang/exampleSite/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Steve Francia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/hugo-theme-yinyang/exampleSite/README.md b/hugo-theme-yinyang/exampleSite/README.md new file mode 100644 index 0000000..49eb04a --- /dev/null +++ b/hugo-theme-yinyang/exampleSite/README.md @@ -0,0 +1,26 @@ +# hugoBasicExample + +This is an example site for [Hugo](https://gohugo.io/). + +It is intended to be a demo site for the various [Hugo themes](https://themes.gohugo.io/). + +# Using + +1. [Install Hugo](https://gohugo.io/overview/installing/) +2. Clone this repository +```bash +git clone https://github.com/gohugoio/hugoBasicExample.git +cd hugoBasicExample +``` +3. Clone the repository you want to test. If you want to test all hugo themes, you can clone [the full list](https://github.com/gohugoio/hugoThemes) +```bash +git clone --recursive https://URL/OF/YOUR/THEME themes/YOURTHEME +``` +or +```bash +git clone --recursive https://github.com/gohugoio/hugoThemes.git themes +``` +4. Run Hugo and select the theme of your choosing +```bash +hugo server -t YOURTHEME +``` diff --git a/hugo-theme-yinyang/exampleSite/config.toml b/hugo-theme-yinyang/exampleSite/config.toml new file mode 100644 index 0000000..ffb09b2 --- /dev/null +++ b/hugo-theme-yinyang/exampleSite/config.toml @@ -0,0 +1,27 @@ +baseURL = "https://example.com" +languageCode = "en-us" +title = "Joway's Blog" +theme = "yinyang" +DefaultContentLanguage = "en" + +[author] + name = "Joway Wang" + homepage = "https://joway.io/" + +[languages] + [languages.en] + contentDir = "content/en" + languageName = "English" + weight = 1 + [languages.cn] + contentDir = "content/cn" + languageName = "Chinese" + weight = 1 + +[params] +disqus = "joway" # disqus account name +extraHead = '' + +[[params.socials]] +name = "About Me" +link = "https://joway.io" diff --git a/hugo-theme-yinyang/exampleSite/content/about.md b/hugo-theme-yinyang/exampleSite/content/about.md new file mode 100644 index 0000000..77adcf1 --- /dev/null +++ b/hugo-theme-yinyang/exampleSite/content/about.md @@ -0,0 +1,18 @@ ++++ +title = "About Hugo" +date = "2014-04-09" +menu = "main" ++++ + +Hugo is the **world’s fastest framework for building websites**. It is written in Go. + +It makes use of a variety of open source projects including: + +* https://github.com/russross/blackfriday +* https://github.com/alecthomas/chroma +* https://github.com/muesli/smartcrop +* https://github.com/spf13/cobra +* https://github.com/spf13/viper + +Learn more and contribute on [GitHub](https://github.com/gohugoio). + diff --git a/hugo-theme-yinyang/exampleSite/content/cn/posts/cn.md b/hugo-theme-yinyang/exampleSite/content/cn/posts/cn.md new file mode 100644 index 0000000..ff80649 --- /dev/null +++ b/hugo-theme-yinyang/exampleSite/content/cn/posts/cn.md @@ -0,0 +1,10 @@ +--- +title: 中文题目 +date: 2018-03-04 +type: "post" +draft: false +--- + +## 中文信息 + +中文 diff --git a/hugo-theme-yinyang/exampleSite/content/en/post/creating-a-new-theme.md b/hugo-theme-yinyang/exampleSite/content/en/post/creating-a-new-theme.md new file mode 100644 index 0000000..f8230a1 --- /dev/null +++ b/hugo-theme-yinyang/exampleSite/content/en/post/creating-a-new-theme.md @@ -0,0 +1,1150 @@ +--- +author: "Michael Henderson" +date: 2014-09-28 +linktitle: Creating a New Theme +menu: + main: + parent: tutorials +next: /tutorials/github-pages-blog +prev: /tutorials/automated-deployments +title: Creating a New Theme +weight: 10 +--- + + +## Introduction + +This tutorial will show you how to create a simple theme in Hugo. I assume that you are familiar with HTML, the bash command line, and that you are comfortable using Markdown to format content. I'll explain how Hugo uses templates and how you can organize your templates to create a theme. I won't cover using CSS to style your theme. + +We'll start with creating a new site with a very basic template. Then we'll add in a few pages and posts. With small variations on that, you will be able to create many different types of web sites. + +In this tutorial, commands that you enter will start with the "$" prompt. The output will follow. Lines that start with "#" are comments that I've added to explain a point. When I show updates to a file, the ":wq" on the last line means to save the file. + +Here's an example: + +``` +## this is a comment +$ echo this is a command +this is a command + +## edit the file +$ vi foo.md ++++ +date = "2014-09-28" +title = "creating a new theme" ++++ + +bah and humbug +:wq + +## show it +$ cat foo.md ++++ +date = "2014-09-28" +title = "creating a new theme" ++++ + +bah and humbug +$ +``` + + +## Some Definitions + +There are a few concepts that you need to understand before creating a theme. + +### Skins + +Skins are the files responsible for the look and feel of your site. It’s the CSS that controls colors and fonts, it’s the Javascript that determines actions and reactions. It’s also the rules that Hugo uses to transform your content into the HTML that the site will serve to visitors. + +You have two ways to create a skin. The simplest way is to create it in the ```layouts/``` directory. If you do, then you don’t have to worry about configuring Hugo to recognize it. The first place that Hugo will look for rules and files is in the ```layouts/``` directory so it will always find the skin. + +Your second choice is to create it in a sub-directory of the ```themes/``` directory. If you do, then you must always tell Hugo where to search for the skin. It’s extra work, though, so why bother with it? + +The difference between creating a skin in ```layouts/``` and creating it in ```themes/``` is very subtle. A skin in ```layouts/``` can’t be customized without updating the templates and static files that it is built from. A skin created in ```themes/```, on the other hand, can be and that makes it easier for other people to use it. + +The rest of this tutorial will call a skin created in the ```themes/``` directory a theme. + +Note that you can use this tutorial to create a skin in the ```layouts/``` directory if you wish to. The main difference will be that you won’t need to update the site’s configuration file to use a theme. + +### The Home Page + +The home page, or landing page, is the first page that many visitors to a site see. It is the index.html file in the root directory of the web site. Since Hugo writes files to the public/ directory, our home page is public/index.html. + +### Site Configuration File + +When Hugo runs, it looks for a configuration file that contains settings that override default values for the entire site. The file can use TOML, YAML, or JSON. I prefer to use TOML for my configuration files. If you prefer to use JSON or YAML, you’ll need to translate my examples. You’ll also need to change the name of the file since Hugo uses the extension to determine how to process it. + +Hugo translates Markdown files into HTML. By default, Hugo expects to find Markdown files in your ```content/``` directory and template files in your ```themes/``` directory. It will create HTML files in your ```public/``` directory. You can change this by specifying alternate locations in the configuration file. + +### Content + +Content is stored in text files that contain two sections. The first section is the “front matter,” which is the meta-information on the content. The second section contains Markdown that will be converted to HTML. + +#### Front Matter + +The front matter is information about the content. Like the configuration file, it can be written in TOML, YAML, or JSON. Unlike the configuration file, Hugo doesn’t use the file’s extension to know the format. It looks for markers to signal the type. TOML is surrounded by “`+++`”, YAML by “`---`”, and JSON is enclosed in curly braces. I prefer to use TOML, so you’ll need to translate my examples if you prefer YAML or JSON. + +The information in the front matter is passed into the template before the content is rendered into HTML. + +#### Markdown + +Content is written in Markdown which makes it easier to create the content. Hugo runs the content through a Markdown engine to create the HTML which will be written to the output file. + +### Template Files + +Hugo uses template files to render content into HTML. Template files are a bridge between the content and presentation. Rules in the template define what content is published, where it's published to, and how it will rendered to the HTML file. The template guides the presentation by specifying the style to use. + +There are three types of templates: single, list, and partial. Each type takes a bit of content as input and transforms it based on the commands in the template. + +Hugo uses its knowledge of the content to find the template file used to render the content. If it can’t find a template that is an exact match for the content, it will shift up a level and search from there. It will continue to do so until it finds a matching template or runs out of templates to try. If it can’t find a template, it will use the default template for the site. + +Please note that you can use the front matter to influence Hugo’s choice of templates. + +#### Single Template + +A single template is used to render a single piece of content. For example, an article or post would be a single piece of content and use a single template. + +#### List Template + +A list template renders a group of related content. That could be a summary of recent postings or all articles in a category. List templates can contain multiple groups. + +The homepage template is a special type of list template. Hugo assumes that the home page of your site will act as the portal for the rest of the content in the site. + +#### Partial Template + +A partial template is a template that can be included in other templates. Partial templates must be called using the “partial” template command. They are very handy for rolling up common behavior. For example, your site may have a banner that all pages use. Instead of copying the text of the banner into every single and list template, you could create a partial with the banner in it. That way if you decide to change the banner, you only have to change the partial template. + +## Create a New Site + +Let's use Hugo to create a new web site. I'm a Mac user, so I'll create mine in my home directory, in the Sites folder. If you're using Linux, you might have to create the folder first. + +The "new site" command will create a skeleton of a site. It will give you the basic directory structure and a useable configuration file. + +``` +$ hugo new site ~/Sites/zafta +$ cd ~/Sites/zafta +$ ls -l +total 8 +drwxr-xr-x 7 quoha staff 238 Sep 29 16:49 . +drwxr-xr-x 3 quoha staff 102 Sep 29 16:49 .. +drwxr-xr-x 2 quoha staff 68 Sep 29 16:49 archetypes +-rw-r--r-- 1 quoha staff 82 Sep 29 16:49 config.toml +drwxr-xr-x 2 quoha staff 68 Sep 29 16:49 content +drwxr-xr-x 2 quoha staff 68 Sep 29 16:49 layouts +drwxr-xr-x 2 quoha staff 68 Sep 29 16:49 static +$ +``` + +Take a look in the content/ directory to confirm that it is empty. + +The other directories (archetypes/, layouts/, and static/) are used when customizing a theme. That's a topic for a different tutorial, so please ignore them for now. + +### Generate the HTML For the New Site + +Running the `hugo` command with no options will read all the available content and generate the HTML files. It will also copy all static files (that's everything that's not content). Since we have an empty site, it won't do much, but it will do it very quickly. + +``` +$ hugo --verbose +INFO: 2014/09/29 Using config file: config.toml +INFO: 2014/09/29 syncing from /Users/quoha/Sites/zafta/static/ to /Users/quoha/Sites/zafta/public/ +WARN: 2014/09/29 Unable to locate layout: [index.html _default/list.html _default/single.html] +WARN: 2014/09/29 Unable to locate layout: [404.html] +0 draft content +0 future content +0 pages created +0 tags created +0 categories created +in 2 ms +$ +``` + +The "`--verbose`" flag gives extra information that will be helpful when we build the template. Every line of the output that starts with "INFO:" or "WARN:" is present because we used that flag. The lines that start with "WARN:" are warning messages. We'll go over them later. + +We can verify that the command worked by looking at the directory again. + +``` +$ ls -l +total 8 +drwxr-xr-x 2 quoha staff 68 Sep 29 16:49 archetypes +-rw-r--r-- 1 quoha staff 82 Sep 29 16:49 config.toml +drwxr-xr-x 2 quoha staff 68 Sep 29 16:49 content +drwxr-xr-x 2 quoha staff 68 Sep 29 16:49 layouts +drwxr-xr-x 4 quoha staff 136 Sep 29 17:02 public +drwxr-xr-x 2 quoha staff 68 Sep 29 16:49 static +$ +``` + +See that new public/ directory? Hugo placed all generated content there. When you're ready to publish your web site, that's the place to start. For now, though, let's just confirm that we have what we'd expect from a site with no content. + +``` +$ ls -l public +total 16 +-rw-r--r-- 1 quoha staff 416 Sep 29 17:02 index.xml +-rw-r--r-- 1 quoha staff 262 Sep 29 17:02 sitemap.xml +$ +``` + +Hugo created two XML files, which is standard, but there are no HTML files. + + + +### Test the New Site + +Verify that you can run the built-in web server. It will dramatically shorten your development cycle if you do. Start it by running the "server" command. If it is successful, you will see output similar to the following: + +``` +$ hugo server --verbose +INFO: 2014/09/29 Using config file: /Users/quoha/Sites/zafta/config.toml +INFO: 2014/09/29 syncing from /Users/quoha/Sites/zafta/static/ to /Users/quoha/Sites/zafta/public/ +WARN: 2014/09/29 Unable to locate layout: [index.html _default/list.html _default/single.html] +WARN: 2014/09/29 Unable to locate layout: [404.html] +0 draft content +0 future content +0 pages created +0 tags created +0 categories created +in 2 ms +Serving pages from /Users/quoha/Sites/zafta/public +Web Server is available at http://localhost:1313 +Press Ctrl+C to stop +``` + +Connect to the listed URL (it's on the line that starts with "Web Server"). If everything is working correctly, you should get a page that shows the following: + +``` +index.xml +sitemap.xml +``` + +That's a listing of your public/ directory. Hugo didn't create a home page because our site has no content. When there's no index.html file in a directory, the server lists the files in the directory, which is what you should see in your browser. + +Let’s go back and look at those warnings again. + +``` +WARN: 2014/09/29 Unable to locate layout: [index.html _default/list.html _default/single.html] +WARN: 2014/09/29 Unable to locate layout: [404.html] +``` + +That second warning is easier to explain. We haven’t created a template to be used to generate “page not found errors.” The 404 message is a topic for a separate tutorial. + +Now for the first warning. It is for the home page. You can tell because the first layout that it looked for was “index.html.” That’s only used by the home page. + +I like that the verbose flag causes Hugo to list the files that it's searching for. For the home page, they are index.html, _default/list.html, and _default/single.html. There are some rules that we'll cover later that explain the names and paths. For now, just remember that Hugo couldn't find a template for the home page and it told you so. + +At this point, you've got a working installation and site that we can build upon. All that’s left is to add some content and a theme to display it. + +## Create a New Theme + +Hugo doesn't ship with a default theme. There are a few available (I counted a dozen when I first installed Hugo) and Hugo comes with a command to create new themes. + +We're going to create a new theme called "zafta." Since the goal of this tutorial is to show you how to fill out the files to pull in your content, the theme will not contain any CSS. In other words, ugly but functional. + +All themes have opinions on content and layout. For example, Zafta uses "post" over "blog". Strong opinions make for simpler templates but differing opinions make it tougher to use themes. When you build a theme, consider using the terms that other themes do. + + +### Create a Skeleton + +Use the hugo "new" command to create the skeleton of a theme. This creates the directory structure and places empty files for you to fill out. + +``` +$ hugo new theme zafta + +$ ls -l +total 8 +drwxr-xr-x 2 quoha staff 68 Sep 29 16:49 archetypes +-rw-r--r-- 1 quoha staff 82 Sep 29 16:49 config.toml +drwxr-xr-x 2 quoha staff 68 Sep 29 16:49 content +drwxr-xr-x 2 quoha staff 68 Sep 29 16:49 layouts +drwxr-xr-x 4 quoha staff 136 Sep 29 17:02 public +drwxr-xr-x 2 quoha staff 68 Sep 29 16:49 static +drwxr-xr-x 3 quoha staff 102 Sep 29 17:31 themes + +$ find themes -type f | xargs ls -l +-rw-r--r-- 1 quoha staff 1081 Sep 29 17:31 themes/zafta/LICENSE.md +-rw-r--r-- 1 quoha staff 0 Sep 29 17:31 themes/zafta/archetypes/default.md +-rw-r--r-- 1 quoha staff 0 Sep 29 17:31 themes/zafta/layouts/_default/list.html +-rw-r--r-- 1 quoha staff 0 Sep 29 17:31 themes/zafta/layouts/_default/single.html +-rw-r--r-- 1 quoha staff 0 Sep 29 17:31 themes/zafta/layouts/index.html +-rw-r--r-- 1 quoha staff 0 Sep 29 17:31 themes/zafta/layouts/partials/footer.html +-rw-r--r-- 1 quoha staff 0 Sep 29 17:31 themes/zafta/layouts/partials/header.html +-rw-r--r-- 1 quoha staff 93 Sep 29 17:31 themes/zafta/theme.toml +$ +``` + +The skeleton includes templates (the files ending in .html), license file, a description of your theme (the theme.toml file), and an empty archetype. + +Please take a minute to fill out the theme.toml and LICENSE.md files. They're optional, but if you're going to be distributing your theme, it tells the world who to praise (or blame). It's also nice to declare the license so that people will know how they can use the theme. + +``` +$ vi themes/zafta/theme.toml +author = "michael d henderson" +description = "a minimal working template" +license = "MIT" +name = "zafta" +source_repo = "" +tags = ["tags", "categories"] +:wq + +## also edit themes/zafta/LICENSE.md and change +## the bit that says "YOUR_NAME_HERE" +``` + +Note that the the skeleton's template files are empty. Don't worry, we'll be changing that shortly. + +``` +$ find themes/zafta -name '*.html' | xargs ls -l +-rw-r--r-- 1 quoha staff 0 Sep 29 17:31 themes/zafta/layouts/_default/list.html +-rw-r--r-- 1 quoha staff 0 Sep 29 17:31 themes/zafta/layouts/_default/single.html +-rw-r--r-- 1 quoha staff 0 Sep 29 17:31 themes/zafta/layouts/index.html +-rw-r--r-- 1 quoha staff 0 Sep 29 17:31 themes/zafta/layouts/partials/footer.html +-rw-r--r-- 1 quoha staff 0 Sep 29 17:31 themes/zafta/layouts/partials/header.html +$ +``` + + + +### Update the Configuration File to Use the Theme + +Now that we've got a theme to work with, it's a good idea to add the theme name to the configuration file. This is optional, because you can always add "-t zafta" on all your commands. I like to put it the configuration file because I like shorter command lines. If you don't put it in the configuration file or specify it on the command line, you won't use the template that you're expecting to. + +Edit the file to add the theme, add a title for the site, and specify that all of our content will use the TOML format. + +``` +$ vi config.toml +theme = "zafta" +baseurl = "" +languageCode = "en-us" +title = "zafta - totally refreshing" +MetaDataFormat = "toml" +:wq + +$ +``` + +### Generate the Site + +Now that we have an empty theme, let's generate the site again. + +``` +$ hugo --verbose +INFO: 2014/09/29 Using config file: /Users/quoha/Sites/zafta/config.toml +INFO: 2014/09/29 syncing from /Users/quoha/Sites/zafta/themes/zafta/static/ to /Users/quoha/Sites/zafta/public/ +INFO: 2014/09/29 syncing from /Users/quoha/Sites/zafta/static/ to /Users/quoha/Sites/zafta/public/ +WARN: 2014/09/29 Unable to locate layout: [404.html theme/404.html] +0 draft content +0 future content +0 pages created +0 tags created +0 categories created +in 2 ms +$ +``` + +Did you notice that the output is different? The warning message for the home page has disappeared and we have an additional information line saying that Hugo is syncing from the theme's directory. + +Let's check the public/ directory to see what Hugo's created. + +``` +$ ls -l public +total 16 +drwxr-xr-x 2 quoha staff 68 Sep 29 17:56 css +-rw-r--r-- 1 quoha staff 0 Sep 29 17:56 index.html +-rw-r--r-- 1 quoha staff 407 Sep 29 17:56 index.xml +drwxr-xr-x 2 quoha staff 68 Sep 29 17:56 js +-rw-r--r-- 1 quoha staff 243 Sep 29 17:56 sitemap.xml +$ +``` + +Notice four things: + +1. Hugo created a home page. This is the file public/index.html. +2. Hugo created a css/ directory. +3. Hugo created a js/ directory. +4. Hugo claimed that it created 0 pages. It created a file and copied over static files, but didn't create any pages. That's because it considers a "page" to be a file created directly from a content file. It doesn't count things like the index.html files that it creates automatically. + +#### The Home Page + +Hugo supports many different types of templates. The home page is special because it gets its own type of template and its own template file. The file, layouts/index.html, is used to generate the HTML for the home page. The Hugo documentation says that this is the only required template, but that depends. Hugo's warning message shows that it looks for three different templates: + +``` +WARN: 2014/09/29 Unable to locate layout: [index.html _default/list.html _default/single.html] +``` + +If it can't find any of these, it completely skips creating the home page. We noticed that when we built the site without having a theme installed. + +When Hugo created our theme, it created an empty home page template. Now, when we build the site, Hugo finds the template and uses it to generate the HTML for the home page. Since the template file is empty, the HTML file is empty, too. If the template had any rules in it, then Hugo would have used them to generate the home page. + +``` +$ find . -name index.html | xargs ls -l +-rw-r--r-- 1 quoha staff 0 Sep 29 20:21 ./public/index.html +-rw-r--r-- 1 quoha staff 0 Sep 29 17:31 ./themes/zafta/layouts/index.html +$ +``` + +#### The Magic of Static + +Hugo does two things when generating the site. It uses templates to transform content into HTML and it copies static files into the site. Unlike content, static files are not transformed. They are copied exactly as they are. + +Hugo assumes that your site will use both CSS and JavaScript, so it creates directories in your theme to hold them. Remember opinions? Well, Hugo's opinion is that you'll store your CSS in a directory named css/ and your JavaScript in a directory named js/. If you don't like that, you can change the directory names in your theme directory or even delete them completely. Hugo's nice enough to offer its opinion, then behave nicely if you disagree. + +``` +$ find themes/zafta -type d | xargs ls -ld +drwxr-xr-x 7 quoha staff 238 Sep 29 17:38 themes/zafta +drwxr-xr-x 3 quoha staff 102 Sep 29 17:31 themes/zafta/archetypes +drwxr-xr-x 5 quoha staff 170 Sep 29 17:31 themes/zafta/layouts +drwxr-xr-x 4 quoha staff 136 Sep 29 17:31 themes/zafta/layouts/_default +drwxr-xr-x 4 quoha staff 136 Sep 29 17:31 themes/zafta/layouts/partials +drwxr-xr-x 4 quoha staff 136 Sep 29 17:31 themes/zafta/static +drwxr-xr-x 2 quoha staff 68 Sep 29 17:31 themes/zafta/static/css +drwxr-xr-x 2 quoha staff 68 Sep 29 17:31 themes/zafta/static/js +$ +``` + +## The Theme Development Cycle + +When you're working on a theme, you will make changes in the theme's directory, rebuild the site, and check your changes in the browser. Hugo makes this very easy: + +1. Purge the public/ directory. +2. Run the built in web server in watch mode. +3. Open your site in a browser. +4. Update the theme. +5. Glance at your browser window to see changes. +6. Return to step 4. + +I’ll throw in one more opinion: never work on a theme on a live site. Always work on a copy of your site. Make changes to your theme, test them, then copy them up to your site. For added safety, use a tool like Git to keep a revision history of your content and your theme. Believe me when I say that it is too easy to lose both your mind and your changes. + +Check the main Hugo site for information on using Git with Hugo. + +### Purge the public/ Directory + +When generating the site, Hugo will create new files and update existing ones in the ```public/``` directory. It will not delete files that are no longer used. For example, files that were created in the wrong directory or with the wrong title will remain. If you leave them, you might get confused by them later. I recommend cleaning out your site prior to generating it. + +Note: If you're building on an SSD, you should ignore this. Churning on a SSD can be costly. + +### Hugo's Watch Option + +Hugo's "`--watch`" option will monitor the content/ and your theme directories for changes and rebuild the site automatically. + +### Live Reload + +Hugo's built in web server supports live reload. As pages are saved on the server, the browser is told to refresh the page. Usually, this happens faster than you can say, "Wow, that's totally amazing." + +### Development Commands + +Use the following commands as the basis for your workflow. + +``` +## purge old files. hugo will recreate the public directory. +## +$ rm -rf public +## +## run hugo in watch mode +## +$ hugo server --watch --verbose +``` + +Here's sample output showing Hugo detecting a change to the template for the home page. Once generated, the web browser automatically reloaded the page. I've said this before, it's amazing. + + +``` +$ rm -rf public +$ hugo server --watch --verbose +INFO: 2014/09/29 Using config file: /Users/quoha/Sites/zafta/config.toml +INFO: 2014/09/29 syncing from /Users/quoha/Sites/zafta/themes/zafta/static/ to /Users/quoha/Sites/zafta/public/ +INFO: 2014/09/29 syncing from /Users/quoha/Sites/zafta/static/ to /Users/quoha/Sites/zafta/public/ +WARN: 2014/09/29 Unable to locate layout: [404.html theme/404.html] +0 draft content +0 future content +0 pages created +0 tags created +0 categories created +in 2 ms +Watching for changes in /Users/quoha/Sites/zafta/content +Serving pages from /Users/quoha/Sites/zafta/public +Web Server is available at http://localhost:1313 +Press Ctrl+C to stop +INFO: 2014/09/29 File System Event: ["/Users/quoha/Sites/zafta/themes/zafta/layouts/index.html": MODIFY|ATTRIB] +Change detected, rebuilding site + +WARN: 2014/09/29 Unable to locate layout: [404.html theme/404.html] +0 draft content +0 future content +0 pages created +0 tags created +0 categories created +in 1 ms +``` + +## Update the Home Page Template + +The home page is one of a few special pages that Hugo creates automatically. As mentioned earlier, it looks for one of three files in the theme's layout/ directory: + +1. index.html +2. _default/list.html +3. _default/single.html + +We could update one of the default templates, but a good design decision is to update the most specific template available. That's not a hard and fast rule (in fact, we'll break it a few times in this tutorial), but it is a good generalization. + +### Make a Static Home Page + +Right now, that page is empty because we don't have any content and we don't have any logic in the template. Let's change that by adding some text to the template. + +``` +$ vi themes/zafta/layouts/index.html + + + +

hugo says hello!

+ + +:wq + +$ +``` + +Build the web site and then verify the results. + +``` +$ hugo --verbose +INFO: 2014/09/29 Using config file: /Users/quoha/Sites/zafta/config.toml +INFO: 2014/09/29 syncing from /Users/quoha/Sites/zafta/themes/zafta/static/ to /Users/quoha/Sites/zafta/public/ +INFO: 2014/09/29 syncing from /Users/quoha/Sites/zafta/static/ to /Users/quoha/Sites/zafta/public/ +WARN: 2014/09/29 Unable to locate layout: [404.html theme/404.html] +0 draft content +0 future content +0 pages created +0 tags created +0 categories created +in 2 ms + +$ find public -type f -name '*.html' | xargs ls -l +-rw-r--r-- 1 quoha staff 78 Sep 29 21:26 public/index.html + +$ cat public/index.html + + + +

hugo says hello!

+ +``` + +#### Live Reload + +Note: If you're running the server with the `--watch` option, you'll see different content in the file: + +``` +$ cat public/index.html + + + +

hugo says hello!

+ + +``` + +When you use `--watch`, the Live Reload script is added by Hugo. Look for live reload in the documentation to see what it does and how to disable it. + +### Build a "Dynamic" Home Page + +"Dynamic home page?" Hugo's a static web site generator, so this seems an odd thing to say. I mean let's have the home page automatically reflect the content in the site every time Hugo builds it. We'll use iteration in the template to do that. + +#### Create New Posts + +Now that we have the home page generating static content, let's add some content to the site. We'll display these posts as a list on the home page and on their own page, too. + +Hugo has a command to generate a skeleton post, just like it does for sites and themes. + +``` +$ hugo --verbose new post/first.md +INFO: 2014/09/29 Using config file: /Users/quoha/Sites/zafta/config.toml +INFO: 2014/09/29 attempting to create post/first.md of post +INFO: 2014/09/29 curpath: /Users/quoha/Sites/zafta/themes/zafta/archetypes/default.md +ERROR: 2014/09/29 Unable to Cast to map[string]interface{} + +$ +``` + +That wasn't very nice, was it? + +The "new" command uses an archetype to create the post file. Hugo created an empty default archetype file, but that causes an error when there's a theme. For me, the workaround was to create an archetypes file specifically for the post type. + +``` +$ vi themes/zafta/archetypes/post.md ++++ +Description = "" +Tags = [] +Categories = [] ++++ +:wq + +$ find themes/zafta/archetypes -type f | xargs ls -l +-rw-r--r-- 1 quoha staff 0 Sep 29 21:53 themes/zafta/archetypes/default.md +-rw-r--r-- 1 quoha staff 51 Sep 29 21:54 themes/zafta/archetypes/post.md + +$ hugo --verbose new post/first.md +INFO: 2014/09/29 Using config file: /Users/quoha/Sites/zafta/config.toml +INFO: 2014/09/29 attempting to create post/first.md of post +INFO: 2014/09/29 curpath: /Users/quoha/Sites/zafta/themes/zafta/archetypes/post.md +INFO: 2014/09/29 creating /Users/quoha/Sites/zafta/content/post/first.md +/Users/quoha/Sites/zafta/content/post/first.md created + +$ hugo --verbose new post/second.md +INFO: 2014/09/29 Using config file: /Users/quoha/Sites/zafta/config.toml +INFO: 2014/09/29 attempting to create post/second.md of post +INFO: 2014/09/29 curpath: /Users/quoha/Sites/zafta/themes/zafta/archetypes/post.md +INFO: 2014/09/29 creating /Users/quoha/Sites/zafta/content/post/second.md +/Users/quoha/Sites/zafta/content/post/second.md created + +$ ls -l content/post +total 16 +-rw-r--r-- 1 quoha staff 104 Sep 29 21:54 first.md +-rw-r--r-- 1 quoha staff 105 Sep 29 21:57 second.md + +$ cat content/post/first.md ++++ +Categories = [] +Description = "" +Tags = [] +date = "2014-09-29T21:54:53-05:00" +title = "first" + ++++ +my first post + +$ cat content/post/second.md ++++ +Categories = [] +Description = "" +Tags = [] +date = "2014-09-29T21:57:09-05:00" +title = "second" + ++++ +my second post + +$ +``` + +Build the web site and then verify the results. + +``` +$ rm -rf public +$ hugo --verbose +INFO: 2014/09/29 Using config file: /Users/quoha/Sites/zafta/config.toml +INFO: 2014/09/29 syncing from /Users/quoha/Sites/zafta/themes/zafta/static/ to /Users/quoha/Sites/zafta/public/ +INFO: 2014/09/29 syncing from /Users/quoha/Sites/zafta/static/ to /Users/quoha/Sites/zafta/public/ +INFO: 2014/09/29 found taxonomies: map[string]string{"category":"categories", "tag":"tags"} +WARN: 2014/09/29 Unable to locate layout: [404.html theme/404.html] +0 draft content +0 future content +2 pages created +0 tags created +0 categories created +in 4 ms +$ +``` + +The output says that it created 2 pages. Those are our new posts: + +``` +$ find public -type f -name '*.html' | xargs ls -l +-rw-r--r-- 1 quoha staff 78 Sep 29 22:13 public/index.html +-rw-r--r-- 1 quoha staff 0 Sep 29 22:13 public/post/first/index.html +-rw-r--r-- 1 quoha staff 0 Sep 29 22:13 public/post/index.html +-rw-r--r-- 1 quoha staff 0 Sep 29 22:13 public/post/second/index.html +$ +``` + +The new files are empty because because the templates used to generate the content are empty. The homepage doesn't show the new content, either. We have to update the templates to add the posts. + +### List and Single Templates + +In Hugo, we have three major kinds of templates. There's the home page template that we updated previously. It is used only by the home page. We also have "single" templates which are used to generate output for a single content file. We also have "list" templates that are used to group multiple pieces of content before generating output. + +Generally speaking, list templates are named "list.html" and single templates are named "single.html." + +There are three other types of templates: partials, content views, and terms. We will not go into much detail on these. + +### Add Content to the Homepage + +The home page will contain a list of posts. Let's update its template to add the posts that we just created. The logic in the template will run every time we build the site. + +``` +$ vi themes/zafta/layouts/index.html + + + + {{ range first 10 .Data.Pages }} +

{{ .Title }}

+ {{ end }} + + +:wq + +$ +``` + +Hugo uses the Go template engine. That engine scans the template files for commands which are enclosed between "{{" and "}}". In our template, the commands are: + +1. range +2. .Title +3. end + +The "range" command is an iterator. We're going to use it to go through the first ten pages. Every HTML file that Hugo creates is treated as a page, so looping through the list of pages will look at every file that will be created. + +The ".Title" command prints the value of the "title" variable. Hugo pulls it from the front matter in the Markdown file. + +The "end" command signals the end of the range iterator. The engine loops back to the top of the iteration when it finds "end." Everything between the "range" and "end" is evaluated every time the engine goes through the iteration. In this file, that would cause the title from the first ten pages to be output as heading level one. + +It's helpful to remember that some variables, like .Data, are created before any output files. Hugo loads every content file into the variable and then gives the template a chance to process before creating the HTML files. + +Build the web site and then verify the results. + +``` +$ rm -rf public +$ hugo --verbose +INFO: 2014/09/29 Using config file: /Users/quoha/Sites/zafta/config.toml +INFO: 2014/09/29 syncing from /Users/quoha/Sites/zafta/themes/zafta/static/ to /Users/quoha/Sites/zafta/public/ +INFO: 2014/09/29 syncing from /Users/quoha/Sites/zafta/static/ to /Users/quoha/Sites/zafta/public/ +INFO: 2014/09/29 found taxonomies: map[string]string{"tag":"tags", "category":"categories"} +WARN: 2014/09/29 Unable to locate layout: [404.html theme/404.html] +0 draft content +0 future content +2 pages created +0 tags created +0 categories created +in 4 ms +$ find public -type f -name '*.html' | xargs ls -l +-rw-r--r-- 1 quoha staff 94 Sep 29 22:23 public/index.html +-rw-r--r-- 1 quoha staff 0 Sep 29 22:23 public/post/first/index.html +-rw-r--r-- 1 quoha staff 0 Sep 29 22:23 public/post/index.html +-rw-r--r-- 1 quoha staff 0 Sep 29 22:23 public/post/second/index.html +$ cat public/index.html + + + + +

second

+ +

first

+ + + +$ +``` + +Congratulations, the home page shows the title of the two posts. The posts themselves are still empty, but let's take a moment to appreciate what we've done. Your template now generates output dynamically. Believe it or not, by inserting the range command inside of those curly braces, you've learned everything you need to know to build a theme. All that's really left is understanding which template will be used to generate each content file and becoming familiar with the commands for the template engine. + +And, if that were entirely true, this tutorial would be much shorter. There are a few things to know that will make creating a new template much easier. Don't worry, though, that's all to come. + +### Add Content to the Posts + +We're working with posts, which are in the content/post/ directory. That means that their section is "post" (and if we don't do something weird, their type is also "post"). + +Hugo uses the section and type to find the template file for every piece of content. Hugo will first look for a template file that matches the section or type name. If it can't find one, then it will look in the _default/ directory. There are some twists that we'll cover when we get to categories and tags, but for now we can assume that Hugo will try post/single.html, then _default/single.html. + +Now that we know the search rule, let's see what we actually have available: + +``` +$ find themes/zafta -name single.html | xargs ls -l +-rw-r--r-- 1 quoha staff 132 Sep 29 17:31 themes/zafta/layouts/_default/single.html +``` + +We could create a new template, post/single.html, or change the default. Since we don't know of any other content types, let's start with updating the default. + +Remember, any content that we haven't created a template for will end up using this template. That can be good or bad. Bad because I know that we're going to be adding different types of content and we're going to end up undoing some of the changes we've made. It's good because we'll be able to see immediate results. It's also good to start here because we can start to build the basic layout for the site. As we add more content types, we'll refactor this file and move logic around. Hugo makes that fairly painless, so we'll accept the cost and proceed. + +Please see the Hugo documentation on template rendering for all the details on determining which template to use. And, as the docs mention, if you're building a single page application (SPA) web site, you can delete all of the other templates and work with just the default single page. That's a refreshing amount of joy right there. + +#### Update the Template File + +``` +$ vi themes/zafta/layouts/_default/single.html + + + + {{ .Title }} + + +

{{ .Title }}

+ {{ .Content }} + + +:wq + +$ +``` + +Build the web site and verify the results. + +``` +$ rm -rf public +$ hugo --verbose +INFO: 2014/09/29 Using config file: /Users/quoha/Sites/zafta/config.toml +INFO: 2014/09/29 syncing from /Users/quoha/Sites/zafta/themes/zafta/static/ to /Users/quoha/Sites/zafta/public/ +INFO: 2014/09/29 syncing from /Users/quoha/Sites/zafta/static/ to /Users/quoha/Sites/zafta/public/ +INFO: 2014/09/29 found taxonomies: map[string]string{"tag":"tags", "category":"categories"} +WARN: 2014/09/29 Unable to locate layout: [404.html theme/404.html] +0 draft content +0 future content +2 pages created +0 tags created +0 categories created +in 4 ms + +$ find public -type f -name '*.html' | xargs ls -l +-rw-r--r-- 1 quoha staff 94 Sep 29 22:40 public/index.html +-rw-r--r-- 1 quoha staff 125 Sep 29 22:40 public/post/first/index.html +-rw-r--r-- 1 quoha staff 0 Sep 29 22:40 public/post/index.html +-rw-r--r-- 1 quoha staff 128 Sep 29 22:40 public/post/second/index.html + +$ cat public/post/first/index.html + + + + first + + +

first

+

my first post

+ + + + +$ cat public/post/second/index.html + + + + second + + +

second

+

my second post

+ + + +$ +``` + +Notice that the posts now have content. You can go to localhost:1313/post/first to verify. + +### Linking to Content + +The posts are on the home page. Let's add a link from there to the post. Since this is the home page, we'll update its template. + +``` +$ vi themes/zafta/layouts/index.html + + + + {{ range first 10 .Data.Pages }} +

{{ .Title }}

+ {{ end }} + + +``` + +Build the web site and verify the results. + +``` +$ rm -rf public +$ hugo --verbose +INFO: 2014/09/29 Using config file: /Users/quoha/Sites/zafta/config.toml +INFO: 2014/09/29 syncing from /Users/quoha/Sites/zafta/themes/zafta/static/ to /Users/quoha/Sites/zafta/public/ +INFO: 2014/09/29 syncing from /Users/quoha/Sites/zafta/static/ to /Users/quoha/Sites/zafta/public/ +INFO: 2014/09/29 found taxonomies: map[string]string{"tag":"tags", "category":"categories"} +WARN: 2014/09/29 Unable to locate layout: [404.html theme/404.html] +0 draft content +0 future content +2 pages created +0 tags created +0 categories created +in 4 ms + +$ find public -type f -name '*.html' | xargs ls -l +-rw-r--r-- 1 quoha staff 149 Sep 29 22:44 public/index.html +-rw-r--r-- 1 quoha staff 125 Sep 29 22:44 public/post/first/index.html +-rw-r--r-- 1 quoha staff 0 Sep 29 22:44 public/post/index.html +-rw-r--r-- 1 quoha staff 128 Sep 29 22:44 public/post/second/index.html + +$ cat public/index.html + + + + +

second

+ +

first

+ + + + +$ +``` + +### Create a Post Listing + +We have the posts displaying on the home page and on their own page. We also have a file public/post/index.html that is empty. Let's make it show a list of all posts (not just the first ten). + +We need to decide which template to update. This will be a listing, so it should be a list template. Let's take a quick look and see which list templates are available. + +``` +$ find themes/zafta -name list.html | xargs ls -l +-rw-r--r-- 1 quoha staff 0 Sep 29 17:31 themes/zafta/layouts/_default/list.html +``` + +As with the single post, we have to decide to update _default/list.html or create post/list.html. We still don't have multiple content types, so let's stay consistent and update the default list template. + +## Creating Top Level Pages + +Let's add an "about" page and display it at the top level (as opposed to a sub-level like we did with posts). + +The default in Hugo is to use the directory structure of the content/ directory to guide the location of the generated html in the public/ directory. Let's verify that by creating an "about" page at the top level: + +``` +$ vi content/about.md ++++ +title = "about" +description = "about this site" +date = "2014-09-27" +slug = "about time" ++++ + +## about us + +i'm speechless +:wq +``` + +Generate the web site and verify the results. + +``` +$ find public -name '*.html' | xargs ls -l +-rw-rw-r-- 1 mdhender staff 334 Sep 27 15:08 public/about-time/index.html +-rw-rw-r-- 1 mdhender staff 527 Sep 27 15:08 public/index.html +-rw-rw-r-- 1 mdhender staff 358 Sep 27 15:08 public/post/first-post/index.html +-rw-rw-r-- 1 mdhender staff 0 Sep 27 15:08 public/post/index.html +-rw-rw-r-- 1 mdhender staff 342 Sep 27 15:08 public/post/second-post/index.html +``` + +Notice that the page wasn't created at the top level. It was created in a sub-directory named 'about-time/'. That name came from our slug. Hugo will use the slug to name the generated content. It's a reasonable default, by the way, but we can learn a few things by fighting it for this file. + +One other thing. Take a look at the home page. + +``` +$ cat public/index.html + + + +

creating a new theme

+

about

+

second

+

first

+ + +``` + +Notice that the "about" link is listed with the posts? That's not desirable, so let's change that first. + +``` +$ vi themes/zafta/layouts/index.html + + + +

posts

+ {{ range first 10 .Data.Pages }} + {{ if eq .Type "post"}} +

{{ .Title }}

+ {{ end }} + {{ end }} + +

pages

+ {{ range .Data.Pages }} + {{ if eq .Type "page" }} +

{{ .Title }}

+ {{ end }} + {{ end }} + + +:wq +``` + +Generate the web site and verify the results. The home page has two sections, posts and pages, and each section has the right set of headings and links in it. + +But, that about page still renders to about-time/index.html. + +``` +$ find public -name '*.html' | xargs ls -l +-rw-rw-r-- 1 mdhender staff 334 Sep 27 15:33 public/about-time/index.html +-rw-rw-r-- 1 mdhender staff 645 Sep 27 15:33 public/index.html +-rw-rw-r-- 1 mdhender staff 358 Sep 27 15:33 public/post/first-post/index.html +-rw-rw-r-- 1 mdhender staff 0 Sep 27 15:33 public/post/index.html +-rw-rw-r-- 1 mdhender staff 342 Sep 27 15:33 public/post/second-post/index.html +``` + +Knowing that hugo is using the slug to generate the file name, the simplest solution is to change the slug. Let's do it the hard way and change the permalink in the configuration file. + +``` +$ vi config.toml +[permalinks] + page = "/:title/" + about = "/:filename/" +``` + +Generate the web site and verify that this didn't work. Hugo lets "slug" or "URL" override the permalinks setting in the configuration file. Go ahead and comment out the slug in content/about.md, then generate the web site to get it to be created in the right place. + +## Sharing Templates + +If you've been following along, you probably noticed that posts have titles in the browser and the home page doesn't. That's because we didn't put the title in the home page's template (layouts/index.html). That's an easy thing to do, but let's look at a different option. + +We can put the common bits into a shared template that's stored in the themes/zafta/layouts/partials/ directory. + +### Create the Header and Footer Partials + +In Hugo, a partial is a sugar-coated template. Normally a template reference has a path specified. Partials are different. Hugo searches for them along a TODO defined search path. This makes it easier for end-users to override the theme's presentation. + +``` +$ vi themes/zafta/layouts/partials/header.html + + + + {{ .Title }} + + +:wq + +$ vi themes/zafta/layouts/partials/footer.html + + +:wq +``` + +### Update the Home Page Template to Use the Partials + +The most noticeable difference between a template call and a partials call is the lack of path: + +``` +{{ template "theme/partials/header.html" . }} +``` +versus +``` +{{ partial "header.html" . }} +``` +Both pass in the context. + +Let's change the home page template to use these new partials. + +``` +$ vi themes/zafta/layouts/index.html +{{ partial "header.html" . }} + +

posts

+ {{ range first 10 .Data.Pages }} + {{ if eq .Type "post"}} +

{{ .Title }}

+ {{ end }} + {{ end }} + +

pages

+ {{ range .Data.Pages }} + {{ if or (eq .Type "page") (eq .Type "about") }} +

{{ .Type }} - {{ .Title }} - {{ .RelPermalink }}

+ {{ end }} + {{ end }} + +{{ partial "footer.html" . }} +:wq +``` + +Generate the web site and verify the results. The title on the home page is now "your title here", which comes from the "title" variable in the config.toml file. + +### Update the Default Single Template to Use the Partials + +``` +$ vi themes/zafta/layouts/_default/single.html +{{ partial "header.html" . }} + +

{{ .Title }}

+ {{ .Content }} + +{{ partial "footer.html" . }} +:wq +``` + +Generate the web site and verify the results. The title on the posts and the about page should both reflect the value in the markdown file. + +## Add “Date Published” to Posts + +It's common to have posts display the date that they were written or published, so let's add that. The front matter of our posts has a variable named "date." It's usually the date the content was created, but let's pretend that's the value we want to display. + +### Add “Date Published” to the Template + +We'll start by updating the template used to render the posts. The template code will look like: + +``` +{{ .Date.Format "Mon, Jan 2, 2006" }} +``` + +Posts use the default single template, so we'll change that file. + +``` +$ vi themes/zafta/layouts/_default/single.html +{{ partial "header.html" . }} + +

{{ .Title }}

+

{{ .Date.Format "Mon, Jan 2, 2006" }}

+ {{ .Content }} + +{{ partial "footer.html" . }} +:wq +``` + +Generate the web site and verify the results. The posts now have the date displayed in them. There's a problem, though. The "about" page also has the date displayed. + +As usual, there are a couple of ways to make the date display only on posts. We could do an "if" statement like we did on the home page. Another way would be to create a separate template for posts. + +The "if" solution works for sites that have just a couple of content types. It aligns with the principle of "code for today," too. + +Let's assume, though, that we've made our site so complex that we feel we have to create a new template type. In Hugo-speak, we're going to create a section template. + +Let's restore the default single template before we forget. + +``` +$ mkdir themes/zafta/layouts/post +$ vi themes/zafta/layouts/_default/single.html +{{ partial "header.html" . }} + +

{{ .Title }}

+ {{ .Content }} + +{{ partial "footer.html" . }} +:wq +``` + +Now we'll update the post's version of the single template. If you remember Hugo's rules, the template engine will use this version over the default. + +``` +$ vi themes/zafta/layouts/post/single.html +{{ partial "header.html" . }} + +

{{ .Title }}

+

{{ .Date.Format "Mon, Jan 2, 2006" }}

+ {{ .Content }} + +{{ partial "footer.html" . }} +:wq + +``` + +Note that we removed the date logic from the default template and put it in the post template. Generate the web site and verify the results. Posts have dates and the about page doesn't. + +### Don't Repeat Yourself + +DRY is a good design goal and Hugo does a great job supporting it. Part of the art of a good template is knowing when to add a new template and when to update an existing one. While you're figuring that out, accept that you'll be doing some refactoring. Hugo makes that easy and fast, so it's okay to delay splitting up a template. diff --git a/hugo-theme-yinyang/exampleSite/content/en/post/goisforlovers.md b/hugo-theme-yinyang/exampleSite/content/en/post/goisforlovers.md new file mode 100644 index 0000000..df125d8 --- /dev/null +++ b/hugo-theme-yinyang/exampleSite/content/en/post/goisforlovers.md @@ -0,0 +1,344 @@ ++++ +title = "(Hu)go Template Primer" +description = "" +tags = [ + "go", + "golang", + "templates", + "themes", + "development", +] +date = "2014-04-02" +categories = [ + "Development", + "golang", +] +menu = "main" ++++ + +Hugo uses the excellent [Go][] [html/template][gohtmltemplate] library for +its template engine. It is an extremely lightweight engine that provides a very +small amount of logic. In our experience that it is just the right amount of +logic to be able to create a good static website. If you have used other +template systems from different languages or frameworks you will find a lot of +similarities in Go templates. + +This document is a brief primer on using Go templates. The [Go docs][gohtmltemplate] +provide more details. + +## Introduction to Go Templates + +Go templates provide an extremely simple template language. It adheres to the +belief that only the most basic of logic belongs in the template or view layer. +One consequence of this simplicity is that Go templates parse very quickly. + +A unique characteristic of Go templates is they are content aware. Variables and +content will be sanitized depending on the context of where they are used. More +details can be found in the [Go docs][gohtmltemplate]. + +## Basic Syntax + +Golang templates are HTML files with the addition of variables and +functions. + +**Go variables and functions are accessible within {{ }}** + +Accessing a predefined variable "foo": + + {{ foo }} + +**Parameters are separated using spaces** + +Calling the add function with input of 1, 2: + + {{ add 1 2 }} + +**Methods and fields are accessed via dot notation** + +Accessing the Page Parameter "bar" + + {{ .Params.bar }} + +**Parentheses can be used to group items together** + + {{ if or (isset .Params "alt") (isset .Params "caption") }} Caption {{ end }} + + +## Variables + +Each Go template has a struct (object) made available to it. In hugo each +template is passed either a page or a node struct depending on which type of +page you are rendering. More details are available on the +[variables](/layout/variables) page. + +A variable is accessed by referencing the variable name. + + {{ .Title }} + +Variables can also be defined and referenced. + + {{ $address := "123 Main St."}} + {{ $address }} + + +## Functions + +Go template ship with a few functions which provide basic functionality. The Go +template system also provides a mechanism for applications to extend the +available functions with their own. [Hugo template +functions](/layout/functions) provide some additional functionality we believe +are useful for building websites. Functions are called by using their name +followed by the required parameters separated by spaces. Template +functions cannot be added without recompiling hugo. + +**Example:** + + {{ add 1 2 }} + +## Includes + +When including another template you will pass to it the data it will be +able to access. To pass along the current context please remember to +include a trailing dot. The templates location will always be starting at +the /layout/ directory within Hugo. + +**Example:** + + {{ template "chrome/header.html" . }} + + +## Logic + +Go templates provide the most basic iteration and conditional logic. + +### Iteration + +Just like in Go, the Go templates make heavy use of range to iterate over +a map, array or slice. The following are different examples of how to use +range. + +**Example 1: Using Context** + + {{ range array }} + {{ . }} + {{ end }} + +**Example 2: Declaring value variable name** + + {{range $element := array}} + {{ $element }} + {{ end }} + +**Example 2: Declaring key and value variable name** + + {{range $index, $element := array}} + {{ $index }} + {{ $element }} + {{ end }} + +### Conditionals + +If, else, with, or, & and provide the framework for handling conditional +logic in Go Templates. Like range, each statement is closed with `end`. + + +Go Templates treat the following values as false: + +* false +* 0 +* any array, slice, map, or string of length zero + +**Example 1: If** + + {{ if isset .Params "title" }}

{{ index .Params "title" }}

{{ end }} + +**Example 2: If -> Else** + + {{ if isset .Params "alt" }} + {{ index .Params "alt" }} + {{else}} + {{ index .Params "caption" }} + {{ end }} + +**Example 3: And & Or** + + {{ if and (or (isset .Params "title") (isset .Params "caption")) (isset .Params "attr")}} + +**Example 4: With** + +An alternative way of writing "if" and then referencing the same value +is to use "with" instead. With rebinds the context `.` within its scope, +and skips the block if the variable is absent. + +The first example above could be simplified as: + + {{ with .Params.title }}

{{ . }}

{{ end }} + +**Example 5: If -> Else If** + + {{ if isset .Params "alt" }} + {{ index .Params "alt" }} + {{ else if isset .Params "caption" }} + {{ index .Params "caption" }} + {{ end }} + +## Pipes + +One of the most powerful components of Go templates is the ability to +stack actions one after another. This is done by using pipes. Borrowed +from unix pipes, the concept is simple, each pipeline's output becomes the +input of the following pipe. + +Because of the very simple syntax of Go templates, the pipe is essential +to being able to chain together function calls. One limitation of the +pipes is that they only can work with a single value and that value +becomes the last parameter of the next pipeline. + +A few simple examples should help convey how to use the pipe. + +**Example 1 :** + + {{ if eq 1 1 }} Same {{ end }} + +is the same as + + {{ eq 1 1 | if }} Same {{ end }} + +It does look odd to place the if at the end, but it does provide a good +illustration of how to use the pipes. + +**Example 2 :** + + {{ index .Params "disqus_url" | html }} + +Access the page parameter called "disqus_url" and escape the HTML. + +**Example 3 :** + + {{ if or (or (isset .Params "title") (isset .Params "caption")) (isset .Params "attr")}} + Stuff Here + {{ end }} + +Could be rewritten as + + {{ isset .Params "caption" | or isset .Params "title" | or isset .Params "attr" | if }} + Stuff Here + {{ end }} + + +## Context (aka. the dot) + +The most easily overlooked concept to understand about Go templates is that {{ . }} +always refers to the current context. In the top level of your template this +will be the data set made available to it. Inside of a iteration it will have +the value of the current item. When inside of a loop the context has changed. . +will no longer refer to the data available to the entire page. If you need to +access this from within the loop you will likely want to set it to a variable +instead of depending on the context. + +**Example:** + + {{ $title := .Site.Title }} + {{ range .Params.tags }} +
  • {{ . }} - {{ $title }}
  • + {{ end }} + +Notice how once we have entered the loop the value of {{ . }} has changed. We +have defined a variable outside of the loop so we have access to it from within +the loop. + +# Hugo Parameters + +Hugo provides the option of passing values to the template language +through the site configuration (for sitewide values), or through the meta +data of each specific piece of content. You can define any values of any +type (supported by your front matter/config format) and use them however +you want to inside of your templates. + + +## Using Content (page) Parameters + +In each piece of content you can provide variables to be used by the +templates. This happens in the [front matter](/content/front-matter). + +An example of this is used in this documentation site. Most of the pages +benefit from having the table of contents provided. Sometimes the TOC just +doesn't make a lot of sense. We've defined a variable in our front matter +of some pages to turn off the TOC from being displayed. + +Here is the example front matter: + +``` +--- +title: "Permalinks" +date: "2013-11-18" +aliases: + - "/doc/permalinks/" +groups: ["extras"] +groups_weight: 30 +notoc: true +--- +``` + +Here is the corresponding code inside of the template: + + {{ if not .Params.notoc }} +
    + {{ .TableOfContents }} +
    + {{ end }} + + + +## Using Site (config) Parameters +In your top-level configuration file (eg, `config.yaml`) you can define site +parameters, which are values which will be available to you in chrome. + +For instance, you might declare: + +```yaml +params: + CopyrightHTML: "Copyright © 2013 John Doe. All Rights Reserved." + TwitterUser: "spf13" + SidebarRecentLimit: 5 +``` + +Within a footer layout, you might then declare a `