Skip to content

Commit

Permalink
move dump back to dump
Browse files Browse the repository at this point in the history
  • Loading branch information
rusq committed Jan 27, 2023
1 parent ef2a9ad commit 78ea432
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 185 deletions.
177 changes: 162 additions & 15 deletions cmd/slackdump/internal/dump/dump.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,178 @@
package dump

import (
"context"
_ "embed"
"encoding/json"
"errors"
"flag"
"fmt"
"runtime/trace"
"strings"
"text/template"
"time"

"github.com/rusq/dlog"
"github.com/rusq/slackdump/v2"
"github.com/rusq/slackdump/v2/auth"
"github.com/rusq/slackdump/v2/cmd/slackdump/internal/cfg"
"github.com/rusq/slackdump/v2/cmd/slackdump/internal/golang/base"
"github.com/rusq/slackdump/v2/cmd/slackdump/internal/list"
"github.com/rusq/slackdump/v2/fsadapter"
"github.com/rusq/slackdump/v2/internal/app/config"
"github.com/rusq/slackdump/v2/internal/app/nametmpl"
"github.com/rusq/slackdump/v2/internal/structures"
"github.com/rusq/slackdump/v2/types"
)

//go:embed assets/list_conversation.md
var dumpMd string

const codeBlock = "```"

// CmdDump is the dump command.
var CmdDump = &base.Command{
UsageLine: "slackdump dump [flags] <IDs or URLs>",
Short: "dump individual conversations or threads",
Long: `
# Dump Command
This command is an alias for:
` + codeBlock + `
slackdump list conversation
` + codeBlock + `
To get extended usage help, run ` + "`slackdump help list conversation`",
UsageLine: "slackdump dump [flags] <IDs or URLs>",
Short: "dump individual conversations or threads",
Long: dumpMd,
RequireAuth: true,
PrintFlags: true,
}

func init() {
CmdDump.Run = list.RunDump
CmdDump.Wizard = list.WizDump
CmdDump.Long = list.HelpDump(CmdDump)
CmdDump.Run = RunDump
CmdDump.Wizard = WizDump
CmdDump.Long = HelpDump(CmdDump)
}

// ErrNothingToDo is returned if there's no links to dump.
var ErrNothingToDo = errors.New("no conversations to dump, run \"slackdump help dump\"")

type options struct {
Oldest time.Time // Oldest is the timestamp of the oldest message to fetch.
Latest time.Time // Latest is the timestamp of the newest message to fetch.
NameTemplate string // NameTemplate is the template for the output file name.
JSONL bool // JSONL should be true if the output should be JSONL instead of JSON.
}

var opts options

// ptr returns a pointer to the given value.
func ptr[T any](a T) *T { return &a }

// InitDumpFlagset initializes the flagset for the dump command.
func InitDumpFlagset(fs *flag.FlagSet) {
fs.Var(ptr(config.TimeValue(opts.Oldest)), "from", "timestamp of the oldest message to fetch")
fs.Var(ptr(config.TimeValue(opts.Latest)), "to", "timestamp of the newest message to fetch")
fs.StringVar(&opts.NameTemplate, "ft", nametmpl.Default, "output file naming template.\n")
fs.BoolVar(&opts.JSONL, "jsonl", false, "output JSONL instead of JSON")
}

// RunDump is the main entry point for the dump command.
func RunDump(ctx context.Context, cmd *base.Command, args []string) error {
if len(args) == 0 {
base.SetExitStatus(base.SInvalidParameters)
return ErrNothingToDo
}
// Retrieve the Authentication provider.
prov, err := auth.FromContext(ctx)
if err != nil {
base.SetExitStatus(base.SApplicationError)
return err
}

// initialize the list of entities to dump.
list, err := structures.NewEntityList(args)
if err != nil {
base.SetExitStatus(base.SInvalidParameters)
return err
} else if list.IsEmpty() {
base.SetExitStatus(base.SInvalidParameters)
return ErrNothingToDo
}

// initialize the file naming template.
if opts.NameTemplate == "" {
opts.NameTemplate = nametmpl.Default
}
namer, err := newNamer(opts.NameTemplate, "json")
if err != nil {
base.SetExitStatus(base.SUserError)
return fmt.Errorf("file template error: %w", err)
}

// Initialize the filesystem.
fs, err := fsadapter.New(cfg.BaseLoc)
if err != nil {
base.SetExitStatus(base.SApplicationError)
return err
}
defer fs.Close()

// Initialize the session.
cfg.SlackOptions.Filesystem = fs
cfg.SlackOptions.Logger = dlog.FromContext(ctx)
sess, err := slackdump.NewWithOptions(ctx, prov, cfg.SlackOptions)
if err != nil {
base.SetExitStatus(base.SApplicationError)
return err
}

// Dump conversations.
for _, link := range list.Include {
conv, err := sess.Dump(ctx, link, opts.Oldest, opts.Latest)
if err != nil {
return err
}

if err := save(ctx, fs, namer.Filename(conv), conv); err != nil {
return err
}
}
return nil
}

type namer struct {
t *template.Template
ext string
}

func newNamer(tmpl string, eext string) (namer, error) {
t, err := template.New("name").Parse(tmpl)
if err != nil {
return namer{}, err
}
return namer{t: t, ext: eext}, nil
}

// Filename returns the filename for the given conversation.
func (n namer) Filename(conv *types.Conversation) string {
var buf strings.Builder
if err := n.t.Execute(&buf, conv); err != nil {
panic(err)
}
return buf.String() + "." + n.ext
}

func save(ctx context.Context, fs fsadapter.FS, filename string, conv *types.Conversation) error {
_, task := trace.NewTask(ctx, "saveData")
defer task.End()

f, err := fs.Create(filename)
if err != nil {
return err
}
defer f.Close()

return json.NewEncoder(f).Encode(conv)
}

var helpTmpl = template.Must(template.New("dumphelp").Parse(string(dumpMd)))

// HelpDump returns the help message for the dump command.
func HelpDump(cmd *base.Command) string {
var buf strings.Builder
if err := helpTmpl.Execute(&buf, cmd); err != nil {
panic(err)
}
return buf.String()
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package list
package dump

import (
"context"
Expand Down
155 changes: 0 additions & 155 deletions cmd/slackdump/internal/list/conversation.go

This file was deleted.

17 changes: 11 additions & 6 deletions cmd/slackdump/internal/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ which is sometimes unreasonably slow.
Commands: []*base.Command{
CmdListUsers,
CmdListChannels,
CmdListConversation,
},
}

Expand Down Expand Up @@ -107,16 +106,20 @@ func saveData(ctx context.Context, sess *slackdump.Session, data any, filename s
}
defer fs.Close()

if err := writeData(ctx, fs, filename, data, typ, sess.Users); err != nil {
return err
}
dlog.FromContext(ctx).Printf("Data saved to: %q\n", filepath.Join(cfg.BaseLoc, filename))
return nil
}

func writeData(ctx context.Context, fs fsadapter.FS, filename string, data any, typ format.Type, users []slack.User) error {
f, err := fs.Create(filename)
if err != nil {
return fmt.Errorf("failed to create file: %w", err)
}
defer f.Close()
if err := fmtPrint(ctx, f, data, typ, sess.Users); err != nil {
return err
}
dlog.FromContext(ctx).Printf("Data saved to: %q\n", filepath.Join(cfg.BaseLoc, filename))
return nil
return fmtPrint(ctx, f, data, typ, users)
}

// fmtPrint prints the given data to the given writer, using the given format.
Expand All @@ -138,6 +141,8 @@ func fmtPrint(ctx context.Context, w io.Writer, a any, typ format.Type, u []slac
return cvt.Channels(ctx, w, u, val)
case types.Users:
return cvt.Users(ctx, w, val)
case *types.Conversation:
return cvt.Conversation(ctx, w, u, val)
default:
return fmt.Errorf("unsupported data type: %T", a)
}
Expand Down
Loading

0 comments on commit 78ea432

Please sign in to comment.