-
Notifications
You must be signed in to change notification settings - Fork 7
Add ghost tutorial command and generated tutorial doc #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
murrayju
wants to merge
2
commits into
main
Choose a base branch
from
murrayju/tutorial
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/timescale/ghost/internal/tutorial" | ||
| ) | ||
|
|
||
| // renderTutorialMarkdown walks a tutorial.Tutorial and emits markdown. The | ||
| // renderer is content-agnostic: every piece of tutorial-specific text comes | ||
| // from the struct, so updating a tutorial only requires editing its | ||
| // definition in the tutorial package. | ||
| func renderTutorialMarkdown(t tutorial.Tutorial) string { | ||
| var sb strings.Builder | ||
|
|
||
| fmt.Fprintf(&sb, "# %s\n\n", t.Title) | ||
| if t.Callout != "" { | ||
| fmt.Fprintf(&sb, "> %s\n\n", t.Callout) | ||
| } | ||
| writeParagraphs(&sb, t.Intro) | ||
|
|
||
| for i, step := range t.Steps { | ||
| writeStepMarkdown(&sb, i+1, step) | ||
| } | ||
| writeStepMarkdown(&sb, len(t.Steps)+1, t.DeleteStep) | ||
|
|
||
| return strings.TrimRight(sb.String(), "\n") + "\n" | ||
| } | ||
|
|
||
| func writeParagraphs(sb *strings.Builder, paragraphs []string) { | ||
| for _, p := range paragraphs { | ||
| sb.WriteString(p) | ||
| sb.WriteString("\n\n") | ||
| } | ||
| } | ||
|
|
||
| func writeStepMarkdown(sb *strings.Builder, number int, step tutorial.Step) { | ||
| fmt.Fprintf(sb, "## Step %d — %s\n\n", number, step.Title) | ||
|
|
||
| visibleBlocks := tutorial.FilterBlocks(step.Blocks, tutorial.TargetDocsOnly) | ||
|
|
||
| if step.JoinedBlocks { | ||
| for _, block := range visibleBlocks { | ||
| if block.Prose != "" { | ||
| sb.WriteString(block.Prose + "\n\n") | ||
| } | ||
| } | ||
| commands := make([]string, 0, len(visibleBlocks)) | ||
| outputs := make([]string, 0, len(visibleBlocks)) | ||
| for _, block := range visibleBlocks { | ||
| if len(block.Args) == 0 { | ||
| continue | ||
| } | ||
| commands = append(commands, tutorial.FormatCommand(block.Args)) | ||
| if block.ExpectedOutput != "" { | ||
| outputs = append(outputs, block.ExpectedOutput) | ||
| } | ||
| } | ||
| if len(commands) > 0 { | ||
| writeCodeBlock(sb, "bash", strings.Join(commands, "\n")) | ||
| } | ||
| if len(outputs) > 0 { | ||
| writeCodeBlock(sb, "", strings.Join(outputs, "\n")) | ||
| } | ||
| return | ||
| } | ||
|
|
||
| for _, block := range visibleBlocks { | ||
| if block.Prose != "" { | ||
| sb.WriteString(block.Prose + "\n\n") | ||
| } | ||
| if len(block.Args) > 0 { | ||
| writeCodeBlock(sb, "bash", tutorial.FormatCommand(block.Args)) | ||
| if block.ExpectedOutput != "" { | ||
| writeCodeBlock(sb, "", block.ExpectedOutput) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func writeCodeBlock(sb *strings.Builder, lang, content string) { | ||
| sb.WriteString("```") | ||
| sb.WriteString(lang) | ||
| sb.WriteString("\n") | ||
| sb.WriteString(content) | ||
| sb.WriteString("\n```\n\n") | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "os" | ||
| "path/filepath" | ||
| "testing" | ||
|
|
||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/timescale/ghost/internal/tutorial" | ||
| ) | ||
|
|
||
| func TestAllTutorialDocsMatchGoldenFiles(t *testing.T) { | ||
| for _, tut := range tutorial.All() { | ||
| t.Run(tut.Filename, func(t *testing.T) { | ||
| goldenPath := filepath.Join("..", "..", "docs", "tutorials", tut.Filename) | ||
| want, err := os.ReadFile(goldenPath) | ||
| if err != nil { | ||
| t.Fatalf("failed to read %s: %v", goldenPath, err) | ||
| } | ||
| got := renderTutorialMarkdown(tut) | ||
| if diff := cmp.Diff(string(want), got); diff != "" { | ||
| t.Errorf("%s out of date (run `go run ./cmd/generate-tutorial-docs`):\n%s", goldenPath, diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "flag" | ||
| "fmt" | ||
| "log" | ||
| "os" | ||
| "path/filepath" | ||
|
|
||
| "github.com/timescale/ghost/internal/tutorial" | ||
| ) | ||
|
|
||
| func main() { | ||
| outDir := flag.String("out", "./docs/tutorials", "Output directory") | ||
| flag.Parse() | ||
|
|
||
| if err := os.MkdirAll(*outDir, 0o755); err != nil { | ||
| log.Fatal(err) | ||
| } | ||
|
|
||
| for _, t := range tutorial.All() { | ||
| path := filepath.Join(*outDir, t.Filename) | ||
| if err := os.WriteFile(path, []byte(renderTutorialMarkdown(t)), 0o644); err != nil { | ||
| log.Fatal(err) | ||
| } | ||
| fmt.Printf("Generated %s\n", path) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| --- | ||
| title: "ghost tutorial" | ||
| slug: "ghost_tutorial" | ||
| description: "CLI reference for ghost tutorial" | ||
| --- | ||
|
|
||
| ## ghost tutorial | ||
|
|
||
| Run an interactive Ghost tutorial | ||
|
|
||
| ### Synopsis | ||
|
|
||
| Run an interactive tutorial that demonstrates the core Ghost workflow. | ||
|
|
||
| The tutorial creates a temporary database, inserts | ||
| sample data, forks the database, mutates the fork, compares the original and | ||
| fork, and then asks whether to delete or keep the tutorial databases. Each step | ||
| explains and echoes the equivalent Ghost CLI command before running it. | ||
|
|
||
| ``` | ||
| ghost tutorial [flags] | ||
| ``` | ||
|
|
||
| ### Examples | ||
|
|
||
| ``` | ||
| ghost tutorial | ||
| ``` | ||
|
|
||
| ### Options | ||
|
|
||
| ``` | ||
| -h, --help help for tutorial | ||
| ``` | ||
|
|
||
| ### Options inherited from parent commands | ||
|
|
||
| ``` | ||
| --analytics enable/disable usage analytics (default true) | ||
| --color enable colored output (default true) | ||
| --config-dir string config directory (default "~/.config/ghost") | ||
| --version-check check for updates (default true) | ||
| ``` | ||
|
|
||
| ### SEE ALSO | ||
|
|
||
| * [ghost](ghost.md) - CLI for managing Postgres databases |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| # Learn the basics of Ghost | ||
|
|
||
| > Run `ghost tutorial` to step through this tutorial live in the CLI. | ||
|
|
||
| This guided tour walks through the core Ghost workflow: create a database, load data, fork it, change the fork, compare the results, and clean up. Each step shows the exact `ghost` command the live tutorial runs and the output you can expect to see. | ||
|
|
||
| Throughout this guide, the temporary databases are named `tutorial-example` and `tutorial-example-fork`. The live `ghost tutorial` command generates a random suffix instead. | ||
|
|
||
| ## Step 1 — Create a database | ||
|
|
||
| ```bash | ||
| ghost create --name tutorial-example --wait | ||
| ``` | ||
|
|
||
| ``` | ||
| Created database 'tutorial-example' | ||
| ID: abc1234567 | ||
| Connection: postgresql://tsdbadmin:<password>@<host>:5432/tsdb?sslmode=require | ||
| ``` | ||
|
|
||
| ## Step 2 — Add sample data with SQL | ||
|
|
||
| The sql command connects to the database and executes the query you provide. | ||
|
|
||
| ```bash | ||
| ghost sql tutorial-example \ | ||
| "CREATE TABLE ghost_tutorial_items (id serial PRIMARY KEY, name text NOT NULL, location text NOT NULL); | ||
| INSERT INTO ghost_tutorial_items (name, location) VALUES ('apples', 'original'), ('bananas', 'original'), ('carrots', 'original');" | ||
| ``` | ||
|
|
||
| ``` | ||
| CREATE TABLE | ||
| INSERT 0 3 | ||
| ``` | ||
|
|
||
| ## Step 3 — Query the original database | ||
|
|
||
| ```bash | ||
| ghost sql tutorial-example "SELECT id, name, location FROM ghost_tutorial_items ORDER BY id;" | ||
| ``` | ||
|
|
||
| ``` | ||
| id │ name │ location | ||
| ────┼─────────┼────────── | ||
| 1 │ apples │ original | ||
| 2 │ bananas │ original | ||
| 3 │ carrots │ original | ||
| (3 rows) | ||
| ``` | ||
|
|
||
| ## Step 4 — Fork the database | ||
|
|
||
| Forking creates an independent copy you can safely experiment with. | ||
|
|
||
| ```bash | ||
| ghost fork tutorial-example --name tutorial-example-fork --wait | ||
| ``` | ||
|
|
||
| ``` | ||
| Forked 'tutorial-example' → 'tutorial-example-fork' | ||
| ID: def1234567 | ||
| Connection: postgresql://tsdbadmin:<password>@<host>:5432/tsdb?sslmode=require | ||
| ``` | ||
|
|
||
| ## Step 5 — Mutate the fork | ||
|
|
||
| These changes are made only on the fork. | ||
|
|
||
| ```bash | ||
| ghost sql tutorial-example-fork \ | ||
| "INSERT INTO ghost_tutorial_items (name, location) VALUES ('dragonfruit', 'fork'); | ||
| UPDATE ghost_tutorial_items SET location = 'fork' WHERE name = 'bananas';" | ||
| ``` | ||
|
|
||
| ``` | ||
| INSERT 0 1 | ||
| UPDATE 1 | ||
| ``` | ||
|
|
||
| ## Step 6 — Compare the original and the fork | ||
|
|
||
| First, query the original database: | ||
|
|
||
| ```bash | ||
| ghost sql tutorial-example "SELECT id, name, location FROM ghost_tutorial_items ORDER BY id;" | ||
| ``` | ||
|
|
||
| ``` | ||
| id │ name │ location | ||
| ────┼─────────┼────────── | ||
| 1 │ apples │ original | ||
| 2 │ bananas │ original | ||
| 3 │ carrots │ original | ||
| (3 rows) | ||
| ``` | ||
|
|
||
| Now query the fork. Notice the extra row and updated value: | ||
|
|
||
| ```bash | ||
| ghost sql tutorial-example-fork "SELECT id, name, location FROM ghost_tutorial_items ORDER BY id;" | ||
| ``` | ||
|
|
||
| ``` | ||
| id │ name │ location | ||
| ────┼─────────────┼────────── | ||
| 1 │ apples │ original | ||
| 2 │ bananas │ fork | ||
| 3 │ carrots │ original | ||
| 4 │ dragonfruit │ fork | ||
| (4 rows) | ||
| ``` | ||
|
|
||
| ## Step 7 — Delete the tutorial databases | ||
|
|
||
| When the main steps finish, the live tutorial asks whether to delete the databases. To run the cleanup step yourself, use the following. | ||
|
|
||
| ```bash | ||
| ghost delete tutorial-example-fork --confirm | ||
| ghost delete tutorial-example --confirm | ||
| ``` | ||
|
|
||
| ``` | ||
| Deleted 'tutorial-example-fork' (def1234567) | ||
| Deleted 'tutorial-example' (abc1234567) | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a plan to do something with this markdown version of the tutorial?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The existing tutorials in this folder are published to https://ghost.build/tutorials/. I think that's automated, so this will also appear there? Either way, my intention is to publish it there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think @thenoahhein probably set that up. Not sure if it's fully automated or not.