-
-
Notifications
You must be signed in to change notification settings - Fork 0
Blog post parser #32
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
Merged
Merged
Blog post parser #32
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 08d98c1
format
gocanto b9e3b03
better front-matter
gocanto 5e381aa
purge cache
gocanto 2291d44
extract pkg
gocanto 9f1a658
start working on abstraction
gocanto 0cb8fc8
format
gocanto a4538d7
read uri from CLI
gocanto 349a193
format
gocanto ba90c4b
start working on menu
gocanto 4442eba
format
gocanto 3ad1f4b
fix
gocanto 6e783c4
reuse colours
gocanto 5bf20db
tweaks
gocanto d6bc045
format
gocanto 1c76698
connect parsing with menu
gocanto cc655f0
stract attrs
gocanto 305878f
format
gocanto 9cf16b1
extract handler
gocanto 5c6b92a
pass parsed post
gocanto 90cf628
map post
gocanto a2508e9
format
gocanto d9695df
fix seeder
gocanto 907bd21
simpler cli colours
gocanto f2cec41
start working on persisting posts
gocanto e11f801
add db transaction
gocanto f429218
format
gocanto fe37b99
wire process
gocanto 937e878
fix DB insert
gocanto dd162b0
tweaks
gocanto 0550083
better abstraction
gocanto dbe7064
fix panel nil pointers
gocanto cd24aa0
start working on client
gocanto 27baa51
guard against nil
gocanto a42e99f
extract http client
gocanto 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 was deleted.
Oops, something went wrong.
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,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 | ||
gocanto marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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 { | ||
gocanto marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| cli.Magentaln(seed.Format(time.DateTime)) | ||
| } | ||
| } | ||
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,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) | ||
| } | ||
gocanto marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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 | ||
| } | ||
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,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) | ||
|
|
||
gocanto marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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) | ||
| } | ||
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.