Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e4008c0
star working on parser
gocanto Jun 12, 2025
08d98c1
format
gocanto Jun 12, 2025
b9e3b03
better front-matter
gocanto Jun 13, 2025
5e381aa
purge cache
gocanto Jun 13, 2025
2291d44
extract pkg
gocanto Jun 13, 2025
9f1a658
start working on abstraction
gocanto Jun 13, 2025
0cb8fc8
format
gocanto Jun 13, 2025
a4538d7
read uri from CLI
gocanto Jun 13, 2025
349a193
format
gocanto Jun 13, 2025
ba90c4b
start working on menu
gocanto Jun 13, 2025
4442eba
format
gocanto Jun 13, 2025
3ad1f4b
fix
gocanto Jun 13, 2025
6e783c4
reuse colours
gocanto Jun 13, 2025
5bf20db
tweaks
gocanto Jun 13, 2025
d6bc045
format
gocanto Jun 13, 2025
1c76698
connect parsing with menu
gocanto Jun 13, 2025
cc655f0
stract attrs
gocanto Jun 16, 2025
305878f
format
gocanto Jun 16, 2025
9cf16b1
extract handler
gocanto Jun 16, 2025
5c6b92a
pass parsed post
gocanto Jun 16, 2025
90cf628
map post
gocanto Jun 17, 2025
a2508e9
format
gocanto Jun 17, 2025
d9695df
fix seeder
gocanto Jun 17, 2025
907bd21
simpler cli colours
gocanto Jun 17, 2025
f2cec41
start working on persisting posts
gocanto Jun 18, 2025
e11f801
add db transaction
gocanto Jun 18, 2025
f429218
format
gocanto Jun 18, 2025
fe37b99
wire process
gocanto Jun 18, 2025
937e878
fix DB insert
gocanto Jun 18, 2025
dd162b0
tweaks
gocanto Jun 18, 2025
0550083
better abstraction
gocanto Jun 18, 2025
dbe7064
fix panel nil pointers
gocanto Jun 18, 2025
cd24aa0
start working on client
gocanto Jun 18, 2025
27baa51
guard against nil
gocanto Jun 18, 2025
a42e99f
extract http client
gocanto Jun 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion boost/spark.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func Spark(envPath string) (*env.Environment, *pkg.Validator) {
validate := GetDefaultValidate()
validate := pkg.GetDefaultValidator()

envMap, err := godotenv.Read(envPath)

Expand Down
12 changes: 0 additions & 12 deletions boost/validate.go

This file was deleted.

81 changes: 81 additions & 0 deletions cli/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package main

import (
"github.com/oullin/boost"
"github.com/oullin/cli/panel"
"github.com/oullin/cli/posts"
"github.com/oullin/env"
"github.com/oullin/pkg"
"github.com/oullin/pkg/cli"
"time"
)

var environment *env.Environment

func init() {
secrets, _ := boost.Spark("./../.env")

environment = secrets
}

func main() {
menu := panel.MakeMenu()

for {
err := menu.CaptureInput()

if err != nil {
cli.Errorln(err.Error())
continue
}

switch menu.GetChoice() {
case 1:
input, err := menu.CapturePostURL()

if err != nil {
cli.Errorln(err.Error())
continue
}

httpClient := pkg.MakeDefaultClient(nil)
handler := posts.MakeHandler(input, httpClient, environment)

if _, err := handler.NotParsed(); err != nil {
cli.Errorln(err.Error())
continue
}

return
case 2:
showTime()
case 3:
timeParse()
case 0:
cli.Successln("Goodbye!")
return
default:
cli.Errorln("Unknown option. Try again.")
}

cli.Blueln("Press Enter to continue...")

menu.PrintLine()
}
}

func showTime() {
now := time.Now().Format("2006-01-02 15:04:05")

cli.Cyanln("\nThe current time is: " + now)
}

func timeParse() {
s := pkg.MakeStringable("2025-04-12")

if seed, err := s.ToDatetime(); err != nil {
panic(err)
} else {
cli.Magentaln(seed.Format(time.DateTime))
}
}
162 changes: 162 additions & 0 deletions cli/panel/menu.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package panel

import (
"bufio"
"fmt"
"github.com/oullin/cli/posts"
"github.com/oullin/pkg"
"github.com/oullin/pkg/cli"
"golang.org/x/term"
"net/url"
"os"
"strconv"
"strings"
)

type Menu struct {
Choice *int
Reader *bufio.Reader
Validator *pkg.Validator
}

func MakeMenu() Menu {
menu := Menu{
Reader: bufio.NewReader(os.Stdin),
Validator: pkg.GetDefaultValidator(),
}

menu.Print()

return menu
}

func (p *Menu) PrintLine() {
_, _ = p.Reader.ReadString('\n')
}

func (p *Menu) GetChoice() int {
if p.Choice == nil {
return 0
}

return *p.Choice
}

func (p *Menu) CaptureInput() error {
fmt.Print(cli.YellowColour + "Select an option: " + cli.Reset)
input, err := p.Reader.ReadString('\n')

if err != nil {
return fmt.Errorf("%s error reading input: %v %s", cli.RedColour, err, cli.Reset)
}

input = strings.TrimSpace(input)
choice, err := strconv.Atoi(input)

if err != nil {
return fmt.Errorf("%s Please enter a valid number. %s", cli.RedColour, cli.Reset)
}

p.Choice = &choice

return nil
}

func (p *Menu) Print() {
// Try to get the terminal width; default to 80 if it fails
width, _, err := term.GetSize(int(os.Stdout.Fd()))

if err != nil || width < 20 {
width = 80
}

inner := width - 2 // space between the two border chars

// Build box pieces
border := "╔" + strings.Repeat("═", inner) + "╗"
title := "║" + p.CenterText(" Main Menu ", inner) + "║"
divider := "╠" + strings.Repeat("═", inner) + "╣"
footer := "╚" + strings.Repeat("═", inner) + "╝"

// Print in color
fmt.Println()
fmt.Println(cli.CyanColour + border)
fmt.Println(title)
fmt.Println(divider)

p.PrintOption("1) Parse Posts", inner)
p.PrintOption("2) Show Time", inner)
p.PrintOption("3) Show Date", inner)
p.PrintOption("0) Exit", inner)

fmt.Println(footer + cli.Reset)
}

// PrintOption left-pads a space, writes the text, then fills to the full inner width.
func (p *Menu) PrintOption(text string, inner int) {
content := " " + text

if len(content) > inner {
content = content[:inner]
}

padding := inner - len(content)
fmt.Printf("║%s%s║\n", content, strings.Repeat(" ", padding))
}

// CenterText centers s within width, padding with spaces.
func (p *Menu) CenterText(s string, width int) string {
if len(s) >= width {
return s[:width]
}

pad := width - len(s)
left := pad / 2
right := pad - left

return strings.Repeat(" ", left) + s + strings.Repeat(" ", right)
}

func (p *Menu) CapturePostURL() (*posts.Input, error) {
fmt.Print("Enter the post markdown file URL: ")

uri, err := p.Reader.ReadString('\n')

if err != nil {
return nil, fmt.Errorf("%sError reading the given post URL: %v %s", cli.RedColour, err, cli.Reset)
}

uri = strings.TrimSpace(uri)
if uri == "" {
return nil, fmt.Errorf("%sError: no URL provided: %s", cli.RedColour, cli.Reset)
}

parsedURL, err := url.Parse(uri)
if err != nil {
return nil, fmt.Errorf("%sError: invalid URL: %v %s", cli.RedColour, err, cli.Reset)
}

if parsedURL.Scheme != "https" || parsedURL.Host != "raw.githubusercontent.com" {
return nil, fmt.Errorf("%sError: URL must begin with https://raw.githubusercontent.com %s", cli.RedColour, cli.Reset)
}

input := posts.Input{
Url: parsedURL.String(),
}

validate := p.Validator

if _, err := validate.Rejects(input); err != nil {
return nil, fmt.Errorf(
"%sError validating the given post URL: %v %s \n%sViolations:%s %s",
cli.RedColour,
err,
cli.Reset,
cli.BlueColour,
cli.Reset,
validate.GetErrorsAsJason(),
)
}

return &input, nil
}
89 changes: 89 additions & 0 deletions cli/posts/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package posts

import (
"context"
"fmt"
"github.com/oullin/boost"
"github.com/oullin/database/repository"
"github.com/oullin/env"
"github.com/oullin/pkg"
"github.com/oullin/pkg/markdown"
"net/http"
"time"
)

type Handler struct {
Input *Input
Client *pkg.Client
Posts *repository.Posts
Users *repository.Users
IsDebugging bool
}

func MakeHandler(input *Input, client *pkg.Client, env *env.Environment) Handler {
db := boost.MakeDbConnection(env)

return Handler{
Input: input,
Client: client,
IsDebugging: false,
Posts: &repository.Posts{
DB: db,
Categories: &repository.Categories{
DB: db,
},
},
Users: &repository.Users{
DB: db,
},
}
}

func (h Handler) NotParsed() (bool, error) {
var err error
var content string
uri := h.Input.Url

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

h.Client.OnHeaders = func(req *http.Request) {
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Pragma", "no-cache")
}

content, err = h.Client.Get(ctx, uri)

if err != nil {
return false, fmt.Errorf("error fetching url [%s]: %w", uri, err)
}

var article *markdown.Post
if article, err = markdown.Parse(content); err != nil || article == nil {
return false, fmt.Errorf("error parsing url [%s]: %w", uri, err)
}

if h.IsDebugging {
h.RenderArticle(article)
}

if err = h.HandlePost(article); err != nil {
return true, err
}

return true, nil
}

func (h Handler) RenderArticle(post *markdown.Post) {
fmt.Printf("Title: %s\n", post.Title)
fmt.Printf("Excerpt: %s\n", post.Excerpt)
fmt.Printf("Slug: %s\n", post.Slug)
fmt.Printf("Author: %s\n", post.Author)
fmt.Printf("Image URL: %s\n", post.ImageURL)
fmt.Printf("Image Alt: %s\n", post.ImageAlt)
fmt.Printf("Category: %s\n", post.Category)
fmt.Printf("Category Slug: %s\n", post.CategorySlug)
fmt.Printf("Tags Alt: %s\n", post.Tags)
fmt.Println("\n--- Content ---")
fmt.Println(post.Content)
}
Loading
Loading