mirror of
https://github.com/larksuite/cli.git
synced 2026-07-08 10:08:02 +08:00
Merge pull request #1152 from zhengzhijiej-tech/fix/eval-issues
docs(sheets): sync +pivot-create summarize_by lowercase enum values from spec
This commit is contained in:
@@ -214,8 +214,12 @@ func TestBatchOp_BodyMatchesStandalone(t *testing.T) {
|
||||
{
|
||||
shortcut: "+pivot-create",
|
||||
sc: PivotCreate,
|
||||
args: []string{"--sheet-id", "sh1", "--properties", `{"rows":[]}`, "--source", "Sheet1!A1:D100"},
|
||||
subInput: `{"sheet-id":"sh1","properties":{"rows":[]},"source":"Sheet1!A1:D100"}`,
|
||||
// +pivot-create renamed --sheet-id / --sheet-name → --target-sheet-id /
|
||||
// --target-sheet-name to flag the placement-sheet semantics (the data
|
||||
// source is in --source). Both standalone args and the +batch-update
|
||||
// sub-op input must use the new names.
|
||||
args: []string{"--target-sheet-id", "sh1", "--properties", `{"rows":[]}`, "--source", "Sheet1!A1:D100"},
|
||||
subInput: `{"target-sheet-id":"sh1","properties":{"rows":[]},"source":"Sheet1!A1:D100"}`,
|
||||
},
|
||||
{
|
||||
shortcut: "+cond-format-create",
|
||||
@@ -292,10 +296,13 @@ func TestBatchOp_BodyMatchesStandalone(t *testing.T) {
|
||||
t.Fatalf("bad subInput JSON: %v", err)
|
||||
}
|
||||
fv := newMapFlagViewForCommand(tc.shortcut, subInput)
|
||||
sid := subInput["sheet-id"]
|
||||
sname := subInput["sheet-name"]
|
||||
sidStr, _ := sid.(string)
|
||||
snameStr, _ := sname.(string)
|
||||
// Match what translateBatchOp does — read the sheet selector
|
||||
// via the shortcut-specific flag names so +pivot-create
|
||||
// (target-sheet-id / target-sheet-name) and the rest
|
||||
// (sheet-id / sheet-name) both resolve correctly.
|
||||
sidFlag, snameFlag := sheetSelectorFlagsForSubOp(tc.shortcut)
|
||||
sidStr, _ := subInput[sidFlag].(string)
|
||||
snameStr, _ := subInput[snameFlag].(string)
|
||||
batchBody, err := mapping.translate(fv, testToken, sidStr, snameStr)
|
||||
if err != nil {
|
||||
t.Fatalf("batch translate failed: %v", err)
|
||||
|
||||
@@ -43,6 +43,21 @@ type batchOpMapping struct {
|
||||
translate batchTranslateFn
|
||||
}
|
||||
|
||||
// sheetSelectorFlagsForSubOp returns the (id, name) flag names a +batch-update
|
||||
// sub-op uses to express its placement / context sheet. Defaults are
|
||||
// `sheet-id` / `sheet-name`; +pivot-create deviates because its create
|
||||
// shortcut renamed the placement selector to `target-sheet-id` /
|
||||
// `target-sheet-name` (the data-source sheet is encoded in --source as
|
||||
// `'SheetName'!Range`, not in a sheet selector flag). Update / delete on
|
||||
// pivot still use the default names — only the create create-side
|
||||
// shortcut was renamed.
|
||||
func sheetSelectorFlagsForSubOp(shortcut string) (string, string) {
|
||||
if shortcut == "+pivot-create" {
|
||||
return "target-sheet-id", "target-sheet-name"
|
||||
}
|
||||
return "sheet-id", "sheet-name"
|
||||
}
|
||||
|
||||
// objCreateTranslate / objUpdateTranslate / objDeleteTranslate bind an object
|
||||
// CRUD spec to the shared object_crud builders.
|
||||
func objCreateTranslate(spec objectCRUDSpec) batchTranslateFn {
|
||||
@@ -278,8 +293,9 @@ func translateBatchOp(raw interface{}, token string, index int) (map[string]inte
|
||||
}
|
||||
}
|
||||
fv := newMapFlagViewForCommand(sc, input)
|
||||
sheetID := strings.TrimSpace(fv.Str("sheet-id"))
|
||||
sheetName := strings.TrimSpace(fv.Str("sheet-name"))
|
||||
sheetIDFlag, sheetNameFlag := sheetSelectorFlagsForSubOp(sc)
|
||||
sheetID := strings.TrimSpace(fv.Str(sheetIDFlag))
|
||||
sheetName := strings.TrimSpace(fv.Str(sheetNameFlag))
|
||||
body, err := mapping.translate(fv, token, sheetID, sheetName)
|
||||
if err != nil {
|
||||
return nil, common.FlagErrorf("operations[%d] (%s): %v", index, sc, err)
|
||||
|
||||
@@ -1799,7 +1799,7 @@
|
||||
"kind": "own",
|
||||
"type": "string",
|
||||
"required": "xor",
|
||||
"desc": "Source range for listFromRange dropdown (A1 + sheet prefix, e.g. `Sheet1!T1:T3`); maps to server `data_validation.range` and auto-sets `data_validation.type='listFromRange'`. XOR with `--options`: pass `--options` for an inline list (type=list), pass this for a range reference (type=listFromRange). `--colors` length rule unchanged (≤ source range cell count); `--highlight` / `--multiple` behave the same. When `--highlight` is on and the source covers more than 2000 cells, the server flags the dropdown as option-error (highlight + large source is an unsupported combo); CLI emits a stderr warning. Pass `--highlight=false` to suppress."
|
||||
"desc": "Source range for listFromRange dropdown (A1 + sheet prefix, e.g. `'Sheet1'!T1:T3`); maps to server `data_validation.range` and auto-sets `data_validation.type='listFromRange'`. XOR with `--options`: pass `--options` for an inline list (type=list), pass this for a range reference (type=listFromRange). `--colors` length rule unchanged (≤ source range cell count); `--highlight` / `--multiple` behave the same. When `--highlight` is on and the source covers more than 2000 cells, the server flags the dropdown as option-error (highlight + large source is an unsupported combo); CLI emits a stderr warning. Pass `--highlight=false` to suppress."
|
||||
},
|
||||
{
|
||||
"name": "dry-run",
|
||||
@@ -2527,7 +2527,7 @@
|
||||
"kind": "own",
|
||||
"type": "string",
|
||||
"required": "required",
|
||||
"desc": "Target ranges as a JSON array (e.g. `[\"Sheet1!A1:B2\",\"Sheet2!D1:D10\"]`); each item must include a sheet prefix; the prefix must be the sheet display name (e.g. `Sheet1`), not the sheet reference_id; ranges may target different sheets; the same style is applied to every range",
|
||||
"desc": "Target ranges as a JSON array (e.g. `[\"'Sheet1'!A1:B2\",\"'Sheet2'!D1:D10\"]`); each item must include a sheet prefix; the prefix must be the sheet display name (e.g. `Sheet1`), not the sheet reference_id; ranges may target different sheets; the same style is applied to every range",
|
||||
"input": [
|
||||
"file",
|
||||
"stdin"
|
||||
@@ -2673,7 +2673,7 @@
|
||||
"kind": "own",
|
||||
"type": "string",
|
||||
"required": "required",
|
||||
"desc": "Target ranges as a JSON array (e.g. `[\"Sheet1!A2:A100\",\"Sheet1!C2:C100\"]`); each item must include a sheet prefix; the prefix must be the sheet display name (e.g. `Sheet1`), not the sheet reference_id",
|
||||
"desc": "Target ranges as a JSON array (e.g. `[\"'Sheet1'!A2:A100\",\"'Sheet1'!C2:C100\"]`); each item must include a sheet prefix; the prefix must be the sheet display name (e.g. `Sheet1`), not the sheet reference_id",
|
||||
"input": [
|
||||
"file",
|
||||
"stdin"
|
||||
@@ -2720,7 +2720,7 @@
|
||||
"kind": "own",
|
||||
"type": "string",
|
||||
"required": "xor",
|
||||
"desc": "Source range for listFromRange dropdown (A1 + sheet prefix, e.g. `Sheet1!T1:T3`); maps to server `data_validation.range` and auto-sets `data_validation.type='listFromRange'`. XOR with `--options`: pass `--options` for an inline list (type=list), pass this for a range reference (type=listFromRange). `--colors` length rule unchanged (≤ source range cell count); `--highlight` / `--multiple` behave the same. When `--highlight` is on and the source covers more than 2000 cells, the server flags the dropdown as option-error (highlight + large source is an unsupported combo); CLI emits a stderr warning. Pass `--highlight=false` to suppress."
|
||||
"desc": "Source range for listFromRange dropdown (A1 + sheet prefix, e.g. `'Sheet1'!T1:T3`); maps to server `data_validation.range` and auto-sets `data_validation.type='listFromRange'`. XOR with `--options`: pass `--options` for an inline list (type=list), pass this for a range reference (type=listFromRange). `--colors` length rule unchanged (≤ source range cell count); `--highlight` / `--multiple` behave the same. When `--highlight` is on and the source covers more than 2000 cells, the server flags the dropdown as option-error (highlight + large source is an unsupported combo); CLI emits a stderr warning. Pass `--highlight=false` to suppress."
|
||||
},
|
||||
{
|
||||
"name": "dry-run",
|
||||
@@ -2753,7 +2753,7 @@
|
||||
"kind": "own",
|
||||
"type": "string",
|
||||
"required": "required",
|
||||
"desc": "Target ranges as a JSON array (up to 100 items, e.g. `[\"Sheet1!E2:E6\"]`); each item must include a sheet prefix; the prefix must be the sheet display name (e.g. `Sheet1`), not the sheet reference_id",
|
||||
"desc": "Target ranges as a JSON array (up to 100 items, e.g. `[\"'Sheet1'!E2:E6\"]`); each item must include a sheet prefix; the prefix must be the sheet display name (e.g. `Sheet1`), not the sheet reference_id",
|
||||
"input": [
|
||||
"file",
|
||||
"stdin"
|
||||
@@ -2797,7 +2797,7 @@
|
||||
"kind": "own",
|
||||
"type": "string",
|
||||
"required": "required",
|
||||
"desc": "Target ranges as a JSON array (e.g. `[\"Sheet1!A2:Z1000\",\"Sheet2!A2:Z1000\"]`); each item must include a sheet prefix; the prefix must be the sheet display name (e.g. `Sheet1`), not the sheet reference_id; ranges may target different sheets; the same scope is cleared from every range",
|
||||
"desc": "Target ranges as a JSON array (e.g. `[\"'Sheet1'!A2:Z1000\",\"'Sheet2'!A2:Z1000\"]`); each item must include a sheet prefix; the prefix must be the sheet display name (e.g. `Sheet1`), not the sheet reference_id; ranges may target different sheets; the same scope is cleared from every range",
|
||||
"input": [
|
||||
"file",
|
||||
"stdin"
|
||||
@@ -3106,20 +3106,6 @@
|
||||
"required": "xor",
|
||||
"desc": "Spreadsheet token (XOR with `--url`)"
|
||||
},
|
||||
{
|
||||
"name": "sheet-id",
|
||||
"kind": "public",
|
||||
"type": "string",
|
||||
"required": "xor",
|
||||
"desc": "Reference_id of the sub-sheet where the pivot table is located / will be created to (mutually exclusive with --sheet-name; takes priority when both given; when both omitted, a new sub-sheet is auto-created to host the pivot — recommended)"
|
||||
},
|
||||
{
|
||||
"name": "sheet-name",
|
||||
"kind": "public",
|
||||
"type": "string",
|
||||
"required": "xor",
|
||||
"desc": "Name of the sub-sheet where the pivot table is located / will be created to (mutually exclusive with --sheet-id; when both omitted, a new sub-sheet is auto-created to host the pivot — recommended)"
|
||||
},
|
||||
{
|
||||
"name": "properties",
|
||||
"kind": "own",
|
||||
@@ -3139,12 +3125,26 @@
|
||||
"desc": "Top-left cell within the target sub-sheet (A1 notation, e.g. `A1`); maps to the top-level `target_position`, default `A1` (not sent when the value is A1). It and `--range` both express placement but map to different wire fields — avoid passing conflicting values for both.",
|
||||
"default": "A1"
|
||||
},
|
||||
{
|
||||
"name": "target-sheet-id",
|
||||
"kind": "own",
|
||||
"type": "string",
|
||||
"required": "xor",
|
||||
"desc": "Reference_id of the target sub-sheet where the pivot table will be placed (mutually exclusive with `--target-sheet-name`; takes priority when both given; when both omitted, a new sub-sheet is auto-created to host the pivot — recommended). Distinct from the data-source sheet, which lives inside --source as a 'Sheet'-prefixed A1 reference like 'Sheet1'!A1:D100."
|
||||
},
|
||||
{
|
||||
"name": "target-sheet-name",
|
||||
"kind": "own",
|
||||
"type": "string",
|
||||
"required": "xor",
|
||||
"desc": "Name of the target sub-sheet where the pivot table will be placed (mutually exclusive with `--target-sheet-id`; when both omitted, a new sub-sheet is auto-created to host the pivot — recommended). Distinct from the data-source sheet, which lives inside --source as a 'Sheet'-prefixed A1 reference like 'Sheet1'!A1:D100."
|
||||
},
|
||||
{
|
||||
"name": "source",
|
||||
"kind": "own",
|
||||
"type": "string",
|
||||
"required": "required",
|
||||
"desc": "Pivot table source range (A1 notation; format `SheetName!StartCell:EndCell`, e.g. `Sheet1!A1:D100`)"
|
||||
"desc": "Pivot table source range (A1 notation; format `'SheetName'!StartCell:EndCell`, e.g. `'Sheet1'!A1:D100`)"
|
||||
},
|
||||
{
|
||||
"name": "range",
|
||||
|
||||
@@ -3634,7 +3634,7 @@
|
||||
},
|
||||
"rules": {
|
||||
"type": "array",
|
||||
"description": "列级筛选规则列表,每一项对应一个具体列的筛选条件。update 为整组覆盖(PUT 语义):传入的 rules 即更新后的完整规则集,会先清空所有现有列的旧规则再应用,未列出的列其旧规则会被清除(不与旧规则合并);传空数组 [] 即清空所有列的规则。",
|
||||
"description": "列级筛选规则列表,每一项对应一个具体列的筛选条件。update 时传空数组 [] 表示清空所有现有列的规则。",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "单列筛选规则。",
|
||||
@@ -3879,7 +3879,7 @@
|
||||
},
|
||||
"rules": {
|
||||
"type": "array",
|
||||
"description": "列级筛选规则列表,每一项对应一个具体列的筛选条件。update 为整组覆盖(PUT 语义):传入的 rules 即更新后的完整规则集,会先清空所有现有列的旧规则再应用,未列出的列其旧规则会被清除(不与旧规则合并);传空数组 [] 即清空所有列的规则。",
|
||||
"description": "列级筛选规则列表,每一项对应一个具体列的筛选条件。update 时传空数组 [] 表示清空所有现有列的规则。",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "单列筛选规则。",
|
||||
@@ -4128,7 +4128,7 @@
|
||||
},
|
||||
"rules": {
|
||||
"type": "array",
|
||||
"description": "列级筛选规则列表,每一项对应一个具体列的筛选条件。结构与 manage_filter_object.properties.rules 完全一致。update 同样为整组覆盖(PUT 语义):传入的 rules 整组替换该视图所有列的规则,未列出的列其旧规则会被清除(不与旧规则合并),传空数组 [] 即清空全部。",
|
||||
"description": "列级筛选规则列表,每一项对应一个具体列的筛选条件。结构与 manage_filter_object.properties.rules 完全一致。",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "单列筛选规则。",
|
||||
@@ -4373,7 +4373,7 @@
|
||||
},
|
||||
"rules": {
|
||||
"type": "array",
|
||||
"description": "列级筛选规则列表,每一项对应一个具体列的筛选条件。结构与 manage_filter_object.properties.rules 完全一致。update 同样为整组覆盖(PUT 语义):传入的 rules 整组替换该视图所有列的规则,未列出的列其旧规则会被清除(不与旧规则合并),传空数组 [] 即清空全部。",
|
||||
"description": "列级筛选规则列表,每一项对应一个具体列的筛选条件。结构与 manage_filter_object.properties.rules 完全一致。",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "单列筛选规则。",
|
||||
|
||||
@@ -142,18 +142,24 @@ func requireSheetSelector(sheetID, sheetName string) error {
|
||||
// Used by shortcuts whose backend tool treats sheet_id/sheet_name as the
|
||||
// placement target rather than the operation context (currently only
|
||||
// +pivot-create). Other shortcuts continue to use requireSheetSelector.
|
||||
func optionalSheetSelector(sheetID, sheetName string) error {
|
||||
//
|
||||
// idFlagName / nameFlagName parameterize the flag names quoted back in
|
||||
// the mutex / control-char errors — +pivot-create exposes the placement
|
||||
// selector as `--target-sheet-id` / `--target-sheet-name`, not the
|
||||
// generic `--sheet-id` / `--sheet-name`, and the error wording must
|
||||
// match what the user actually typed.
|
||||
func optionalSheetSelector(sheetID, sheetName, idFlagName, nameFlagName string) error {
|
||||
sheetID = strings.TrimSpace(sheetID)
|
||||
sheetName = strings.TrimSpace(sheetName)
|
||||
if sheetID != "" && sheetName != "" {
|
||||
return common.FlagErrorf("--sheet-id and --sheet-name are mutually exclusive")
|
||||
return common.FlagErrorf("--%s and --%s are mutually exclusive", idFlagName, nameFlagName)
|
||||
}
|
||||
if sheetID != "" {
|
||||
if err := validate.RejectControlChars(sheetID, "sheet-id"); err != nil {
|
||||
if err := validate.RejectControlChars(sheetID, idFlagName); err != nil {
|
||||
return common.FlagErrorf("%v", err)
|
||||
}
|
||||
} else if sheetName != "" {
|
||||
if err := validate.RejectControlChars(sheetName, "sheet-name"); err != nil {
|
||||
if err := validate.RejectControlChars(sheetName, nameFlagName); err != nil {
|
||||
return common.FlagErrorf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,32 @@ type objectCRUDSpec struct {
|
||||
// rejected. Update/delete continue to require an explicit selector.
|
||||
// Today only pivotSpec opts in.
|
||||
allowEmptySheetSelectorOnCreate bool
|
||||
// createSheetIDFlag / createSheetNameFlag override the default
|
||||
// `sheet-id` / `sheet-name` flag names on the *create* shortcut and
|
||||
// its +batch-update sub-op. Used by pivot to expose
|
||||
// `target-sheet-id` / `target-sheet-name` — the placement target,
|
||||
// semantically distinct from the data-source sheet (which is encoded
|
||||
// in --source as `'SheetName'!Range`). Empty = default names.
|
||||
// Update/delete continue to use `sheet-id` / `sheet-name`.
|
||||
createSheetIDFlag string
|
||||
createSheetNameFlag string
|
||||
}
|
||||
|
||||
// sheetIDFlagOnCreate / sheetNameFlagOnCreate return the cobra flag name
|
||||
// used to read the placement-sheet selector on this spec's create
|
||||
// shortcut. Defaults to `sheet-id` / `sheet-name`.
|
||||
func (s objectCRUDSpec) sheetIDFlagOnCreate() string {
|
||||
if s.createSheetIDFlag != "" {
|
||||
return s.createSheetIDFlag
|
||||
}
|
||||
return "sheet-id"
|
||||
}
|
||||
|
||||
func (s objectCRUDSpec) sheetNameFlagOnCreate() string {
|
||||
if s.createSheetNameFlag != "" {
|
||||
return s.createSheetNameFlag
|
||||
}
|
||||
return "sheet-name"
|
||||
}
|
||||
|
||||
func newObjectCreateShortcut(spec objectCRUDSpec) common.Shortcut {
|
||||
@@ -80,15 +106,15 @@ func newObjectCreateShortcut(spec objectCRUDSpec) common.Shortcut {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sheetID := strings.TrimSpace(runtime.Str("sheet-id"))
|
||||
sheetName := strings.TrimSpace(runtime.Str("sheet-name"))
|
||||
sheetID := strings.TrimSpace(runtime.Str(spec.sheetIDFlagOnCreate()))
|
||||
sheetName := strings.TrimSpace(runtime.Str(spec.sheetNameFlagOnCreate()))
|
||||
_, err = objectCreateInput(runtime, token, sheetID, sheetName, spec)
|
||||
return err
|
||||
},
|
||||
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
token, _ := resolveSpreadsheetToken(runtime)
|
||||
sheetID := strings.TrimSpace(runtime.Str("sheet-id"))
|
||||
sheetName := strings.TrimSpace(runtime.Str("sheet-name"))
|
||||
sheetID := strings.TrimSpace(runtime.Str(spec.sheetIDFlagOnCreate()))
|
||||
sheetName := strings.TrimSpace(runtime.Str(spec.sheetNameFlagOnCreate()))
|
||||
input, _ := objectCreateInput(runtime, token, sheetID, sheetName, spec)
|
||||
return invokeToolDryRun(token, ToolKindWrite, spec.toolName, input)
|
||||
},
|
||||
@@ -97,8 +123,8 @@ func newObjectCreateShortcut(spec objectCRUDSpec) common.Shortcut {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sheetID := strings.TrimSpace(runtime.Str("sheet-id"))
|
||||
sheetName := strings.TrimSpace(runtime.Str("sheet-name"))
|
||||
sheetID := strings.TrimSpace(runtime.Str(spec.sheetIDFlagOnCreate()))
|
||||
sheetName := strings.TrimSpace(runtime.Str(spec.sheetNameFlagOnCreate()))
|
||||
input, err := objectCreateInput(runtime, token, sheetID, sheetName, spec)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -116,7 +142,7 @@ func newObjectCreateShortcut(spec objectCRUDSpec) common.Shortcut {
|
||||
func objectCreateInput(runtime flagView, token, sheetID, sheetName string, spec objectCRUDSpec) (map[string]interface{}, error) {
|
||||
var err error
|
||||
if spec.allowEmptySheetSelectorOnCreate {
|
||||
err = optionalSheetSelector(sheetID, sheetName)
|
||||
err = optionalSheetSelector(sheetID, sheetName, spec.sheetIDFlagOnCreate(), spec.sheetNameFlagOnCreate())
|
||||
} else {
|
||||
err = requireSheetSelector(sheetID, sheetName)
|
||||
}
|
||||
@@ -312,6 +338,8 @@ var pivotSpec = objectCRUDSpec{
|
||||
idFlag: "pivot-table-id",
|
||||
idField: "pivot_table_id",
|
||||
allowEmptySheetSelectorOnCreate: true,
|
||||
createSheetIDFlag: "target-sheet-id",
|
||||
createSheetNameFlag: "target-sheet-name",
|
||||
enhanceCreateInput: func(rt flagView, input map[string]interface{}) {
|
||||
if v := strings.TrimSpace(rt.Str("target-position")); v != "" && v != "A1" {
|
||||
input["target_position"] = v
|
||||
|
||||
@@ -52,16 +52,20 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) {
|
||||
},
|
||||
},
|
||||
// pivot — has extra create flags incl. required --source.
|
||||
// --sheet-id is the placement target (where the pivot lands);
|
||||
// pivotSpec.allowEmptySheetSelectorOnCreate lets both --sheet-id
|
||||
// and --sheet-name be omitted so the backend auto-creates a
|
||||
// sub-sheet — covered separately in the +pivot-create empty-
|
||||
// selector / mutex tests below.
|
||||
// --target-sheet-id is the placement target (where the pivot lands);
|
||||
// the placement selector is renamed from the generic --sheet-id /
|
||||
// --sheet-name to --target-sheet-id / --target-sheet-name to keep
|
||||
// it semantically distinct from the data-source sheet (which is
|
||||
// encoded inside --source as `'SheetName'!Range`).
|
||||
// pivotSpec.allowEmptySheetSelectorOnCreate lets both target
|
||||
// selectors be omitted so the backend auto-creates a sub-sheet —
|
||||
// covered separately in the +pivot-create empty-selector / mutex
|
||||
// tests below.
|
||||
{
|
||||
name: "+pivot-create with placement / source / range flags",
|
||||
sc: PivotCreate,
|
||||
args: []string{
|
||||
"--url", testURL, "--sheet-id", testSheetID,
|
||||
"--url", testURL, "--target-sheet-id", testSheetID,
|
||||
"--properties", `{"rows":[{"field":"A"}]}`,
|
||||
"--source", "Sheet1!A1:F1000",
|
||||
"--range", "F1",
|
||||
@@ -80,10 +84,10 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
// +pivot-create accepts both sheet selectors empty — backend
|
||||
// +pivot-create accepts both target selectors empty — backend
|
||||
// auto-creates a placement sub-sheet.
|
||||
{
|
||||
name: "+pivot-create empty --sheet-id / --sheet-name omits sheet from input",
|
||||
name: "+pivot-create empty --target-sheet-id / --target-sheet-name omits sheet from input",
|
||||
sc: PivotCreate,
|
||||
args: []string{
|
||||
"--url", testURL,
|
||||
@@ -349,9 +353,9 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestPivotCreate_SheetSelectorSemantics locks in the "at most one"
|
||||
// semantics for +pivot-create (and only +pivot-create): both --sheet-id
|
||||
// and --sheet-name may be omitted (backend auto-creates a placement
|
||||
// sub-sheet), but passing both is rejected.
|
||||
// semantics for +pivot-create (and only +pivot-create): both
|
||||
// --target-sheet-id and --target-sheet-name may be omitted (backend
|
||||
// auto-creates a placement sub-sheet), but passing both is rejected.
|
||||
//
|
||||
// Companion regression — TestObjectCreate_RequiresSheetSelector below —
|
||||
// confirms every other *-create still rejects empty selector.
|
||||
@@ -378,25 +382,30 @@ func TestPivotCreate_SheetSelectorSemantics(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, stderr, err := runShortcutCapturingErr(t, PivotCreate, []string{
|
||||
"--url", testURL,
|
||||
"--sheet-id", testSheetID,
|
||||
"--sheet-name", "Sheet1",
|
||||
"--target-sheet-id", testSheetID,
|
||||
"--target-sheet-name", "Sheet1",
|
||||
"--properties", `{"rows":[{"field":"A"}]}`,
|
||||
"--source", "Sheet1!A1:F1000",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatalf("expected CLI to reject both --sheet-id and --sheet-name set; stderr=%s", stderr)
|
||||
t.Fatalf("expected CLI to reject both --target-sheet-id and --target-sheet-name set; stderr=%s", stderr)
|
||||
}
|
||||
combined := stderr + err.Error()
|
||||
if !strings.Contains(combined, "mutually exclusive") {
|
||||
t.Errorf("expected error to say 'mutually exclusive'; got=%s|%v", stderr, err)
|
||||
}
|
||||
// 错误信息必须用真实的 flag 名(target-*),否则模型按消息提示去
|
||||
// 改 --sheet-id 还是错的。
|
||||
if !strings.Contains(combined, "--target-sheet-id") {
|
||||
t.Errorf("expected error to quote --target-sheet-id flag name; got=%s|%v", stderr, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("only sheet-id is accepted", func(t *testing.T) {
|
||||
t.Run("only target-sheet-id is accepted", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
body := parseDryRunBody(t, PivotCreate, []string{
|
||||
"--url", testURL,
|
||||
"--sheet-id", testSheetID,
|
||||
"--target-sheet-id", testSheetID,
|
||||
"--properties", `{"rows":[{"field":"A"}]}`,
|
||||
"--source", "Sheet1!A1:F1000",
|
||||
})
|
||||
|
||||
@@ -81,8 +81,8 @@ metadata:
|
||||
- ⚠️ **`/wiki/` 知识库链接不能直接当表格定位用**:wiki 链接背后可能是电子表格,也可能是文档 / 多维表格等其它类型,`--url` **不会**自动把 wiki token 解析成 spreadsheet token,直接传会失败。必须先把它解析成真实文档 token —— `lark-cli wiki +node-get --node-token "<wiki 链接或 token>"`,确认返回的 `obj_type` 为 `sheet` 后,取其 `obj_token` 作为 `--spreadsheet-token` 传入(解析细节见 [`../lark-wiki/SKILL.md`](../lark-wiki/SKILL.md))。
|
||||
- **例外**:`+workbook-create` 是新建一个还不存在的表格,**不接受任何 spreadsheet / sheet 定位 flag**(只有 `--title` / `--folder-token` / `--headers` / `--values`)。
|
||||
2. **sheet 定位(公共四件套 shortcut 必填)**:`--sheet-id` 与 `--sheet-name` 二选一,**必须给其中之一**。两个都不给 → 校验报错 `specify at least one of --sheet-id or --sheet-name`。
|
||||
- ⚠️ **`--range` 里的 `Sheet1!` 前缀不能替代 sheet 定位**:即使写了 `--range "Sheet1!A1:B2"`,仍**必须**额外传 `--sheet-id` 或 `--sheet-name`,否则照样报上面的错。
|
||||
- **例外**:徽章标为 `_公共:URL/token(无 sheet 定位)…_` 的 shortcut(如 `+workbook-info` / `+workbook-export` / `+batch-update` / `+dropdown-get|update|delete` / `+cells-batch-set-style` / `+cells-batch-clear` / `+sheet-create`)**不接受也不需要** sheet 定位,只给一组 spreadsheet 定位即可。
|
||||
- ⚠️ **`--range` 里的 `Sheet1!` 前缀不能替代 sheet 定位**:即使写了 `--range "'Sheet1'!A1:B2"`,仍**必须**额外传 `--sheet-id` 或 `--sheet-name`,否则照样报上面的错。
|
||||
- **例外**:徽章标为 `_公共:URL/token(无 sheet 定位)…_` 的 shortcut(如 `+workbook-info` / `+workbook-export` / `+batch-update` / `+dropdown-get|update|delete` / `+cells-batch-set-style` / `+cells-batch-clear` / `+sheet-create`)**不接受也不需要** sheet 定位,只给一组 spreadsheet 定位即可。`+pivot-create` 用 `--target-sheet-id` / `--target-sheet-name`(XOR,可都不传,落点细节见 `lark-sheets-pivot-table`)。
|
||||
|
||||
| Flag | Type | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
|
||||
@@ -51,7 +51,7 @@ _公共:URL/token(无 sheet 定位) · 系统:`--dry-run`_
|
||||
|
||||
| Flag | Type | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组,每项必须带 sheet 前缀(如 `["Sheet1!A1:B2","Sheet2!D1:D10"]`);前缀必须是 sheet 显示名(如 `Sheet1`),不接受 sheet reference_id;支持跨 sheet;所有 range 应用同一组 style |
|
||||
| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组,每项必须带 sheet 前缀(如 `["'Sheet1'!A1:B2","'Sheet2'!D1:D10"]`);前缀必须是 sheet 显示名(如 `Sheet1`),不接受 sheet reference_id;支持跨 sheet;所有 range 应用同一组 style |
|
||||
| `--background-color` | string | optional | 背景颜色(十六进制,如 `#ffffff`) |
|
||||
| `--font-color` | string | optional | 字体颜色(十六进制,如 `#000000`) |
|
||||
| `--font-size` | float64 | optional | 字体大小(px,例:10、12、14) |
|
||||
@@ -70,12 +70,12 @@ _公共:URL/token(无 sheet 定位) · 系统:`--dry-run`_
|
||||
|
||||
| Flag | Type | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组(如 `["Sheet1!A2:A100","Sheet1!C2:C100"]`),每项必须带 sheet 前缀;前缀必须是 sheet 显示名(如 `Sheet1`),不接受 sheet reference_id |
|
||||
| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组(如 `["'Sheet1'!A2:A100","'Sheet1'!C2:C100"]`),每项必须带 sheet 前缀;前缀必须是 sheet 显示名(如 `Sheet1`),不接受 sheet reference_id |
|
||||
| `--options` | string + File + Stdin(复合 JSON) | xor | 下拉选项 JSON 数组,例如 `["opt1","opt2"]`。服务端不限制选项数量,也不限制单个选项长度;含逗号的选项可以接受(写入时会自动转义)。大量选项建议改用 `--source-range`。 |
|
||||
| `--colors` | string + File + Stdin(简单 JSON) | optional | 下拉胶囊背景色,RGB hex 数组(如 `["#1FB6C1","#F006C2"]`)。长度可短不可长——超长 Validate 拦截(`--colors length (N) must not exceed dropdown source size (M)`),未指定项按内置 10 色色板循环补色。**单独传即生效**;`--highlight=false` 时被忽略。 |
|
||||
| `--multiple` | bool | optional | 启用多选 |
|
||||
| `--highlight` | bool | optional | 下拉胶囊背景色高亮开关。**不传 = 开**(按内置 10 色色板循环上色);`--highlight=false` 关闭得到纯白下拉。配色用 `--colors` 覆盖。 |
|
||||
| `--source-range` | string | xor | listFromRange 模式的下拉源 range,A1 表示法 + sheet 前缀(如 `Sheet1!T1:T3`)。映射到 server `data_validation.range`,搭配 server `data_validation.type='listFromRange'` 自动生效。跟 `--options` 二选一:传 `--options` 走 inline 列表(type=list),传本 flag 走 range 引用(type=listFromRange)。`--colors` 长度规则不变(≤ 源 range 单元格数),`--highlight` / `--multiple` 行为相同。当 `--highlight` 开启且 source 覆盖单元格数超过 2000 时,服务端会将该下拉判为 option-error(这是不支持的组合);CLI 会向 stderr 输出 warning。如需取消,传 `--highlight=false`。 |
|
||||
| `--source-range` | string | xor | listFromRange 模式的下拉源 range,A1 表示法 + sheet 前缀(如 `'Sheet1'!T1:T3`)。映射到 server `data_validation.range`,搭配 server `data_validation.type='listFromRange'` 自动生效。跟 `--options` 二选一:传 `--options` 走 inline 列表(type=list),传本 flag 走 range 引用(type=listFromRange)。`--colors` 长度规则不变(≤ 源 range 单元格数),`--highlight` / `--multiple` 行为相同。当 `--highlight` 开启且 source 覆盖单元格数超过 2000 时,服务端会将该下拉判为 option-error(这是不支持的组合);CLI 会向 stderr 输出 warning。如需取消,传 `--highlight=false`。 |
|
||||
|
||||
### `+dropdown-delete`
|
||||
|
||||
@@ -83,7 +83,7 @@ _公共:URL/token(无 sheet 定位) · 系统:`--yes`、`--dry-run`_
|
||||
|
||||
| Flag | Type | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组(最多 100 个,如 `["Sheet1!E2:E6"]`),每项必须带 sheet 前缀;前缀必须是 sheet 显示名(如 `Sheet1`),不接受 sheet reference_id |
|
||||
| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组(最多 100 个,如 `["'Sheet1'!E2:E6"]`),每项必须带 sheet 前缀;前缀必须是 sheet 显示名(如 `Sheet1`),不接受 sheet reference_id |
|
||||
|
||||
### `+cells-batch-clear`
|
||||
|
||||
@@ -91,7 +91,7 @@ _公共:URL/token(无 sheet 定位) · 系统:`--yes`、`--dry-run`_
|
||||
|
||||
| Flag | Type | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组,每项必须带 sheet 前缀(如 `["Sheet1!A2:Z1000","Sheet2!A2:Z1000"]`);前缀必须是 sheet 显示名(如 `Sheet1`),不接受 sheet reference_id;支持跨 sheet;对所有 range 执行同一 scope 的清除 |
|
||||
| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组,每项必须带 sheet 前缀(如 `["'Sheet1'!A2:Z1000","'Sheet2'!A2:Z1000"]`);前缀必须是 sheet 显示名(如 `Sheet1`),不接受 sheet reference_id;支持跨 sheet;对所有 range 执行同一 scope 的清除 |
|
||||
| `--scope` | string | optional | 清除范围 enum:`content`(默认,仅清内容)/ `formats`(仅清格式)/ `all`(清内容 + 格式)(可选值:`content` / `formats` / `all`) |
|
||||
|
||||
## Schemas
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
> **正确做法**:
|
||||
> 1. 在 `data` 下显式设置 `"headerMode": "detached"`;
|
||||
> 2. `refs` **只覆盖该子集的纯数据**,不要向上/向左多带 1 行/列,也不要把全局表头整段并进来(否则会把其它分组的数据混进图);
|
||||
> 3. **`nameRef` 必填**:给 `dim1.serie.nameRef` 写真正表头中"类别名"那一格的 A1 引用(如 `Sheet2!A1`),给每个 `dim2.series[i].nameRef` 写对应数值列的 A1 引用(如 `Sheet2!C1`、`Sheet2!D1`)。任一缺失会被校验拦下并报 `headerMode=detached requires ... nameRef`;
|
||||
> 3. **`nameRef` 必填**:给 `dim1.serie.nameRef` 写真正表头中"类别名"那一格的 A1 引用(如 `'Sheet2'!A1`,sheet 名按 A1 标准单引号包裹),给每个 `dim2.series[i].nameRef` 写对应数值列的 A1 引用(如 `'Sheet2'!C1`、`'Sheet2'!D1`)。任一缺失会被校验拦下并报 `headerMode=detached requires ... nameRef`;
|
||||
> 4. `refs[i].value` 必须是单元格或普通矩形范围(CELL / NORMAL),不接受整行/整列/开区间;`direction='column'` 时起始行必须 > 0,`direction='row'` 时起始列必须 > 0;
|
||||
> 5. `index` 仍按 `refs` 内的列/行号填,从 1 开始。
|
||||
>
|
||||
@@ -173,7 +173,7 @@ lark-cli sheets +chart-create --url "https://example.feishu.cn/sheets/shtXXX" \
|
||||
"size":{"width":600,"height":400},
|
||||
"snapshot":{
|
||||
"data":{
|
||||
"refs":[{"value":"Sheet1!A1:B10"}],
|
||||
"refs":[{"value":"'Sheet1'!A1:B10"}],
|
||||
"dim1":{"serie":{"index":1}},
|
||||
"dim2":{"series":[{"index":2}]}
|
||||
},
|
||||
@@ -203,7 +203,7 @@ lark-cli sheets +chart-create --url "..." --sheet-name "Sheet1" --properties '{
|
||||
}]
|
||||
}},
|
||||
"data":{
|
||||
"refs":[{"value":"Sheet1!A1:B11"}],
|
||||
"refs":[{"value":"'Sheet1'!A1:B11"}],
|
||||
"dim1":{"serie":{"index":1,"aggregate":true}},
|
||||
"dim2":{"series":[{"index":2,"aggregateType":"sum"}]}
|
||||
}
|
||||
@@ -225,11 +225,11 @@ lark-cli sheets +chart-create --url "..." --sheet-name "Sheet2" --properties '{
|
||||
"data":{
|
||||
"headerMode":"detached",
|
||||
"direction":"column",
|
||||
"refs":[{"value":"Sheet2!A11:D17"}],
|
||||
"dim1":{"serie":{"index":1,"nameRef":"Sheet2!A1"}},
|
||||
"refs":[{"value":"'Sheet2'!A11:D17"}],
|
||||
"dim1":{"serie":{"index":1,"nameRef":"'Sheet2'!A1"}},
|
||||
"dim2":{"series":[
|
||||
{"index":3,"nameRef":"Sheet2!C1"},
|
||||
{"index":4,"nameRef":"Sheet2!D1"}
|
||||
{"index":3,"nameRef":"'Sheet2'!C1"},
|
||||
{"index":4,"nameRef":"'Sheet2'!D1"}
|
||||
]}
|
||||
}
|
||||
}
|
||||
@@ -250,9 +250,9 @@ lark-cli sheets +chart-create --url "..." --sheet-name "Sheet2" --properties '{
|
||||
|
||||
```jsonc
|
||||
// 错误 1:refs 含全局表头但跨段 —— 多个区域被混进同一张图
|
||||
{"data":{"refs":[{"value":"Sheet!A1:E17"}], ... }} // 华东图混进华北 8 行
|
||||
{"data":{"refs":[{"value":"'Sheet'!A1:E17"}], ... }} // 华东图混进华北 8 行
|
||||
// 错误 2:inline + refs 只取数据段、不写 detached/nameRef —— 图例显示成具体数据值
|
||||
{"data":{"refs":[{"value":"Sheet!A10:E17"}],"dim1":{"serie":{"index":1}}, ... }}
|
||||
{"data":{"refs":[{"value":"'Sheet'!A10:E17"}],"dim1":{"serie":{"index":1}}, ... }}
|
||||
```
|
||||
|
||||
✅ 正确模式:3 张图各自 detached、refs 干净不重叠:
|
||||
@@ -261,15 +261,15 @@ lark-cli sheets +chart-create --url "..." --sheet-name "Sheet2" --properties '{
|
||||
// 图 1:华北
|
||||
{"data":{
|
||||
"headerMode":"detached","direction":"column",
|
||||
"refs":[{"value":"Sheet!A2:E9"}],
|
||||
"dim1":{"serie":{"index":1,"nameRef":"Sheet!A1"}},
|
||||
"refs":[{"value":"'Sheet'!A2:E9"}],
|
||||
"dim1":{"serie":{"index":1,"nameRef":"'Sheet'!A1"}},
|
||||
"dim2":{"series":[
|
||||
{"index":3,"nameRef":"Sheet!C1"},
|
||||
{"index":4,"nameRef":"Sheet!D1"}
|
||||
{"index":3,"nameRef":"'Sheet'!C1"},
|
||||
{"index":4,"nameRef":"'Sheet'!D1"}
|
||||
]}
|
||||
}}
|
||||
// 图 2:华东 —— refs 改 Sheet!A10:E17,其余同上
|
||||
// 图 3:华南 —— refs 改 Sheet!A18:E25,其余同上
|
||||
// 图 2:华东 —— refs 改 'Sheet'!A10:E17,其余同上
|
||||
// 图 3:华南 —— refs 改 'Sheet'!A18:E25,其余同上
|
||||
```
|
||||
|
||||
> `--properties` JSON 关键字段:
|
||||
|
||||
@@ -20,19 +20,19 @@
|
||||
创建透视表前先识别用户需求中的分组维度和聚合指标,**不要搞反**:
|
||||
|
||||
- **rows(行字段)** = 分组维度,即"按什么分组"。例:部门、地区、医生、产品类别
|
||||
- **values(值字段)** = 聚合指标,即"统计什么数值"。例:SUM(销售额)、COUNT(订单数)
|
||||
- **values(值字段)** = 聚合指标,即"统计什么数值"。例:销售额(聚合方式 `sum`)、订单数(聚合方式 `count`)
|
||||
- **columns(列字段)** = 交叉维度(可选),即"再按什么横向展开"。例:月份、性别
|
||||
|
||||
| 用户说 | rows | values | columns |
|
||||
|--------|------|--------|---------|
|
||||
| "按部门统计人数" | 部门 | COUNT(姓名) | — |
|
||||
| "按医生统计费用和结余" | 主管医生 | SUM(费用), SUM(结余) | — |
|
||||
| "各部门男女人数" | 部门 | COUNT(姓名) | 性别 |
|
||||
| "按部门统计人数" | 部门 | 姓名(`summarize_by: "count"`) | — |
|
||||
| "按医生统计费用和结余" | 主管医生 | 费用(`"sum"`)、结余(`"sum"`) | — |
|
||||
| "各部门男女人数" | 部门 | 姓名(`"count"`) | 性别 |
|
||||
|
||||
**常见配置错误(必须注意)**:
|
||||
- **数据源范围必须精确**:透视表的数据源范围必须包含表头行,且精确覆盖全部数据行列。范围过大(包含空行/空列)或过小(遗漏数据列)都会导致透视表结果错误
|
||||
- **行列字段选择要匹配用户意图**:用户说"按商品统计金额"→ 行字段=商品,值字段=金额(SUM)。不要把行列字段搞反
|
||||
- **聚合类型要匹配**:用户说"统计数量"→ COUNT;"统计总额"→ SUM;"统计平均"→ AVERAGE。默认不要用 COUNT 替代 SUM
|
||||
- **行列字段选择要匹配用户意图**:用户说"按商品统计金额"→ 行字段=商品,值字段=金额(`summarize_by: "sum"`)。不要把行列字段搞反
|
||||
- **聚合类型要匹配**:用户说"统计数量"→ `summarize_by: "count"`;"统计总额"→ `"sum"`;"统计平均"→ `"average"`。完整合法值:`sum` / `count` / `average` / `max` / `min` / `product` / `countNums` / `stdDev` / `stdDevp` / `var` / `varp` / `distinct` / `median`。默认不要用 `count` 替代 `sum`
|
||||
- **参数长度限制**:如果透视表配置 JSON 过长(数据源范围跨越大量行列),可能导致工具调用失败。此时应先确认数据范围的精确边界,避免传入过大的 range
|
||||
- **创建后必须验证**:调用 `+pivot-list` 确认透视表结构正确
|
||||
|
||||
@@ -57,13 +57,15 @@ _公共四件套 · 系统:`--dry-run`_
|
||||
|
||||
### `+pivot-create`
|
||||
|
||||
_公共四件套 · 系统:`--dry-run`_
|
||||
_公共:URL/token(无 sheet 定位) · 系统:`--dry-run`_
|
||||
|
||||
| Flag | Type | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `--properties` | string + File + Stdin(复合 JSON) | required | JSON:{"rows":[...],"columns":[...],"values":[...],"filters":[...],"show_row_grand_total":true,"show_col_grand_total":true}(数据源走 --source,不要再放进 properties.source) |
|
||||
| `--target-position` | string | optional | 透视表落点子表内的起始 cell(A1 格式,如 `A1`),映射到顶层 `target_position`,默认 `A1`(值为 A1 时不下发)。它与 `--range` 都表达落点但落在不同 wire 字段,避免两者同时给冲突值 |
|
||||
| `--source` | string | required | 透视表源数据区域(A1 表示法,格式 `SheetName!StartCell:EndCell`,如 `Sheet1!A1:D100`) |
|
||||
| `--target-sheet-id` | string | xor | 透视表落点目标子表的 reference_id(与 `--target-sheet-name` 互斥,优先于 --target-sheet-name;都不传时自动新建一张子表放置透视表——推荐)。与数据源 sheet 区分:数据源 sheet 写在 --source 的 A1 引用里(带 sheet 前缀,形如 `'Sheet1'!A1:D100`)。 |
|
||||
| `--target-sheet-name` | string | xor | 透视表落点目标子表的名称(与 `--target-sheet-id` 互斥;都不传时自动新建一张子表放置透视表——推荐)。与数据源 sheet 区分:数据源 sheet 写在 --source 的 A1 引用里(带 sheet 前缀,形如 `'Sheet1'!A1:D100`)。 |
|
||||
| `--source` | string | required | 透视表源数据区域(A1 表示法,格式 `'SheetName'!StartCell:EndCell`,如 `'Sheet1'!A1:D100`) |
|
||||
| `--range` | string | optional | 透视表左上角放置位置(A1 单值,如 `F1`,仅 create 生效),映射到 `properties.range`;省略时放在落点子表(默认新建子表)的左上角。它与 `--target-position` 都表达落点但落在不同 wire 字段,避免两者同时给冲突值 |
|
||||
|
||||
### `+pivot-update`
|
||||
@@ -108,7 +110,9 @@ _创建/更新的透视表属性_
|
||||
|
||||
## Examples
|
||||
|
||||
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`。其中 `--sheet-id` / `--sheet-name` 在 `+pivot-update` / `+pivot-delete` / `+pivot-list` 上是公共四件套语义(定位透视表所在 sheet,XOR 必传一个);但在 **`+pivot-create` 上是透视表的"落点"语义**——两个都不传时后端自动新建子表存放产物(强烈推荐,绝不碰源数据)。
|
||||
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`,其中 `--sheet-id` / `--sheet-name` 在 `+pivot-update` / `+pivot-delete` / `+pivot-list` 上是公共四件套语义(定位透视表所在 sheet,XOR 必传一个)。
|
||||
|
||||
**`+pivot-create` 例外**:placement 选择器用 `--target-sheet-id` / `--target-sheet-name`(XOR,两个都不传时后端自动新建子表存放产物,强烈推荐,绝不碰源数据)。数据源 sheet 写在 `--source` 的 `'SheetName'!Range` 里,不靠 sheet 选择器 flag。
|
||||
|
||||
### `+pivot-list`
|
||||
|
||||
@@ -121,26 +125,26 @@ lark-cli sheets +pivot-list --url "..." --sheet-id "$SID"
|
||||
> 数据源 `--source` 必须从表头行开始;空行 / 汇总行会被当作数据参与聚合,需提前用 `+csv-get` 确认起止边界。`--source` 和 `--range` 是独立 flag(不要再放 `--properties`);`rows` / `columns` / `values` 等数组字段走 `--properties`。
|
||||
>
|
||||
> **先理清 `+pivot-create` 上 4 个位置类入参(语义不同,别混)**:
|
||||
> - `--source`(**必填**):**源数据**区域,须自带 `Sheet!` 前缀(如 `Sheet1!A1:D100`)。源 sheet 的名字在 `--source` 字符串里,**不**通过单独 flag 传。
|
||||
> - `--sheet-id` / `--sheet-name`:**透视表的落点 sheet**(即产物放哪张子表)。两个互斥(最多传一个),都不传时后端自动新建子表存放产物(强烈推荐)。**注意:跟其它 shortcut 不同,这里 `--sheet-id` / `--sheet-name` 表达的不是"数据源所在 sheet"而是"产物落点 sheet"**。
|
||||
> - `--source`(**必填**):**源数据**区域,须自带 `Sheet!` 前缀(如 `'Sheet1'!A1:D100`,sheet 名按 A1 标准单引号包裹)。源 sheet 的名字在 `--source` 字符串里,**不**通过单独 flag 传。
|
||||
> - `--target-sheet-id` / `--target-sheet-name`:**透视表的落点 sheet**(即产物放哪张子表)。两个互斥(最多传一个),都不传时后端自动新建子表存放产物(强烈推荐)。
|
||||
> - `--target-position`(可选,A1 表示法,默认 `A1`):落点 sheet 内的起始 cell,映射到顶层 `target_position`。
|
||||
> - `--range`(可选,A1 单值,仅 create 生效):跟 `--target-position` 表达同一意图但映射到 `properties.range`,**两者不要同时给**。
|
||||
>
|
||||
> **落点 3 种策略(互斥,选其一)**:
|
||||
> 1. **默认(强烈推荐)**:`--sheet-id` / `--sheet-name` / `--target-position` / `--range` **全都不传** → 服务端**自动新建子表**存放产物,绝不碰任何已有数据。
|
||||
> 2. **放进指定的已有子表**:传 `--sheet-id <落点子表 id>`(或 `--sheet-name`),可选 `--target-position <子表内起点 cell>`。⚠️ **若落点子表就是源数据所在的 sheet**,必须配 `--target-position` 或 `--range` 指向源数据范围**之外**的位置,否则产物默认从 A1 起会盖在源数据上。
|
||||
> 3. **`--range`**:跟策略 2 等价(同样需要 `--sheet-id` / `--sheet-name` 指定落点子表,不然落到自动新建子表),只是用 `properties.range` 那条 wire 路径表达位置。同样的覆盖风险,同样需要避开源数据范围。
|
||||
> 1. **默认(强烈推荐)**:`--target-sheet-id` / `--target-sheet-name` / `--target-position` / `--range` **全都不传** → 服务端**自动新建子表**存放产物,绝不碰任何已有数据。
|
||||
> 2. **放进指定的已有子表**:传 `--target-sheet-id <落点子表 id>`(或 `--target-sheet-name`),可选 `--target-position <子表内起点 cell>`。⚠️ **若落点子表就是源数据所在的 sheet**,必须配 `--target-position` 或 `--range` 指向源数据范围**之外**的位置,否则产物默认从 A1 起会盖在源数据上。
|
||||
> 3. **`--range`**:跟策略 2 等价(同样需要 `--target-sheet-id` / `--target-sheet-name` 指定落点子表,不然落到自动新建子表),只是用 `properties.range` 那条 wire 路径表达位置。同样的覆盖风险,同样需要避开源数据范围。
|
||||
>
|
||||
> 一般用策略 1(默认新建子表)即可,零覆盖风险,无需任何 `--sheet-*` / `--range` / `--target-*` flag。
|
||||
> 一般用策略 1(默认新建子表)即可,零覆盖风险,无需任何 `--target-*` / `--range` flag。
|
||||
|
||||
```bash
|
||||
# 策略 1(强烈推荐):不传任何落点 flag → 后端自动新建子表,零覆盖风险
|
||||
lark-cli sheets +pivot-create --url "..." \
|
||||
--source "Sheet1!A1:D100" --properties @pivot.json
|
||||
--source "'Sheet1'!A1:D100" --properties @pivot.json
|
||||
|
||||
# 策略 2:落进指定的已有目标子表(注意目标 sheet ≠ 源 sheet,否则要配 --target-position 避开源数据)
|
||||
lark-cli sheets +pivot-create --url "..." \
|
||||
--source "Sheet1!A1:D100" --sheet-id "$DEST_SID" --target-position "A1" --properties @pivot.json
|
||||
--source "'Sheet1'!A1:D100" --target-sheet-id "$DEST_SID" --target-position "A1" --properties @pivot.json
|
||||
```
|
||||
|
||||
### `+pivot-update`
|
||||
@@ -155,7 +159,7 @@ lark-cli sheets +pivot-delete --url "..." --sheet-id "$SID" --pivot-table-id "$P
|
||||
|
||||
### Validate / DryRun / Execute 约束
|
||||
|
||||
- `Validate`:`--url` / `--spreadsheet-token` XOR 必填;`+pivot-{update,delete,list}` 的 `--sheet-id` / `--sheet-name` XOR 必填一个,`+pivot-create` 例外(两个都可空,触发 backend auto-create 子表);`+pivot-create` 的 `--source` 必填且必须含表头行;`--properties` 中 `rows` / `columns` / `values` 至少非空之一;`+pivot-delete` 强制 `--yes` 或 `--dry-run`。
|
||||
- `Validate`:`--url` / `--spreadsheet-token` XOR 必填;`+pivot-{update,delete,list}` 的 `--sheet-id` / `--sheet-name` XOR 必填一个;`+pivot-create` 例外(用 `--target-sheet-id` / `--target-sheet-name` 表达落点,两个都可空时触发 backend auto-create 子表,两个都给则报 mutually exclusive);`+pivot-create` 的 `--source` 必填且必须含表头行;`--properties` 中 `rows` / `columns` / `values` 至少非空之一;`+pivot-delete` 强制 `--yes` 或 `--dry-run`。
|
||||
- `DryRun`:写操作输出"将要 POST/PATCH/DELETE 的 pivot 请求模板"+ 预估输出尺寸(行数 × 列数)。
|
||||
- `Execute`:写后不自动回读;如需确认,自行调用 `+pivot-list --pivot-table-id <id>` 并用 `+csv-get` 抽样读透视产物核对输出尺寸 + 总计行位置。
|
||||
|
||||
|
||||
@@ -108,8 +108,8 @@ lark-cli sheets +sparkline-create --url "..." --sheet-id "$SID" --properties @sp
|
||||
{
|
||||
"config": { "line_width": 2 },
|
||||
"sparklines": [
|
||||
{"position": {"row": 1, "col": "F"}, "source": "Sheet1!A2:E2"},
|
||||
{"position": {"row": 2, "col": "F"}, "source": "Sheet1!A3:E3"}
|
||||
{"position": {"row": 1, "col": "F"}, "source": "'Sheet1'!A2:E2"},
|
||||
{"position": {"row": 2, "col": "F"}, "source": "'Sheet1'!A3:E3"}
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -122,8 +122,8 @@ lark-cli sheets +sparkline-create --url "..." --sheet-id "$SID" --properties @sp
|
||||
# 假设 +sparkline-list 已返回 group_id=grpA,组内 sparkline_id=sl_1 / sl_2
|
||||
lark-cli sheets +sparkline-update --url "..." --sheet-id "$SID" --group-id "grpA" --properties '{
|
||||
"sparklines": [
|
||||
{"sparkline_id":"sl_1","source":"Sheet1!A2:A20"},
|
||||
{"sparkline_id":"sl_2","source":"Sheet1!B2:B20"}
|
||||
{"sparkline_id":"sl_1","source":"'Sheet1'!A2:A20"},
|
||||
{"sparkline_id":"sl_2","source":"'Sheet1'!B2:B20"}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
@@ -143,9 +143,9 @@ Step 2: `+cells-set` — range="A2", cells 含 value + cell_styles + border_styl
|
||||
| flag | 选项来源 | 适用场景 |
|
||||
|---|---|---|
|
||||
| `--options '["a","b","c"]'` | 写在命令里的固定列表 | 选项集是常量、不需要事后维护 |
|
||||
| `--source-range 'Sheet1!T1:T3'` | 已有单元格里的值 | 选项要跟数据动态同步;想维护一张「枚举值」列后多处引用 |
|
||||
| `--source-range ''\''Sheet1'\''!T1:T3'` | 已有单元格里的值 | 选项要跟数据动态同步;想维护一张「枚举值」列后多处引用 |
|
||||
|
||||
两个 flag **必须传一个、且只能传一个**——同时传或都不传,CLI 会立刻报错。`--source-range` 用 A1 + sheet 前缀写法(如 `Sheet1!T1:T3`),可以指同 sheet 也可以指其它 sheet(如 `Refs!A1:A10`)。
|
||||
两个 flag **必须传一个、且只能传一个**——同时传或都不传,CLI 会立刻报错。`--source-range` 用 A1 + sheet 前缀写法(如 `'Sheet1'!T1:T3`,sheet 名按 A1 标准单引号包裹),可以指同 sheet 也可以指其它 sheet(如 `'Refs'!A1:A10`)。
|
||||
|
||||
### 配色:默认即上色,三种意图三条线
|
||||
|
||||
@@ -182,13 +182,13 @@ lark-cli sheets +dropdown-set \
|
||||
--colors '["#bff7d9","#FFE699","#bacefd"]'
|
||||
```
|
||||
|
||||
**`--source-range` 模式**(先在 `Sheet1!T1:T3` 维护「男/女/保密」三行,再让 `B2:B21` 引用它):
|
||||
**`--source-range` 模式**(先在 `'Sheet1'!T1:T3` 维护「男/女/保密」三行,再让 `B2:B21` 引用它):
|
||||
|
||||
```
|
||||
lark-cli sheets +dropdown-set \
|
||||
--url https://... --sheet-id <id> \
|
||||
--range B2:B21 \
|
||||
--source-range 'Sheet1!T1:T3' \
|
||||
--source-range ''\''Sheet1'\''!T1:T3' \
|
||||
--colors '["#cce8ff","#ffd6e7","#e6e6e6"]'
|
||||
```
|
||||
|
||||
@@ -290,7 +290,7 @@ _公共四件套 · 系统:`--dry-run`_
|
||||
| `--colors` | string + File + Stdin(简单 JSON) | optional | 下拉胶囊背景色,RGB hex 数组(如 `["#1FB6C1","#F006C2"]`)。长度可短不可长——超长 Validate 拦截(`--colors length (N) must not exceed dropdown source size (M)`),未指定项按内置 10 色色板循环补色。**单独传即生效**;`--highlight=false` 时被忽略。 |
|
||||
| `--multiple` | bool | optional | 启用多选;默认 `false` |
|
||||
| `--highlight` | bool | optional | 下拉胶囊背景色高亮开关。**不传 = 开**(按内置 10 色色板循环上色);`--highlight=false` 关闭得到纯白下拉。配色用 `--colors` 覆盖。 |
|
||||
| `--source-range` | string | xor | listFromRange 模式的下拉源 range,A1 表示法 + sheet 前缀(如 `Sheet1!T1:T3`)。映射到 server `data_validation.range`,搭配 server `data_validation.type='listFromRange'` 自动生效。跟 `--options` 二选一:传 `--options` 走 inline 列表(type=list),传本 flag 走 range 引用(type=listFromRange)。`--colors` 长度规则不变(≤ 源 range 单元格数),`--highlight` / `--multiple` 行为相同。当 `--highlight` 开启且 source 覆盖单元格数超过 2000 时,服务端会将该下拉判为 option-error(这是不支持的组合);CLI 会向 stderr 输出 warning。如需取消,传 `--highlight=false`。 |
|
||||
| `--source-range` | string | xor | listFromRange 模式的下拉源 range,A1 表示法 + sheet 前缀(如 `'Sheet1'!T1:T3`)。映射到 server `data_validation.range`,搭配 server `data_validation.type='listFromRange'` 自动生效。跟 `--options` 二选一:传 `--options` 走 inline 列表(type=list),传本 flag 走 range 引用(type=listFromRange)。`--colors` 长度规则不变(≤ 源 range 单元格数),`--highlight` / `--multiple` 行为相同。当 `--highlight` 开启且 source 覆盖单元格数超过 2000 时,服务端会将该下拉判为 option-error(这是不支持的组合);CLI 会向 stderr 输出 warning。如需取消,传 `--highlight=false`。 |
|
||||
|
||||
### `+csv-put`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user