diff options
| author | Himanshu Sardana <himanshusardana2005@gmail.com> | 2026-03-26 22:31:01 +0000 |
|---|---|---|
| committer | Himanshu Sardana <himanshusardana2005@gmail.com> | 2026-03-26 22:31:01 +0000 |
| commit | e49d32933fc31cdf33fc2aa40688f4c8d874a66e (patch) | |
| tree | 0f4aa695a93d206a3bf59760ef105804e3cd966e | |
| parent | 2c09f0ab4de62e6a7d1d5e8f74f01097f3a3588d (diff) | |
feat: add init command
| -rw-r--r-- | cmd/init.go | 266 | ||||
| -rw-r--r-- | cmd/root.go | 11 |
2 files changed, 277 insertions, 0 deletions
diff --git a/cmd/init.go b/cmd/init.go new file mode 100644 index 0000000..e7fe58a --- /dev/null +++ b/cmd/init.go @@ -0,0 +1,266 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +var ( + headerStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("86")). + Bold(true). + Padding(0, 0, 1, 0) + + inputStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("252")). + Background(lipgloss.Color("235")). + Padding(0, 1) + + buttonStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("86")). + Background(lipgloss.Color("235")). + Padding(0, 2). + Margin(0, 1) + + buttonActiveStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("235")). + Background(lipgloss.Color("86")). + Padding(0, 2). + Margin(0, 1) + + helpStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("240")) +) + +type InitModel struct { + step int + blogName string + siteTitle string + authorName string + authorRole string + authorBio string + theme string + themes []string + cursor int + inputBuffer string + quitting bool + focusedInput bool +} + +func (m *InitModel) Init() tea.Cmd { + m.themes = []string{ + "modern-light", + "modern-dark", + "modern-dark-2", + "modern-dark-catppuccin", + "everforest", + "gruvbox", + "rose-pine", + "terminal-gruvbox", + "tufte", + } + return nil +} + +func (m *InitModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "ctrl+c", "esc": + m.quitting = true + return m, nil + case "enter": + return m.handleEnter() + case "up": + if m.step == 6 { + m.cursor-- + if m.cursor < 0 { + m.cursor = len(m.themes) - 1 + } + } + case "down": + if m.step == 6 { + m.cursor++ + if m.cursor >= len(m.themes) { + m.cursor = 0 + } + } + case "backspace": + if len(m.inputBuffer) > 0 { + m.inputBuffer = m.inputBuffer[:len(m.inputBuffer)-1] + } + case "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", + "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", + "-", "_", " ", ".": + m.inputBuffer += msg.String() + } + } + return m, nil +} + +func (m *InitModel) handleEnter() (tea.Model, tea.Cmd) { + switch m.step { + case 0: + m.blogName = m.inputBuffer + m.inputBuffer = "" + m.step++ + case 1: + m.siteTitle = m.inputBuffer + if m.siteTitle == "" { + m.siteTitle = m.blogName + } + m.inputBuffer = "" + m.step++ + case 2: + m.authorName = m.inputBuffer + m.inputBuffer = "" + m.step++ + case 3: + m.authorRole = m.inputBuffer + m.inputBuffer = "" + m.step++ + case 4: + m.authorBio = m.inputBuffer + m.inputBuffer = "" + m.step++ + case 5: + m.theme = m.inputBuffer + m.inputBuffer = "" + if m.theme != "" { + m.step = 7 + } else { + m.step++ + } + case 6: + m.theme = m.themes[m.cursor] + m.step++ + } + return m, nil +} + +func (m *InitModel) View() string { + var s string + + switch m.step { + case 0: + s = headerStyle.Render("╭─── Kite Setup") + "\n" + + "\n" + "What's the name of your blog?" + "\n" + + "(e.g. my-tech-blog, dev-diary)" + "\n\n" + + inputStyle.Render(m.inputBuffer+"_") + "\n\n" + + helpStyle.Render("type to enter · enter to continue · esc to cancel") + case 1: + s = headerStyle.Render("╭─── Kite Setup") + "\n" + + "\n" + "Site title (for the header):" + "\n\n" + + inputStyle.Render(m.inputBuffer+"_") + "\n\n" + + helpStyle.Render("type to enter · enter to continue · esc to cancel") + case 2: + s = headerStyle.Render("╭─── Kite Setup") + "\n" + + "\n" + "Your name:" + "\n\n" + + inputStyle.Render(m.inputBuffer+"_") + "\n\n" + + helpStyle.Render("type to enter · enter to continue · esc to cancel") + case 3: + s = headerStyle.Render("╭─── Kite Setup") + "\n" + + "\n" + "Your role (e.g. Developer, Writer):" + "\n\n" + + inputStyle.Render(m.inputBuffer+"_") + "\n\n" + + helpStyle.Render("type to enter · enter to continue · esc to cancel") + case 4: + s = headerStyle.Render("╭─── Kite Setup") + "\n" + + "\n" + "Short bio:" + "\n\n" + + inputStyle.Render(m.inputBuffer+"_") + "\n\n" + + helpStyle.Render("type to enter · enter to continue · esc to cancel") + case 5: + s = headerStyle.Render("╭─── Kite Setup") + "\n" + + "\n" + "Preferred theme (or press enter to skip):" + "\n\n" + + inputStyle.Render(m.inputBuffer+"_") + "\n\n" + + helpStyle.Render("type to enter · enter to skip · esc to cancel") + case 6: + s = headerStyle.Render("╭─── Kite Setup") + "\n\n" + + "Select a theme:\n\n" + for i, theme := range m.themes { + if i == m.cursor { + s += " " + buttonActiveStyle.Render("● "+theme) + "\n" + } else { + s += " " + buttonStyle.Render("○ "+theme) + "\n" + } + } + s += "\n" + helpStyle.Render("↑↓ to select · enter to confirm") + } + + return s +} + +func RunInit() error { + m := &InitModel{} + p := tea.NewProgram(m, tea.WithAltScreen()) + if _, err := p.Run(); err != nil { + return err + } + + if m.quitting { + fmt.Println("\nInit cancelled.") + return nil + } + + fmt.Println("\n" + headerStyle.Render("Setting up your blog...")) + + theme := m.theme + if theme == "" { + theme = "modern-light" + } + + dirs := []string{"content", "output", "themes"} + for _, dir := range dirs { + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("creating %s directory: %w", dir, err) + } + } + + siteTitle := m.siteTitle + if siteTitle == "" { + siteTitle = m.blogName + } + + configContent := fmt.Sprintf(`siteTitle: "%s" +authorName: "%s" +authorRole: "%s" +authorBio: "%s" +defaultTheme: "%s" +`, siteTitle, m.authorName, m.authorRole, m.authorBio, theme) + + if err := os.WriteFile("config.yaml", []byte(configContent), 0644); err != nil { + return fmt.Errorf("writing config: %w", err) + } + + sampleContent := "---\n" + + "title: Welcome to Kite\n" + + "date: 2026-01-01\n" + + "tags: [getting-started]\n" + + "---\n\n" + + "# Welcome\n\n" + + "This is your first post! Write your content in Markdown here.\n\n" + + "## Getting Started\n\n" + + "- Add more posts to the content/ directory\n" + + "- Run `kite build` to generate your site\n" + + "- Run `kite serve` to preview locally\n\n" + + "Enjoy blogging!\n" + + if err := os.WriteFile("content/1.md", []byte(sampleContent), 0644); err != nil { + return fmt.Errorf("writing sample content: %w", err) + } + + fmt.Println(" ✓ Created config.yaml") + fmt.Println(" ✓ Created content/ directory") + fmt.Println(" ✓ Created output/ directory") + fmt.Println(" ✓ Created themes/ directory") + fmt.Println(" ✓ Created sample post (content/1.md)") + fmt.Println("\nRun `kite build` to generate your site!") + fmt.Println("Run `kite serve` to preview it locally.") + + return nil +} diff --git a/cmd/root.go b/cmd/root.go index 79bb257..3ef07bc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -26,11 +26,20 @@ func Execute() { runServe(args) case "list-themes": runListThemes(args) + case "init": + runInit(args) default: build.ShowHelpMessage() } } +func runInit(args []string) { + if err := RunInit(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} + func ShowHelp() { fmt.Println(` Kite — A lightweight static site generator @@ -42,6 +51,7 @@ COMMANDS: build Build the static site into the output directory serve Start a local development server with live reload list-themes List all available themes + init Initialize a new blog project OPTIONS: -h, --help Show this help message @@ -51,6 +61,7 @@ EXAMPLES: kite build gruvbox kite serve kite list-themes + kite init DESCRIPTION: Kite converts your content into a static website using themes and templates. |
