Import and Export dispatched through switches that had to be kept in step with the table; now a format is one row and one file. The API used by the UI and cmd/012 is unchanged.
- commit
- b5ee00dd062dc2d9079704ab1e0648f79302acc8
- parent
- ad230063629c3a4588b4b9ff9101c7fd4cb5a90b
- author
- Dami <dami.etoile@gmail.com>
| M | internal/ | 16 | +++++ |
| M | internal/ | 104 | ++++++++++++++++-------------- |
| M | internal/ | 3 | +- |
| M | internal/ | 3 | +- |
| M | internal/ | 3 | +- |
| M | internal/ | 5 | +- |
@@ -146,6 +146,22 @@ func (c *countingReader) Read(p []byte) (int, error) {
146146 return n, err147147 }148148 149+func importCSV(ctx context.Context, name string, opt Options) (*Result, error) {150+ return importDelimited(ctx, name, CSV, opt.Progress)151+}152+153+func importTSV(ctx context.Context, name string, opt Options) (*Result, error) {154+ return importDelimited(ctx, name, TSV, opt.Progress)155+}156+157+func exportCSV(_ context.Context, name string, snap *Snapshot, _ ExportOptions) (*ExportResult, error) {158+ return exportDelimited(name, CSV, snap)159+}160+161+func exportTSV(_ context.Context, name string, snap *Snapshot, _ ExportOptions) (*ExportResult, error) {162+ return exportDelimited(name, TSV, snap)163+}164+149165 func importDelimited(ctx context.Context, name string, k Kind, prog *Progress) (*Result, error) {150166 f, err := os.Open(name)151167 if err != nil {@@ -31,19 +31,45 @@ const (
3131 WK13232 )3333 34-var kinds = []struct {35- kind Kind36- name string // short name, e.g. in "Export as CSV"37- label string // what the file is, for the import picker38- exts []string // recognized extensions, the first one written39- export bool40-}{41- {CSV, "CSV", "Comma-separated values", []string{".csv"}, true},42- {TSV, "TSV", "Tab-separated values", []string{".tsv", ".tab"}, true},43- {XLSX, "XLSX", "Excel workbook", []string{".xlsx", ".xlsm"}, true},44- {SQLite, "SQLite", "SQLite database", []string{".sqlite", ".sqlite3", ".db"}, true},45- {Parquet, "Parquet", "Parquet file", []string{".parquet"}, false},46- {WK1, "WK1", "Lotus 1-2-3 worksheet", []string{".wk1", ".wks"}, false},34+// A fileFormat is everything about a Kind: its names, its extensions, and35+// the functions reading and writing it. Adding a format is a file with36+// its importer (and exporter) and a row here.37+type fileFormat struct {38+ kind Kind39+ name string // short name, e.g. in "Export as CSV"40+ label string // what the file is, for the import picker41+ exts []string // recognized extensions, the first one written42+ // book is set for formats that hold named sheets of their own; a43+ // file of any other kind names its one sheet after the file.44+ book bool45+ read importer46+ // write is nil for formats 012 only imports.47+ write exporter48+}49+50+// An importer reads a file into a new workbook, returning the sheet to51+// show. It checks ctx between rows, and updates opt.Progress.52+type importer func(ctx context.Context, name string, opt Options) (*Result, error)53+54+// An exporter writes a snapshot to a file.55+type exporter func(ctx context.Context, name string, snap *Snapshot, opt ExportOptions) (*ExportResult, error)56+57+// kinds are the formats, in Kind order, which is menu order.58+var kinds = []fileFormat{59+ {CSV, "CSV", "Comma-separated values", []string{".csv"}, false, importCSV, exportCSV},60+ {TSV, "TSV", "Tab-separated values", []string{".tsv", ".tab"}, false, importTSV, exportTSV},61+ {XLSX, "XLSX", "Excel workbook", []string{".xlsx", ".xlsm"}, true, importXLSX, exportXLSX},62+ {SQLite, "SQLite", "SQLite database", []string{".sqlite", ".sqlite3", ".db"}, false, importSQLite, exportSQLite},63+ {Parquet, "Parquet", "Parquet file", []string{".parquet"}, false, importParquet, nil},64+ {WK1, "WK1", "Lotus 1-2-3 worksheet", []string{".wk1", ".wks"}, false, importWK1, nil},65+}66+67+// format returns the kind's format, or nil for an unknown kind.68+func (k Kind) format() *fileFormat {69+ if k < 1 || int(k) > len(kinds) {70+ return nil71+ }72+ return &kinds[k-1]4773 }4874 4975 // Kinds lists every format, in menu order.@@ -57,31 +83,32 @@ func Kinds() []Kind {
5783 5884 // String is the format's short name, e.g. "XLSX".5985 func (k Kind) String() string {60- if k < 1 || int(k) > len(kinds) {61- return "unknown"86+ if f := k.format(); f != nil {87+ return f.name6288 }63- return kinds[k-1].name89+ return "unknown"6490 }6591 6692 // Label says what a file of this kind is, e.g. "Excel workbook".6793 func (k Kind) Label() string {68- if k < 1 || int(k) > len(kinds) {69- return ""94+ if f := k.format(); f != nil {95+ return f.label7096 }71- return kinds[k-1].label97+ return ""7298 }7399 74100 // Ext is the extension written for this kind, e.g. ".xlsx".75101 func (k Kind) Ext() string {76- if k < 1 || int(k) > len(kinds) {77- return ""102+ if f := k.format(); f != nil {103+ return f.exts[0]78104 }79- return kinds[k-1].exts[0]105+ return ""80106 }81107 82108 // CanExport reports whether sheets can be written in this format.83109 func (k Kind) CanExport() bool {84- return k >= 1 && int(k) <= len(kinds) && kinds[k-1].export110+ f := k.format()111+ return f != nil && f.write != nil85112 }86113 87114 // KindOf recognizes a file's format by its extension.@@ -193,27 +220,13 @@ func Import(ctx context.Context, name string, opt Options) (*Result, error) {
193220 }194221 195222 func importKind(ctx context.Context, name string, k Kind, opt Options) (*Result, error) {196- var (197- r *Result198- err error199- )200- switch k {201- case CSV, TSV:202- r, err = importDelimited(ctx, name, k, opt.Progress)203- case XLSX:204- r, err = importXLSX(ctx, name, opt.Progress)205- case SQLite:206- r, err = importSQLite(ctx, name, opt)207- case Parquet:208- r, err = importParquet(ctx, name, opt.Progress)209- case WK1:210- r, err = importWK1(ctx, name, opt.Progress)211- }223+ f := k.format()224+ r, err := f.read(ctx, name, opt)212225 if err != nil {213226 return nil, err214227 }215228 // A file of one table becomes one sheet named after it, as in Sheets.216- if book := r.Sheet.Book(); k != XLSX && book.Len() == 1 {229+ if book := r.Sheet.Book(); !f.book && book.Len() == 1 {217230 base := strings.TrimSuffix(filepath.Base(name), filepath.Ext(name))218231 if opt.Table != "" {219232 base = opt.Table@@ -255,15 +268,10 @@ func Export(ctx context.Context, name string, k Kind, snap *Snapshot, opt Export
255268 }256269 257270 func exportKind(ctx context.Context, name string, k Kind, snap *Snapshot, opt ExportOptions) (*ExportResult, error) {258- switch k {259- case CSV, TSV:260- return exportDelimited(name, k, snap)261- case XLSX:262- return exportXLSX(name, snap)263- case SQLite:264- return exportSQLite(ctx, name, snap, opt.Table)271+ if !k.CanExport() {272+ return nil, fmt.Errorf("can't export %s files", k)265273 }266- return nil, fmt.Errorf("can't export %s files", k)274+ return k.format().write(ctx, name, snap, opt)267275 }268276 269277 // fileSize is the size of the file name, for telemetry; -1 if unknown.@@ -26,7 +26,8 @@ type parquetColumn struct {
2626 // sheet with a header row. Repeated values in a row are joined with2727 // commas. Rows past the sheet's last are counted from the file's2828 // metadata, not read.29-func importParquet(ctx context.Context, name string, prog *Progress) (*Result, error) {29+func importParquet(ctx context.Context, name string, opt Options) (*Result, error) {30+ prog := opt.Progress3031 f, err := os.Open(name)3132 if err != nil {3233 return nil, err@@ -278,7 +278,8 @@ type sqlColumn struct {
278278 // exportSQLite writes the snapshot as a table, its first row naming the279279 // columns. An existing table of that name is replaced; the rest of the280280 // database is left alone.281-func exportSQLite(ctx context.Context, name string, snap *Snapshot, table string) (*ExportResult, error) {281+func exportSQLite(ctx context.Context, name string, snap *Snapshot, opt ExportOptions) (*ExportResult, error) {282+ table := opt.Table282283 if table == "" {283284 table = TableName(snap.Name)284285 }@@ -42,7 +42,8 @@ const (
4242 wk1File4343 )4444 45-func importWK1(ctx context.Context, name string, prog *Progress) (*Result, error) {45+func importWK1(ctx context.Context, name string, opt Options) (*Result, error) {46+ prog := opt.Progress4647 f, err := os.Open(name)4748 if err != nil {4849 return nil, err@@ -21,7 +21,8 @@ const (
2121 2222 // importXLSX reads every sheet of a workbook, with its named ranges, and2323 // returns the sheet that was active in Excel.24-func importXLSX(ctx context.Context, name string, prog *Progress) (*Result, error) {24+func importXLSX(ctx context.Context, name string, opt Options) (*Result, error) {25+ prog := opt.Progress2526 x, err := excelize.OpenFile(name)2627 if err != nil {2728 return nil, err@@ -221,7 +222,7 @@ func importXLSXCell(x *excelize.File, b *builder, ws, cell string, a sheet.Addr,
221222 // snapshot), each with values, formulas in Excel's syntax with their222223 // results cached, number formats, text styles, alignment and column223224 // widths, and the named ranges.224-func exportXLSX(name string, snap *Snapshot) (*ExportResult, error) {225+func exportXLSX(_ context.Context, name string, snap *Snapshot, _ ExportOptions) (*ExportResult, error) {225226 x := excelize.NewFile()226227 defer x.Close()227228 sheets := snap.Sheets