mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-03-28 01:50:38 +08:00
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)
This commit is contained in:
304
internal/cli/contextengine/utils.go
Normal file
304
internal/cli/contextengine/utils.go
Normal file
@ -0,0 +1,304 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user