Files
dingtalk-workspace-cli/internal/helpers/sheet_table.go
T
玉澜 125f0c8fe0 Merge remote-tracking branch 'upstream/main' into feat/schema-on-main
# Conflicts:
#	test/scripts/package_script_test.go
2026-07-13 21:09:27 +08:00

158 lines
5.1 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package helpers
import (
"encoding/json"
"fmt"
"io"
"os"
"strings"
"github.com/spf13/cobra"
)
func newTableCmds() []*cobra.Command {
tableGetCmd := &cobra.Command{
Use: "table-get",
Aliases: []string{"table-read"},
Short: "读取结构化 table 数据",
Long: `读取结构化 table 数据。
返回列名、行数据、pandas-style dtypes 和表格 number formats。`,
Example: ` dws sheet table-get --node NODE_ID
dws sheet table-get --node NODE_ID --sheet-id SHEET_ID --range A1:D20
dws sheet table-get --node NODE_ID --sheet-id Sheet1 --no-header`,
RunE: func(cmd *cobra.Command, args []string) error {
nodeID, err := mustFlagOrFallback(cmd, "node", "file-id", "node-id", "doc-id")
if err != nil {
return err
}
toolArgs := map[string]any{
"nodeId": nodeID,
}
if v, _ := cmd.Flags().GetString("sheet-id"); v != "" {
toolArgs["sheetId"] = v
}
if v, _ := cmd.Flags().GetString("range"); v != "" {
toolArgs["range"] = v
}
if cmd.Flags().Changed("no-header") {
v, _ := cmd.Flags().GetBool("no-header")
toolArgs["noHeader"] = v
}
return callMCPTool("table_get", toolArgs)
},
}
tableGetCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)")
tableGetCmd.Flags().String("sheet-id", "", "工作表 ID 或名称")
tableGetCmd.Flags().String("range", "", "读取范围,A1 表示法;可带 sheet 前缀,如 Sheet1!A1:D10")
tableGetCmd.Flags().Bool("no-header", false, "首行不作为表头,自动生成 col1/col2/...")
tablePutCmd := &cobra.Command{
Use: "table-put",
Aliases: []string{"table-write"},
Short: "写入结构化 table 数据",
Long: `写入结构化 table 数据。
支持一次写入一个或多个 sheet;目标 sheet 存在时写入,传 name 且不存在时会创建 sheet。
--sheets 支持三种输入:
JSON 数组 [{"name":"Sheet1","columns":["name"],"data":[["Alice"]]}]
JSON 对象 {"sheets":[{"name":"Sheet1","columns":["name"],"data":[["Alice"]]}]}
单个 sheet spec JSON 对象 {"name":"Sheet1","columns":["name"],"data":[["Alice"]]}
--sheets 可传 @filepath 从文件读取,或传 - 从 stdin 读取。`,
Example: ` dws sheet table-put --node NODE_ID \
--sheets '[{"name":"Sheet1","columns":["name","score"],"data":[["Alice",95]],"dtypes":{"score":"float64"}}]'
dws sheet table-put --node NODE_ID --sheets @table.json
cat table.json | dws sheet table-put --node NODE_ID --sheets -`,
RunE: func(cmd *cobra.Command, args []string) error {
nodeID, err := mustFlagOrFallback(cmd, "node", "file-id", "node-id", "doc-id")
if err != nil {
return err
}
if err := validateRequiredFlags(cmd, "sheets"); err != nil {
return err
}
raw, err := readTableJSONFlag(cmd, "sheets")
if err != nil {
return err
}
sheets, err := parseTablePutSheets(raw)
if err != nil {
return err
}
return callMCPTool("table_put", map[string]any{
"nodeId": nodeID,
"sheets": sheets,
})
},
}
tablePutCmd.Flags().String("node", "", "表格文档 ID 或 URL (必填)")
tablePutCmd.Flags().String("sheets", "", "sheet table JSON、@文件路径 或 - 表示 stdin (必填)")
return []*cobra.Command{tableGetCmd, tablePutCmd}
}
func readTableJSONFlag(cmd *cobra.Command, flagName string) (string, error) {
value := mustGetFlag(cmd, flagName)
switch {
case value == "-":
data, err := io.ReadAll(cmd.InOrStdin())
if err != nil {
return "", fmt.Errorf("读取 stdin 失败: %w", err)
}
return string(data), nil
case strings.HasPrefix(value, "@"):
data, err := os.ReadFile(strings.TrimPrefix(value, "@"))
if err != nil {
return "", fmt.Errorf("读取 JSON 文件失败: %w", err)
}
return string(data), nil
default:
return value, nil
}
}
func parseTablePutSheets(raw string) ([]any, error) {
var payload any
if err := json.Unmarshal([]byte(raw), &payload); err != nil {
return nil, fmt.Errorf("--sheets JSON 解析失败: %w", err)
}
switch value := payload.(type) {
case []any:
if len(value) == 0 {
return nil, fmt.Errorf("--sheets must contain at least one sheet spec")
}
return value, nil
case map[string]any:
if sheets, ok := value["sheets"]; ok {
items, ok := sheets.([]any)
if !ok {
return nil, fmt.Errorf("--sheets.sheets must be a JSON array")
}
if len(items) == 0 {
return nil, fmt.Errorf("--sheets.sheets must contain at least one sheet spec")
}
return items, nil
}
return []any{value}, nil
default:
return nil, fmt.Errorf("--sheets must be a JSON array, an object with sheets, or a single sheet spec object")
}
}