diff --git a/cmd/redditview/main.go b/cmd/redditview/main.go index 995a81b..efc8a06 100644 --- a/cmd/redditview/main.go +++ b/cmd/redditview/main.go @@ -10,6 +10,7 @@ import ( "time" "github.com/azimut/cli-view/internal/reddit" + "github.com/azimut/cli-view/internal/tui" "github.com/fatih/color" ) @@ -19,11 +20,13 @@ type options struct { timeout time.Duration useColors bool userAgent string + useTUI bool } var opts options func init() { + flag.BoolVar(&opts.useTUI, "x", false, "use TUI") flag.BoolVar(&opts.useColors, "C", true, "use colors") flag.DurationVar(&opts.timeout, "t", time.Second*5, "timeout in seconds") flag.StringVar(&opts.userAgent, "A", "Reddit_Cli/0.1", "user agent to send to reddit") @@ -44,18 +47,21 @@ func run(args []string, stdout io.Writer) error { flag.Usage() return errors.New("missing URL argument") } + url := flag.Args()[0] - thread, err := reddit.Fetch( - url, - opts.userAgent, - int(opts.maxWidth), - int(opts.leftPadding), - opts.timeout, - ) + thread, err := reddit.Fetch(url, opts.userAgent, opts.timeout) if err != nil { return err } - fmt.Println(thread) + thread.Width = opts.maxWidth + thread.LeftPadding = opts.leftPadding + + if opts.useTUI { + tui.RenderLoop(reddit.NewProgram(*thread)) + } else { + fmt.Println(thread) + } + return nil } diff --git a/internal/reddit/fetch.go b/internal/reddit/fetch.go index fc0f09a..d25004f 100644 --- a/internal/reddit/fetch.go +++ b/internal/reddit/fetch.go @@ -7,7 +7,7 @@ import ( "github.com/tidwall/gjson" ) -func Fetch(rawUrl, ua string, maxWidth, leftPadding int, timeout time.Duration) (*Thread, error) { +func Fetch(rawUrl, ua string, timeout time.Duration) (*Thread, error) { url, err := effectiveUrl(rawUrl) if err != nil { return nil, err @@ -22,12 +22,14 @@ func Fetch(rawUrl, ua string, maxWidth, leftPadding int, timeout time.Duration) return nil, err } - thread := toThread(rawJson, rawUrl, leftPadding, maxWidth) + thread := toThread(rawJson, rawUrl) return thread, nil } -func toThread(rawJson, rawUrl string, leftPadding, maxWidth int) *Thread { +func toThread(rawJson, rawUrl string) *Thread { + var thread Thread + post := gjson.Get(rawJson, "0.data.children.0.data") op := Op{ author: post.Get("author").String(), @@ -38,19 +40,21 @@ func toThread(rawJson, rawUrl string, leftPadding, maxWidth int) *Thread { title: post.Get("title").String(), upvotes: post.Get("ups").Int(), url: post.Get("url").String(), - printing: Printing{maxWidth: maxWidth, leftPadding: leftPadding}, + thread: &thread, } + thread.op = op if op.nComments <= 0 { - return &Thread{op: op} + return &thread } - var comments []Comment - var json_comments []gjson.Result + json_comments := gjson.Get(rawJson, "1.data.children.#.data").Array() - json_comments = gjson.Get(rawJson, "1.data.children.#.data").Array() for _, json_comment := range json_comments { - comment := toComment(json_comment, &op) + + comment := toComment(json_comment) + comment.thread = &thread + // TODO: is probably a "More..." link if comment.author == "" { continue @@ -59,18 +63,21 @@ func toThread(rawJson, rawUrl string, leftPadding, maxWidth int) *Thread { if comment.author == "[deleted]" && len(comment.jsonReplies) == 0 { continue } - addReplies(&comment, &op) - comments = append(comments, comment) - } - return &Thread{ - op: op, - comments: comments, + + addReplies(&comment) + thread.comments = append(thread.comments, comment) } + + return &thread } -func addReplies(parentComment *Comment, op *Op) { +func addReplies(parentComment *Comment) { + for _, jsonReply := range parentComment.jsonReplies { - comment := toComment(jsonReply, op) + + comment := toComment(jsonReply) + comment.thread = parentComment.thread + // TODO: is probably a "More..." link if comment.author == "" { continue @@ -79,12 +86,13 @@ func addReplies(parentComment *Comment, op *Op) { if comment.author == "[deleted]" && len(comment.jsonReplies) == 0 { continue } + parentComment.replies = append(parentComment.replies, &comment) - addReplies(&comment, op) + addReplies(&comment) } } -func toComment(jsonComment gjson.Result, op *Op) Comment { +func toComment(jsonComment gjson.Result) Comment { return Comment{ author: jsonComment.Get("author").String(), createdUtc: jsonComment.Get("created_utc").Int(), @@ -92,7 +100,6 @@ func toComment(jsonComment gjson.Result, op *Op) Comment { id: jsonComment.Get("id").String(), jsonReplies: jsonComment.Get("replies.data.children.#.data").Array(), message: jsonComment.Get("body").String(), - op: op, score: jsonComment.Get("score").Int(), } } diff --git a/internal/reddit/format.go b/internal/reddit/format.go index 482b2a4..cd23512 100644 --- a/internal/reddit/format.go +++ b/internal/reddit/format.go @@ -20,8 +20,8 @@ var reMarkdownLink = regexp.MustCompile(`\[([^\]]+)\]\(([^\)]+)\)`) var reHTTPLink = regexp.MustCompile(`[^\[^\(^m]http[s]?://[^\s^\[^\(^\[]+`) func (op Op) String() (ret string) { - leftPadding := op.printing.leftPadding - maxWidth := op.printing.maxWidth + leftPadding := int(op.thread.LeftPadding) + maxWidth := int(op.thread.Width) ret += "\n" ret += fmt.Sprintf("title: %s\n", op.title) ret += fmt.Sprintf(" self: %s\n", op.self) @@ -44,8 +44,8 @@ func (op Op) String() (ret string) { } func (comment Comment) String() (ret string) { - leftPadding := comment.op.printing.leftPadding - maxWidth := comment.op.printing.maxWidth + leftPadding := int(comment.thread.LeftPadding) + maxWidth := int(comment.thread.Width) commentLeftPadding := int(comment.depth)*leftPadding + 1 ret += fixupContent( comment.message, @@ -53,7 +53,7 @@ func (comment Comment) String() (ret string) { commentLeftPadding, ) author := comment.author - if comment.author == comment.op.author { + if comment.author == comment.thread.op.author { author = color.New(AuthorColor).Sprint(comment.author) } diff --git a/internal/reddit/tui.go b/internal/reddit/tui.go new file mode 100644 index 0000000..73ac515 --- /dev/null +++ b/internal/reddit/tui.go @@ -0,0 +1,41 @@ +package reddit + +import ( + "fmt" + + "github.com/azimut/cli-view/internal/tui" + tea "github.com/charmbracelet/bubbletea" +) + +const rightPadding = 10 + +type Model struct { + render tui.Model + Thread +} + +func NewProgram(thread Thread) *tea.Program { + return tea.NewProgram(Model{Thread: thread}, + tea.WithAltScreen()) +} + +func (m Model) Init() tea.Cmd { + return nil +} + +func (m Model) View() string { + return m.render.View() +} + +func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + m.render, cmd = m.render.Update(msg) + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.Width = 200 + m.render.RawContent = fmt.Sprint(m) + m.Width = uint(msg.Width) - rightPadding + m.render.Viewport.SetContent(fmt.Sprint(m)) + } + return m, cmd +} diff --git a/internal/reddit/type.go b/internal/reddit/type.go index c3470a3..3ffc513 100644 --- a/internal/reddit/type.go +++ b/internal/reddit/type.go @@ -2,23 +2,20 @@ package reddit import "github.com/tidwall/gjson" -type Printing struct { - leftPadding int - maxWidth int -} - type Thread struct { - comments []Comment - op Op + comments []Comment + op Op + Width uint + LeftPadding uint } type Op struct { author string createdUTC int64 nComments int64 - printing Printing self string selftext string + thread *Thread title string upvotes int64 url string @@ -31,7 +28,7 @@ type Comment struct { id string jsonReplies []gjson.Result message string - op *Op replies []*Comment score int64 + thread *Thread }