Files
ragflow/internal/cli/contextengine/utils.go
Yingfeng 6e309f9d0a Feat: Initialize context engine CLI (#13776)
### What problem does this PR solve?

- Add multiple output format to ragflow_cli
- Initialize contextengine to Go module
  - ls datasets/ls files
  - cat file
  - search -d dir -q query

issue: #13714

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2026-03-26 21:07:06 +08:00

305 lines
6.7 KiB
Go

//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// 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 contextengine
import (
"encoding/json"
"fmt"
"time"
)
// FormatNode formats a node for display
func FormatNode(node *Node, format string) map[string]interface{} {
switch format {
case "json":
return map[string]interface{}{
"name": node.Name,
"path": node.Path,
"type": string(node.Type),
"size": node.Size,
"created_at": node.CreatedAt.Format(time.RFC3339),
"updated_at": node.UpdatedAt.Format(time.RFC3339),
}
case "table":
return map[string]interface{}{
"name": node.Name,
"path": node.Path,
"type": string(node.Type),
"size": formatSize(node.Size),
"created_at": formatTime(node.CreatedAt),
"updated_at": formatTime(node.UpdatedAt),
}
default: // "plain"
return map[string]interface{}{
"name": node.Name,
"path": node.Path,
"type": string(node.Type),
"created_at": formatTime(node.CreatedAt),
"updated_at": formatTime(node.UpdatedAt),
}
}
}
// FormatNodes formats a list of nodes for display
func FormatNodes(nodes []*Node, format string) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(nodes))
for _, node := range nodes {
result = append(result, FormatNode(node, format))
}
return result
}
// formatSize formats a size in bytes to human-readable format
func formatSize(size int64) string {
if size == 0 {
return "-"
}
const (
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
TB = 1024 * GB
)
switch {
case size >= TB:
return fmt.Sprintf("%.2f TB", float64(size)/TB)
case size >= GB:
return fmt.Sprintf("%.2f GB", float64(size)/GB)
case size >= MB:
return fmt.Sprintf("%.2f MB", float64(size)/MB)
case size >= KB:
return fmt.Sprintf("%.2f KB", float64(size)/KB)
default:
return fmt.Sprintf("%d B", size)
}
}
// formatTime formats a time to a readable string
func formatTime(t time.Time) string {
if t.IsZero() {
return "-"
}
return t.Format("2006-01-02 15:04:05")
}
// ResultToMap converts a Result to a map for JSON serialization
func ResultToMap(result *Result) map[string]interface{} {
if result == nil {
return map[string]interface{}{
"nodes": []interface{}{},
"total": 0,
}
}
nodes := make([]map[string]interface{}, 0, len(result.Nodes))
for _, node := range result.Nodes {
nodes = append(nodes, nodeToMap(node))
}
return map[string]interface{}{
"nodes": nodes,
"total": result.Total,
"has_more": result.HasMore,
"next_offset": result.NextOffset,
}
}
// nodeToMap converts a Node to a map
func nodeToMap(node *Node) map[string]interface{} {
m := map[string]interface{}{
"name": node.Name,
"path": node.Path,
"type": string(node.Type),
}
if node.Size > 0 {
m["size"] = node.Size
}
if !node.CreatedAt.IsZero() {
m["created_at"] = node.CreatedAt.Format(time.RFC3339)
}
if !node.UpdatedAt.IsZero() {
m["updated_at"] = node.UpdatedAt.Format(time.RFC3339)
}
if len(node.Metadata) > 0 {
m["metadata"] = node.Metadata
}
return m
}
// MarshalJSON marshals a Result to JSON bytes
func (r *Result) MarshalJSON() ([]byte, error) {
return json.Marshal(ResultToMap(r))
}
// PrintResult prints a result in the specified format
func PrintResult(result *Result, format string) {
if result == nil {
fmt.Println("No results")
return
}
switch format {
case "json":
data, _ := json.MarshalIndent(ResultToMap(result), "", " ")
fmt.Println(string(data))
case "table":
printTable(result.Nodes)
default: // "plain"
for _, node := range result.Nodes {
fmt.Println(node.Path)
}
}
}
// printTable prints nodes in a simple table format
func printTable(nodes []*Node) {
if len(nodes) == 0 {
fmt.Println("No results")
return
}
// Print header
fmt.Printf("%-40s %-12s %-12s %-20s %-20s\n", "NAME", "TYPE", "SIZE", "CREATED", "UPDATED")
fmt.Println(string(make([]byte, 104)))
// Print rows
for _, node := range nodes {
fmt.Printf("%-40s %-12s %-12s %-20s %-20s\n",
truncateString(node.Name, 40),
node.Type,
formatSize(node.Size),
formatTime(node.CreatedAt),
formatTime(node.UpdatedAt),
)
}
}
// truncateString truncates a string to the specified length
func truncateString(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
return s[:maxLen-3] + "..."
}
// IsValidPath checks if a path is valid
func IsValidPath(path string) bool {
if path == "" {
return false
}
// Check for invalid characters
invalidChars := []string{"..", "//", "\\", "*", "?", "<", ">", "|", "\x00"}
for _, char := range invalidChars {
if containsString(path, char) {
return false
}
}
return true
}
// containsString checks if a string contains a substring
func containsString(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
// JoinPath joins path components
func JoinPath(components ...string) string {
if len(components) == 0 {
return ""
}
result := components[0]
for i := 1; i < len(components); i++ {
if result == "" {
result = components[i]
} else if components[i] == "" {
continue
} else {
// Remove trailing slash from result
for len(result) > 0 && result[len(result)-1] == '/' {
result = result[:len(result)-1]
}
// Remove leading slash from component
start := 0
for start < len(components[i]) && components[i][start] == '/' {
start++
}
result = result + "/" + components[i][start:]
}
}
return result
}
// GetParentPath returns the parent path of a given path
func GetParentPath(path string) string {
path = normalizePath(path)
parts := SplitPath(path)
if len(parts) <= 1 {
return ""
}
return joinStrings(parts[:len(parts)-1], "/")
}
// GetBaseName returns the last component of a path
func GetBaseName(path string) string {
path = normalizePath(path)
parts := SplitPath(path)
if len(parts) == 0 {
return ""
}
return parts[len(parts)-1]
}
// HasPrefix checks if a path has the given prefix
func HasPrefix(path, prefix string) bool {
path = normalizePath(path)
prefix = normalizePath(prefix)
if prefix == "" {
return true
}
if path == prefix {
return true
}
if len(path) > len(prefix) && path[:len(prefix)+1] == prefix+"/" {
return true
}
return false
}