Skip to content

Commit

Permalink
Merge pull request #417 from rokostik/spreadsheet-to-xlsx
Browse files Browse the repository at this point in the history
Implement saving/loading spreadsheets to/from xlsx
  • Loading branch information
refaktor authored Nov 24, 2024
2 parents 5b6ed0a + e0ce889 commit 5b9b0f5
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 1 deletion.
12 changes: 12 additions & 0 deletions evaldo/builtins_os.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,18 @@ var Builtins_os = map[string]*env.Builtin{
},
},

"mktmp": {
Argsn: 0,
Doc: "Creates a temporary directory.",
Fn: func(ps *env.ProgramState, arg0 env.Object, arg1 env.Object, arg2 env.Object, arg3 env.Object, arg4 env.Object) env.Object {
dir, err := os.MkdirTemp("", "rye-tmp-")
if err != nil {
return MakeBuiltinError(ps, "Error creating temporary directory: "+err.Error(), "mktmp")
}
return *env.NewUri1(ps.Idx, "file://"+dir)
},
},

"mv": {
Argsn: 2,
Doc: "Creates a directory.",
Expand Down
158 changes: 158 additions & 0 deletions evaldo/builtins_spreadsheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/refaktor/rye/env"
"github.com/refaktor/rye/util"
"github.com/xuri/excelize/v2"
)

var Builtins_spreadsheet = map[string]*env.Builtin{
Expand Down Expand Up @@ -439,6 +440,15 @@ var Builtins_spreadsheet = map[string]*env.Builtin{
},
},

// Tests:
// equal {
// cc os
// f: mktmp + "/test.csv"
// spr1: spreadsheet { "a" "b" "c" } { 1 1.1 "a" 2 2.2 "b" 3 3.3 "c" }
// spr1 .save\csv f
// spr2: load\csv f |autotype 1.0
// spr1 = spr2
// } true
// Args:
// * file-uri - location of csv file to load
// Tags: #spreadsheet #loading #csv
Expand Down Expand Up @@ -487,6 +497,16 @@ var Builtins_spreadsheet = map[string]*env.Builtin{
}
},
},

// Tests:
// equal {
// cc os
// f:: mktmp + "/test.csv"
// spr1:: spreadsheet { "a" "b" "c" } { 1 1.1 "a" 2 2.2 "b" 3 3.3 "c" }
// spr1 .save\csv f
// spr2:: load\csv f |autotype 1.0
// spr1 = spr2
// } true
// Args:
// * sheet - the sheet to save
// * file-url - where to save the sheet as a .csv file
Expand Down Expand Up @@ -556,6 +576,144 @@ var Builtins_spreadsheet = map[string]*env.Builtin{
},
},

// Tests:
// equal {
// cc os
// f:: mktmp + "/test.xlsx"
// spr1:: spreadsheet { "a" "b" "c" } { 1 1.1 "a" 2 2.2 "b" 3 3.3 "c" }
// spr1 .save\xlsx f
// spr2:: load\xlsx f |autotype 1.0
// spr1 = spr2
// } true
// Args:
// * file-uri - location of xlsx file to load
// Tags: #spreadsheet #loading #xlsx
"load\\xlsx": {
Argsn: 1,
Doc: "Loads the first sheet in an .xlsx file to a Spreadsheet.",
Fn: func(ps *env.ProgramState, arg0 env.Object, arg1 env.Object, arg2 env.Object, arg3 env.Object, arg4 env.Object) env.Object {
switch file := arg0.(type) {
case env.Uri:
f, err := excelize.OpenFile(file.GetPath())
if err != nil {
return MakeBuiltinError(ps, fmt.Sprintf("Unable to open file: %s", err), "load\\xlsx")
}
defer f.Close()

sheetMap := f.GetSheetMap()
if len(sheetMap) == 0 {
return MakeBuiltinError(ps, "No sheets found in file", "load\\xlsx")
}
// sheets map index is 1-based
sheetName := sheetMap[1]
rows, err := f.Rows(sheetName)
if err != nil {
return MakeBuiltinError(ps, fmt.Sprintf("Unable to get rows from sheet: %s", err), "load\\xlsx")
}
rows.Next()
header, err := rows.Columns()
if err != nil {
return MakeBuiltinError(ps, fmt.Sprintf("Unable to get columns from sheet: %s", err), "load\\xlsx")
}
if len(header) == 0 {
return MakeBuiltinError(ps, "Header row is empty", "load\\xlsx")
}
spr := env.NewSpreadsheet(header)
for rows.Next() {
row, err := rows.Columns()
if err != nil {
return MakeBuiltinError(ps, fmt.Sprintf("Unable to get row: %s", err), "load\\xlsx")
}
anyRow := make([]any, len(row))
for i, v := range row {
anyRow[i] = *env.NewString(v)
}
// fill in any missing columns with empty strings
for i := len(row); i < len(spr.Cols); i++ {
anyRow[i] = *env.NewString("")
}
spr.AddRow(*env.NewSpreadsheetRow(anyRow, spr))
}
return *spr
default:
return MakeArgError(ps, 1, []env.Type{env.UriType}, "load\\xlsx")
}
},
},

// Tests:
// equal {
// cc os
// f:: mktmp + "/test.xlsx"
// spr1:: spreadsheet { "a" "b" "c" } { 1 1.1 "a" 2 2.2 "b" 3 3.3 "c" }
// spr1 .save\xlsx f
// spr2:: load\xlsx f |autotype 1.0
// spr1 = spr2
// } true
// Args:
// * spreadsheet - the spreadsheet to save
// * file-url - where to save the spreadsheet as a .xlsx file
// Tags: #spreadsheet #saving #xlsx
"save\\xlsx": {
Argsn: 2,
Doc: "Saves a Spreadsheet to a .xlsx file.",
Fn: func(ps *env.ProgramState, arg0 env.Object, arg1 env.Object, arg2 env.Object, arg3 env.Object, arg4 env.Object) env.Object {
switch spr := arg0.(type) {
case env.Spreadsheet:
switch file := arg1.(type) {
case env.Uri:
sheetName := "Sheet1"
f := excelize.NewFile()
index, err := f.NewSheet(sheetName)
if err != nil {
return MakeBuiltinError(ps, fmt.Sprintf("Unable to create new sheet: %s", err), "save\\xlsx")
}
err = f.SetSheetRow(sheetName, "A1", &spr.Cols)
if err != nil {
return MakeBuiltinError(ps, fmt.Sprintf("Unable to set header row: %s", err), "save\\xlsx")
}
for i, row := range spr.Rows {
// 1-based and skip header row
rowIndex := i + 2
vals := make([]any, len(row.Values))
for j, v := range row.Values {
switch val := v.(type) {
case env.String:
vals[j] = val.Value
case string:
vals[j] = val
case env.Integer:
vals[j] = val.Value
case int64:
vals[j] = val
case env.Decimal:
vals[j] = val.Value
case float64:
vals[j] = val
default:
return MakeBuiltinError(ps, fmt.Sprintf("Unable to save spreadsheet: unsupported type %T", val), "save\\xlsx")
}
}
err = f.SetSheetRow(sheetName, fmt.Sprintf("A%d", rowIndex), &vals)
if err != nil {
return MakeBuiltinError(ps, fmt.Sprintf("Unable to set row %d: %s", rowIndex, err), "save\\xlsx")
}
}
f.SetActiveSheet(index)
err = f.SaveAs(file.GetPath())
if err != nil {
return MakeBuiltinError(ps, fmt.Sprintf("Unable to save spreadsheet: %s", err), "save\\xlsx")
}
return spr
default:
return MakeArgError(ps, 1, []env.Type{env.UriType}, "save\\xlsx")
}
default:
return MakeArgError(ps, 1, []env.Type{env.SpreadsheetType}, "save\\xlsx")
}
},
},

// Example: filtering for rows with the name "Enno"
// sheet: spreadsheet { "name" } { "Enno" "Enya" "Enid" "Bob" "Bill" }
// sheet .where-equal 'name "Enno"
Expand Down
2 changes: 1 addition & 1 deletion evaldo/builtins_spreadsheet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func BenchmarkAutotype(b *testing.B) {
RegisterBuiltins(es)
res := EvalBlock(es)
if res != nil && res.ErrorFlag {
fmt.Println("error")
fmt.Println(res.Res.Print(*es.Idx))
}
}
}
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,21 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.4 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/tklauser/go-sysconf v0.3.13 // indirect
github.com/tklauser/numcpus v0.7.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect
github.com/xuri/excelize/v2 v2.9.0 // indirect
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect
github.com/yhirose/go-peg v0.0.0-20210804202551-de25d6753cf1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
Expand Down
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/mrz1836/postmark v1.6.5 h1:FSlysQmx9n4NnU4IsvZ0nN+rylNqPHvqYcHtuSk9yi8=
github.com/mrz1836/postmark v1.6.5/go.mod h1:6z5MxAH00Kj44owtQaryv9Pbqp5OKT3wWcRSydB0p0A=
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
Expand All @@ -177,6 +179,11 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/refaktor/go-peg v0.0.0-20220116201714-31e3dfa8dc7d h1:FXrWUGgPRzhaZIBho8zNLSrMp0VpP8E9+wbRRnJYlbE=
github.com/refaktor/go-peg v0.0.0-20220116201714-31e3dfa8dc7d/go.mod h1:iIkrsFobLIWX8kQ6Oqj4cl4nwdMSE92DWpWwk9YlG9s=
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
Expand Down Expand Up @@ -207,6 +214,12 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY=
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE=
github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE=
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A=
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
github.com/yhirose/go-peg v0.0.0-20210804202551-de25d6753cf1 h1:7iTmQ0lZwTtfm4XMgP5ezzWMDCjo7GTS0ZgCj6jpVzM=
github.com/yhirose/go-peg v0.0.0-20210804202551-de25d6753cf1/go.mod h1:q2QWLflHsZxT6ixYcXveTYicEvxGh5Uv6CnI7f7BfjQ=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
Expand Down
74 changes: 74 additions & 0 deletions info/spreadsheet.info.rye
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,80 @@ section "base" "base text" {
} 2
}

group "load\\csv"
""
{
arg "* file-uri - location of csv file to load"
arg "Tags: #spreadsheet #loading #csv"
}

{
equal {
cc os
f: mktmp + "/test.csv"
spr1: spreadsheet { "a" "b" "c" } { 1 1.1 "a" 2 2.2 "b" 3 3.3 "c" }
spr1 .save\csv f
spr2: load\csv f |autotype 1.0
spr1 = spr2
} true
}

group "save\\csv"
""
{
arg "* sheet - the sheet to save"
arg "* file-url - where to save the sheet as a .csv file"
arg "Tags: #spreadsheet #saving #csv"
}

{
equal {
cc os
f:: mktmp + "/test.csv"
spr1:: spreadsheet { "a" "b" "c" } { 1 1.1 "a" 2 2.2 "b" 3 3.3 "c" }
spr1 .save\csv f
spr2:: load\csv f |autotype 1.0
spr1 = spr2
} true
}

group "load\\xlsx"
""
{
arg "* file-uri - location of xlsx file to load"
arg "Tags: #spreadsheet #loading #xlsx"
}

{
equal {
cc os
f:: mktmp + "/test.xlsx"
spr1:: spreadsheet { "a" "b" "c" } { 1 1.1 "a" 2 2.2 "b" 3 3.3 "c" }
spr1 .save\xlsx f
spr2:: load\xlsx f |autotype 1.0
spr1 = spr2
} true
}

group "save\\xlsx"
""
{
arg "* spreadsheet - the spreadsheet to save"
arg "* file-url - where to save the spreadsheet as a .xlsx file"
arg "Tags: #spreadsheet #saving #xlsx"
}

{
equal {
cc os
f:: mktmp + "/test.xlsx"
spr1:: spreadsheet { "a" "b" "c" } { 1 1.1 "a" 2 2.2 "b" 3 3.3 "c" }
spr1 .save\xlsx f
spr2:: load\xlsx f |autotype 1.0
spr1 = spr2
} true
}

group "col-sum"
""
{
Expand Down

0 comments on commit 5b9b0f5

Please sign in to comment.