Skip to content

Latest commit

 

History

History
455 lines (330 loc) · 9.49 KB

File metadata and controls

455 lines (330 loc) · 9.49 KB

Org-Lexical Style Guide

Introduction

This guide captures best practices for writing org-mode documents that export beautifully to Ghost via the ox-ghost exporter.

Lexical documents are a flat list of nodes - headings don’t contain content, they’re just styled markers. Understanding this helps you structure content that renders well.

Document Structure

Heading Levels

Org LevelLexicalUsage
*H2Major sections (H1 is post title)
**H3Subsections
***H4Sub-subsections (use sparingly)
****H5Rarely needed
*****H6Avoid - too deep

Ghost posts typically use H2 for sections, H3 for subsections. Deeper nesting often signals content should be restructured.

Front Matter

#+TITLE: Your Post Title
#+AUTHOR: Your Name
#+DATE: 2024-01-29
#+PROPERTY: header-args :eval never-export

The :eval never-export prevents babel re-execution during Stage 2 export.

Code Blocks

Line Width

Keep code lines under 80 characters to avoid horizontal scrollbars. Ghost’s content area fits ~80-90 monospace characters.

WidthUsage
72Strict - fits all displays
80Standard - recommended maximum
100Extended - may scroll on narrow views

Copy Button

Code blocks automatically include a copy-to-clipboard button (via Prism.js toolbar plugin). No special markup needed.

Basic Pattern

Code followed by explanation reads naturally:

#+BEGIN_SRC python
result = compute_something()
print(result)
#+END_SRC

The function returns the computed value...

With Results

Results flow directly after code - no heading needed:

#+BEGIN_SRC shell :results output
echo "Hello from $(hostname)"
#+END_SRC

#+RESULTS:
: Hello from myserver

Named Blocks (Decoupled Generation)

Use #+NAME: to create reusable, named code blocks that can be called from anywhere in the document. This decouples generation from display.

#+NAME: greeting-generator
#+BEGIN_SRC elisp :var name="World"
(format "Hello, %s!" name)
#+END_SRC

Then reference it elsewhere with #+CALL::

#+CALL: greeting-generator(name="Ghost")

#+RESULTS:
: Hello, Ghost!

Hidden Generation Sections

For complex documents, create a :noexport: section for all generators:

* Generators :noexport:

#+NAME: make-list
#+BEGIN_SRC elisp :var items='()
(mapconcat (lambda (i) (format "- %s" i)) items "\n")
#+END_SRC

#+NAME: make-table
#+BEGIN_SRC elisp :var headers='() :var rows='()
;; Table generation logic...
#+END_SRC

* Visible Content

Features:
#+CALL: make-list(items='("Fast" "Reliable" "Beautiful"))

This pattern keeps generation logic separate from content structure.

#+CALL: Syntax

Full syntax for calling named blocks:

#+CALL: block-name(arg1=value1, arg2=value2)

;; With header args:
#+CALL: block-name() :results raw

;; Inline in text:
This value is call_block-name()[:results raw].

REPL Blocks (Code + Output Styles)

Wrap code and results in REPL blocks for styled presentation.

Simple (default)

Pass-through, no styling. Good for quick examples.

#+BEGIN_REPL
#+BEGIN_SRC python
2 + 2
#+END_SRC

#+RESULTS:
: 4
#+END_REPL

Labeled

Adds a label before output. Good for teaching.

#+BEGIN_REPL :style labeled :label "Output"
#+BEGIN_SRC python
print("Hello")
#+END_SRC

#+RESULTS:
: Hello
#+END_REPL

Callout

Wraps output in a colored callout. Use when output is the point.

#+BEGIN_REPL :style callout :emoji "checkmark" :color green
#+BEGIN_SRC shell
./deploy.sh && echo "Success!"
#+END_SRC

#+RESULTS:
: Success!
#+END_REPL

Toggle

Code in collapsible toggle, output visible. Good for long code.

#+BEGIN_REPL :style toggle :heading "Implementation Details"
#+BEGIN_SRC python
# Long implementation here...
def complex_function():
    pass
#+END_SRC

#+RESULTS:
: Function defined
#+END_REPL

Aside

Wraps everything in aside styling. For tangential examples.

#+BEGIN_REPL :style aside
#+BEGIN_SRC python
# Alternative approach...
#+END_SRC

#+RESULTS:
: ...
#+END_REPL

When to Use What

SituationRecommended Style
Quick example, inlinesimple
Teaching concept, show input/outputlabeled
Important result (success, error, key)callout
Long code readers can skiptoggle
“By the way” alternative approachaside
Regular code, no special treatmentno wrapper

Callout Blocks

Use for tips, warnings, and important notes - not for regular content.

#+BEGIN_CALLOUT :emoji warning :color yellow
Be careful with this approach in production!
#+END_CALLOUT
ColorEmojiUsage
bluebulbInfo, tips
yellowwarningCaution
greencheckSuccess, do this
redxError, don’t
greyNeutral note

Toggle Blocks

For content readers can skip:

#+BEGIN_TOGGLE :heading "Technical Details"
Extended explanation that most readers don't need...
#+END_TOGGLE

Aside Blocks

For tangential information:

#+BEGIN_ASIDE
*Note:* This also works with the older API, though it's deprecated.
#+END_ASIDE

Images

#+ATTR_LEXICAL: :cardWidth wide
[[./images/screenshot.png][Alt text description]]

Card widths: regular, wide, full

Links

External: [[https://ghost.org][Ghost CMS]]
Internal: [[file:other-post.org][Related Post]]
Plain URL: https://example.com (auto-linked)

Lists

Org lists map directly to Lexical lists:

- Unordered item
- Another item
  - Nested item

1. Ordered item
2. Another item
   1. Nested numbered

Tables

Tables export as HTML blocks (Lexical doesn’t have native tables):

| Name | Value |
|------+-------|
| Foo | 42 |
| Bar | 17 |

General Principles

  1. One idea per section - Don’t overload headings
  2. Code then explain - Show the code, then discuss it
  3. Callouts are seasoning - Use sparingly for impact
  4. Toggles hide complexity - Let readers choose depth
  5. Asides are whispers - Brief tangents, not main content
  6. Flat is fine - Don’t over-nest; Lexical is flat anyway

Decoupled Generation Patterns

Pattern 1: Named + CALL

Define once, use anywhere. Good for repeated elements.

* Setup :noexport:
#+NAME: version-badge
#+BEGIN_SRC elisp :var v="1.0.0" :var status="stable"
(format "[[https://img.shields.io/badge/v%s-%s-green]]" v status)
#+END_SRC

* Introduction
Current version: call_version-badge(v="2.1.0", status="beta")[:results raw]

* Changelog
Version call_version-badge(v="2.0.0")[:results raw] added...

Pattern 2: Results Placement with #+NAME:

Name your results block to place output away from source:

#+NAME: stats-output
#+RESULTS: generate-stats

* Appendix :noexport:
#+NAME: generate-stats
#+BEGIN_SRC python :results output
print("Generated statistics...")
#+END_SRC

Pattern 3: Literate Code Assembly

Build code from named fragments:

#+NAME: imports
#+BEGIN_SRC python :exports none
import json
import requests
#+END_SRC

#+NAME: main-logic
#+BEGIN_SRC python :exports none :noweb yes
def fetch_data(url):
    return requests.get(url).json()
#+END_SRC

* The Complete Script
#+BEGIN_SRC python :noweb yes :tangle script.py
<<imports>>
<<main-logic>>
if __name__ == "__main__":
    print(fetch_data("https://api.example.com"))
#+END_SRC

Pattern 4: Conditional Content

Generate content based on document variables:

#+NAME: audience
: developers

#+NAME: intro-for-audience
#+BEGIN_SRC elisp :var audience="general"
(pcase audience
  ("developers" "This guide assumes familiarity with git and CLI.")
  ("designers" "No coding required - we'll use the visual editor.")
  (_ "Welcome to our platform!"))
#+END_SRC

* Introduction
#+CALL: intro-for-audience(audience=(org-entry-get nil "audience" t))

Anti-Patterns

Too Many Headings

Bad:

* Setup
** Installing
*** Step 1
**** Download

Better:

* Setup

** Installing

Download the package...

Callout Overload

Bad: Every other paragraph is a callout

Good: One or two callouts per major section

Unexplained Code

Bad:

#+BEGIN_SRC python
x = foo(bar(baz(y)))
#+END_SRC
#+BEGIN_SRC python
z = transform(x)
#+END_SRC

Better: Add context between code blocks

Version History

VersionDateChanges
0.1.02024-01-29Initial style guide
0.2.02026-01-29Added decoupled generation patterns
Added line width guidelines (80 char max)
Added #+NAME: and #+CALL: documentation
Added copy button note (Prism.js toolbar)