Compare commits
10 Commits
703d36038a
...
6350f7a92a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6350f7a92a | ||
|
|
cb8f57c12b | ||
|
|
ae8642cdfe | ||
|
|
45c8623666 | ||
|
|
51eae214cc | ||
|
|
cbe3cfccba | ||
|
|
16a7619ed0 | ||
|
|
5f9d79fbb0 | ||
|
|
bdd2df7e75 | ||
|
|
06c4f8eafa |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -40,4 +40,7 @@ Thumbs.db
|
||||
*.xls
|
||||
|
||||
# .claude
|
||||
.claude/
|
||||
.claude/
|
||||
|
||||
#temp files
|
||||
temp/
|
||||
57
README.md
57
README.md
@@ -10,28 +10,36 @@
|
||||
|
||||
```
|
||||
claudeskill/
|
||||
├── .claude/ # Claude 配置目录
|
||||
│ └── skills/ # Skill 创建器引用
|
||||
├── .claude/ # Claude 配置目录(已安装的 Skills)
|
||||
│ └── skills/ # Skills 创建器引用
|
||||
│ ├── skill-creator/ # Skill 创建指南和工具
|
||||
│ ├── excel-to-markdown/ # Excel 转 Markdown Skill
|
||||
│ └── excel-report-converter/ # Excel 报表转换 Skill
|
||||
├── scripts/ # 独立脚本目录
|
||||
│ ├── excel_to_markdown.py # Excel 转 Markdown 转换器
|
||||
│ └── report_to_db_format.py # 报表格式转数据库格式脚本
|
||||
├── skills/ # 开发中的 Skills
|
||||
│ └── excel-report-converter/ # Excel 报表转换器
|
||||
│ ├── excel-report-converter/ # Excel 报表转换 Skill
|
||||
│ └── minio-upload/ # MinIO 文件上传 Skill
|
||||
├── demo_scripts/ # 演示脚本目录
|
||||
│ └── excel_to_markdown.py # Excel 转 Markdown 转换器
|
||||
├── skills/ # 开发中的 Skills(用于测试和版本控制)
|
||||
│ ├── excel-to-markdown/ # Excel 转 Markdown Skill
|
||||
│ ├── excel-report-converter/ # Excel 报表转换 Skill
|
||||
│ └── minio-upload/ # MinIO 文件上传 Skill
|
||||
├── temp/ # 临时文件目录
|
||||
├── .venv/ # Python 虚拟环境
|
||||
├── CLAUDE.md # Claude Code 项目指南
|
||||
└── README.md # 本文档
|
||||
```
|
||||
|
||||
**说明:**
|
||||
- `.claude/skills/` - 已安装的 Skills,由 Claude Code 自动加载(被 gitignore)
|
||||
- `skills/` - Skills 的开发版本,用于版本控制和协作
|
||||
- `demo_scripts/` - 可独立运行的脚本文件
|
||||
|
||||
## 目录说明
|
||||
|
||||
### `scripts/`
|
||||
### `demo_scripts/`
|
||||
存放可独立使用的脚本文件,这些脚本可以被 Skills 引用。
|
||||
|
||||
**当前脚本:**
|
||||
- `excel_to_markdown.py` - 将 Excel 文件转换为 Markdown 表格格式
|
||||
- `report_to_db_format.py` - 将报表格式的 Excel 文件转换为数据库记录格式
|
||||
|
||||
### `skills/`
|
||||
存放已创建的 Skills,每个 Skill 都是一个独立的功能模块。
|
||||
@@ -69,6 +77,31 @@ claudeskill/
|
||||
- 创建自定义的 Excel 报表转换脚本
|
||||
- 将层级报表数据(表头 + 明细行 + 表尾)转换为规范化数据库记录
|
||||
|
||||
#### minio-upload
|
||||
上传文件到 MinIO obsidian 桶的 Skill,支持灵活的路径配置。
|
||||
|
||||
**功能:**
|
||||
- 上传任意文件到 MinIO 的 obsidian 桶
|
||||
- 支持自定义子路径(如 `notes/2024`, `archive/projects`)
|
||||
- 默认上传到 `main` 路径
|
||||
- 自动处理路径规范化和文件名附加
|
||||
- 完善的错误处理和验证
|
||||
|
||||
**使用场景:**
|
||||
- 快速上传 Markdown 文档到云存储
|
||||
- 按年份/分类组织文件
|
||||
- 备份文件到 MinIO
|
||||
- 批量上传文件到指定路径
|
||||
|
||||
**示例:**
|
||||
```bash
|
||||
# 上传到默认 main 路径
|
||||
.claude/skills/minio-upload/scripts/upload_to_obsidian.sh file.md
|
||||
|
||||
# 上传到自定义子路径
|
||||
.claude/skills/minio-upload/scripts/upload_to_obsidian.sh file.md "notes/2024"
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
|
||||
### 1. 创建新 Skill
|
||||
@@ -100,7 +133,7 @@ python3 .claude/skills/skill-creator/scripts/init_skill.py <skill-name> --path s
|
||||
|
||||
```bash
|
||||
# 测试 Excel 转 Markdown
|
||||
python3 scripts/excel_to_markdown.py demo.xlsx -o demo.md --show-rows --show-cols
|
||||
python3 demo_scripts/excel_to_markdown.py demo.xlsx -o demo.md --show-rows --show-cols
|
||||
```
|
||||
|
||||
### 4. 打包 Skill(当用户主动要求才打包)
|
||||
@@ -164,7 +197,7 @@ python3 .claude/skills/skill-creator/scripts/init_skill.py excel-to-markdown --p
|
||||
|
||||
### 步骤 2:添加脚本
|
||||
```bash
|
||||
cp excel_to_markdown.py skills/excel-to-markdown/scripts/
|
||||
cp demo_scripts/excel_to_markdown.py skills/excel-to-markdown/scripts/
|
||||
```
|
||||
|
||||
### 步骤 3:编写 SKILL.md
|
||||
|
||||
@@ -152,7 +152,8 @@ python3 scripts/custom_converter.py input.xlsx output.xlsx
|
||||
Use `excel-to-markdown` to verify the output:
|
||||
|
||||
```bash
|
||||
python3 scripts/excel_to_markdown.py output.xlsx -o /tmp/verify.md --show-rows --show-cols
|
||||
# For large files, only verify first 10 records
|
||||
python3 scripts/excel_to_markdown.py output.xlsx -o /tmp/verify.md --show-rows --show-cols --rows 1:11
|
||||
```
|
||||
|
||||
Read `/tmp/verify.md` and verify:
|
||||
@@ -162,6 +163,8 @@ Read `/tmp/verify.md` and verify:
|
||||
4. Footer fields are correctly populated
|
||||
5. No data loss or corruption
|
||||
|
||||
**Important:** For large output files with many records, use `--rows 1:11` to only convert and verify the first 10 data records (plus header row). This avoids processing time and memory issues when verifying large exports.
|
||||
|
||||
### Step 4: Iterate if Needed
|
||||
|
||||
If verification reveals issues:
|
||||
|
||||
@@ -19,6 +19,18 @@ Convert an entire Excel file to Markdown:
|
||||
python3 scripts/excel_to_markdown.py input.xlsx -o output.md
|
||||
```
|
||||
|
||||
### Specify Worksheet
|
||||
|
||||
Convert a specific worksheet by name or index:
|
||||
|
||||
```bash
|
||||
# By worksheet name
|
||||
python3 scripts/excel_to_markdown.py input.xlsx -o output.md --sheet "Sheet2"
|
||||
|
||||
# By worksheet index (1-based)
|
||||
python3 scripts/excel_to_markdown.py input.xlsx -o output.md --sheet 2
|
||||
```
|
||||
|
||||
### Convert with Row/Column Numbers
|
||||
|
||||
Display Excel row and column identifiers for reference:
|
||||
@@ -51,6 +63,7 @@ python3 scripts/excel_to_markdown.py input.xlsx -o output.md --start-row 2 --sta
|
||||
| `--show-cols` | Display column letters (A, B, C...) in the first row |
|
||||
| `--start-row` | Starting row number (default: 1) |
|
||||
| `--start-col` | Starting column number (default: 1) |
|
||||
| `--sheet` | Worksheet name or index (default: first worksheet) |
|
||||
|
||||
## Features
|
||||
|
||||
@@ -67,7 +80,12 @@ The converter automatically scans the spreadsheet to identify the actual data ra
|
||||
|
||||
### Multi-Sheet Support
|
||||
|
||||
By default, the converter processes the active (last selected) sheet in the workbook.
|
||||
By default, the converter processes the first worksheet in the workbook. You can specify a different worksheet using the `--sheet` parameter:
|
||||
|
||||
- **By name**: `--sheet "Sheet2"` or `--sheet "Data"`
|
||||
- **By index**: `--sheet 2` (1-based indexing)
|
||||
|
||||
If the specified worksheet is not found, an error will display the list of available worksheets.
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
|
||||
@@ -14,15 +14,34 @@ import argparse
|
||||
class ExcelToMarkdown:
|
||||
"""Excel转Markdown转换器"""
|
||||
|
||||
def __init__(self, excel_file: str):
|
||||
def __init__(self, excel_file: str, sheet_name: Optional[str] = None):
|
||||
"""
|
||||
初始化转换器
|
||||
|
||||
|
||||
Args:
|
||||
excel_file: Excel文件路径
|
||||
sheet_name: 工作表名称或索引(None表示使用第一个工作表)
|
||||
"""
|
||||
self.workbook = openpyxl.load_workbook(excel_file, data_only=True)
|
||||
self.sheet = self.workbook.active
|
||||
|
||||
# 获取指定的工作表
|
||||
if sheet_name is None:
|
||||
# 使用第一个工作表
|
||||
self.sheet = self.workbook.worksheets[0]
|
||||
elif isinstance(sheet_name, int) or sheet_name.isdigit():
|
||||
# 按索引获取(从1开始)
|
||||
sheet_index = int(sheet_name) - 1
|
||||
if 0 <= sheet_index < len(self.workbook.worksheets):
|
||||
self.sheet = self.workbook.worksheets[sheet_index]
|
||||
else:
|
||||
raise ValueError(f"工作表索引 {sheet_name} 超出范围(1-{len(self.workbook.worksheets)})")
|
||||
else:
|
||||
# 按名称获取
|
||||
if sheet_name in self.workbook.sheetnames:
|
||||
self.sheet = self.workbook[sheet_name]
|
||||
else:
|
||||
available_sheets = ", ".join(self.workbook.sheetnames)
|
||||
raise ValueError(f"找不到工作表 '{sheet_name}',可用的工作表: {available_sheets}")
|
||||
|
||||
def detect_data_range(self, max_scan_rows: int = 100, max_scan_cols: int = 100) -> Tuple[int, int]:
|
||||
"""
|
||||
@@ -163,18 +182,24 @@ def main():
|
||||
示例:
|
||||
# 自动检测范围并转换
|
||||
python excel_to_markdown.py input.xlsx -o output.md
|
||||
|
||||
|
||||
# 指定转换前10行、前5列
|
||||
python excel_to_markdown.py input.xlsx -o output.md -r 10 -c 5
|
||||
|
||||
|
||||
# 显示行号和列号
|
||||
python excel_to_markdown.py input.xlsx -o output.md --show-rows --show-cols
|
||||
|
||||
|
||||
# 从第2行第2列开始,转换5行3列
|
||||
python excel_to_markdown.py input.xlsx -o output.md -r 5 -c 3 --start-row 2 --start-col 2
|
||||
|
||||
# 指定工作表(按名称)
|
||||
python excel_to_markdown.py input.xlsx -o output.md --sheet "Sheet2"
|
||||
|
||||
# 指定工作表(按索引)
|
||||
python excel_to_markdown.py input.xlsx -o output.md --sheet 2
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
parser.add_argument('input_file', help='输入的Excel文件路径')
|
||||
parser.add_argument('-o', '--output', help='输出的Markdown文件路径(不指定则输出到控制台)')
|
||||
parser.add_argument('-r', '--max-rows', type=int, help='最大转换行数(不指定则自动检测)')
|
||||
@@ -183,12 +208,13 @@ def main():
|
||||
parser.add_argument('--show-cols', action='store_true', help='显示列号(字母形式)')
|
||||
parser.add_argument('--start-row', type=int, default=1, help='起始行号(默认1)')
|
||||
parser.add_argument('--start-col', type=int, default=1, help='起始列号(默认1)')
|
||||
parser.add_argument('--sheet', help='工作表名称或索引(不指定则使用第一个工作表)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
# 创建转换器
|
||||
converter = ExcelToMarkdown(args.input_file)
|
||||
converter = ExcelToMarkdown(args.input_file, args.sheet)
|
||||
|
||||
# 执行转换
|
||||
markdown_text = converter.convert(
|
||||
|
||||
80
skills/gitea/SKILL.md
Normal file
80
skills/gitea/SKILL.md
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
name: gitea
|
||||
description: Gitea repository management via REST API. Use when user explicitly mentions "gitea" for: (1) Creating repositories, (2) Listing repositories, (3) Deleting repositories, (4) Pushing code to Gitea. Default server: https://gitea.server10086.icu, default account: admin. Requires GITEA_TOKEN environment variable.
|
||||
---
|
||||
|
||||
# Gitea Repository Management
|
||||
|
||||
## Configuration
|
||||
|
||||
- **Server**: `https://gitea.server10086.icu`
|
||||
- **API Base**: `https://gitea.server10086.icu/api/v1`
|
||||
- **Default Account**: `admin`
|
||||
- **Auth Header**: `Authorization: token $GITEA_TOKEN`
|
||||
|
||||
**Always verify GITEA_TOKEN exists before any operation:**
|
||||
```bash
|
||||
if [ -z "$GITEA_TOKEN" ]; then
|
||||
echo "Error: GITEA_TOKEN environment variable not set"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
## Repository Operations
|
||||
|
||||
### List Repositories
|
||||
|
||||
```bash
|
||||
# List all repos for current user
|
||||
curl -s -X GET "https://gitea.server10086.icu/api/v1/user/repos" \
|
||||
-H "Authorization: token $GITEA_TOKEN"
|
||||
```
|
||||
|
||||
### Create Repository
|
||||
|
||||
```bash
|
||||
curl -X POST "https://gitea.server10086.icu/api/v1/admin/repos" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "repo-name",
|
||||
"description": "Repository description",
|
||||
"private": false,
|
||||
"auto_init": false
|
||||
}'
|
||||
```
|
||||
|
||||
### Delete Repository
|
||||
|
||||
```bash
|
||||
curl -X DELETE "https://gitea.server10086.icu/api/v1/repos/admin/repo-name" \
|
||||
-H "Authorization: token $GITEA_TOKEN"
|
||||
```
|
||||
|
||||
### Create and Push Current Directory
|
||||
|
||||
```bash
|
||||
REPO_NAME=$(basename $(pwd))
|
||||
|
||||
# Create repo via API
|
||||
curl -X POST "https://gitea.server10086.icu/api/v1/admin/repos" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\":\"$REPO_NAME\",\"auto_init\":false}"
|
||||
|
||||
# Initialize and push
|
||||
git init
|
||||
git add .
|
||||
git commit -m "Initial commit"
|
||||
git remote add origin "https://gitea.server10086.icu/admin/$REPO_NAME.git"
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Status | Meaning |
|
||||
|--------|---------|
|
||||
| 401 | Invalid or missing GITEA_TOKEN |
|
||||
| 409 | Repository already exists |
|
||||
| 404 | Repository not found |
|
||||
| 204 | Successful deletion (expected) |
|
||||
118
skills/minio-upload/SKILL.md
Normal file
118
skills/minio-upload/SKILL.md
Normal file
@@ -0,0 +1,118 @@
|
||||
---
|
||||
name: minio-upload
|
||||
description: Upload files to Obsidian vaults via MinIO obsidian bucket. Use when user asks to upload files to Obsidian, transfer notes to vaults, sync markdown files to Obsidian, or specify a particular vault destination. The obsidian bucket serves as the sync directory for Obsidian note-taking app.
|
||||
---
|
||||
|
||||
# MinIO Upload
|
||||
|
||||
Upload files to Obsidian vaults through the MinIO obsidian bucket. This bucket serves as the synchronization directory for the Obsidian note-taking application, where each top-level directory represents a separate vault.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Upload a file to the default `main` vault:
|
||||
```bash
|
||||
.claude/skills/minio-upload/scripts/upload_to_obsidian.sh path/to/file.md
|
||||
```
|
||||
|
||||
Upload to a specific vault:
|
||||
```bash
|
||||
.claude/skills/minio-upload/scripts/upload_to_obsidian.sh path/to/file.md "journal"
|
||||
```
|
||||
|
||||
## File Conflict Handling
|
||||
|
||||
**CRITICAL**: When a file with the same name already exists in the target vault, the script will prompt you to choose an action (unless an option is specified).
|
||||
|
||||
### Interactive Prompt (Default)
|
||||
|
||||
If the file exists and no option is provided, you'll see:
|
||||
```
|
||||
⚠️ File 'filename.md' already exists in vault 'main'
|
||||
Choose an action:
|
||||
[o] Overwrite - Replace existing file
|
||||
[s] Skip - Cancel upload
|
||||
[b] Backup - Backup existing file (.bak) then overwrite
|
||||
[r] Rename - Upload with new name (adds numeric suffix)
|
||||
```
|
||||
|
||||
### Command-Line Options
|
||||
|
||||
You can specify the behavior explicitly to skip the prompt:
|
||||
|
||||
- `--force` - Overwrite existing files without prompting
|
||||
- `--skip` - Skip upload if file already exists
|
||||
- `--backup` - Backup existing file (creates `.bak` file) before overwriting
|
||||
- `--rename` - Automatically rename with numeric suffix (e.g., `file-1.md`, `file-2.md`)
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Force overwrite without prompting
|
||||
upload_to_obsidian.sh file.md main --force
|
||||
|
||||
# Skip if file exists
|
||||
upload_to_obsidian.sh file.md journal --skip
|
||||
|
||||
# Backup before overwriting
|
||||
upload_to_obsidian.sh file.md work --backup
|
||||
|
||||
# Auto-rename if conflict
|
||||
upload_to_obsidian.sh file.md personal --rename
|
||||
```
|
||||
|
||||
## Vault Configuration
|
||||
|
||||
**CRITICAL RULE**: When no vault is explicitly specified by the user, files MUST be uploaded to the `main` vault only. Do NOT assume or invent other vault names.
|
||||
|
||||
### Understanding Vault Structure
|
||||
|
||||
The MinIO obsidian bucket contains Obsidian vaults as top-level directories:
|
||||
- **Bucket root**: `minio/obsidian/`
|
||||
- **Vaults**: Each top-level directory is a separate Obsidian vault (e.g., `main/`, `journal/`, `work/`)
|
||||
- **Files uploaded to**: `minio/obsidian/{vault-name}/{filename}`
|
||||
|
||||
### Vault Selection Behavior
|
||||
|
||||
- **No vault specified**: Upload to `main` vault (DEFAULT - use this unless user explicitly requests otherwise)
|
||||
- **Specific vault specified**: Use the exact vault name provided by the user (e.g., `journal`, `work-notes`, `personal`)
|
||||
- **Nested directories**: If a vault name contains slashes, it will be normalized (e.g., `notes/journal` becomes `notes/journal/`)
|
||||
|
||||
### Important Constraints
|
||||
|
||||
1. **Never assume vaults**: If the user doesn't specify a vault, always use `main`
|
||||
2. **User intent only**: Only use non-default vaults when the user explicitly names them
|
||||
3. **Vault validation**: The script automatically normalizes paths and appends the filename
|
||||
|
||||
### Examples
|
||||
|
||||
- User says "upload file.md" → Upload to `main` vault
|
||||
- User says "upload to my journal vault" → Upload to `journal` vault
|
||||
- User says "upload to work-notes" → Upload to `work-notes` vault
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Verify file exists** - Check that the source file is accessible
|
||||
2. **Determine target vault** - Use specified vault name or default to `main`
|
||||
3. **Check for conflicts** - Detect if file already exists in target vault
|
||||
4. **Resolve conflict** - Prompt user or use specified option to handle conflict
|
||||
5. **Upload with mc** - Execute `mc cp` command to transfer file
|
||||
6. **Confirm success** - Verify upload completed and report target location
|
||||
|
||||
## Error Handling
|
||||
|
||||
The script will:
|
||||
- Exit with error if file doesn't exist
|
||||
- Exit with error if no file argument provided
|
||||
- Propagate any mc command failures
|
||||
|
||||
## Resources
|
||||
|
||||
### scripts/upload_to_obsidian.sh
|
||||
|
||||
Bash script that handles the upload process. Validates inputs, constructs the target vault path, and executes the mc copy command.
|
||||
|
||||
**Key features:**
|
||||
- Default to `main` vault when no vault name specified
|
||||
- Support vault names with proper path normalization
|
||||
- Clear error messages for invalid inputs
|
||||
- Success confirmation with source and target paths
|
||||
198
skills/minio-upload/scripts/upload_to_obsidian.sh
Executable file
198
skills/minio-upload/scripts/upload_to_obsidian.sh
Executable file
@@ -0,0 +1,198 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Upload files to MinIO obsidian bucket
|
||||
# Usage: upload_to_obsidian.sh <file_path> [subpath] [options]
|
||||
#
|
||||
# Arguments:
|
||||
# file_path - Path to the file to upload
|
||||
# subpath - Optional subpath/vault within obsidian bucket (default: main)
|
||||
#
|
||||
# Options:
|
||||
# --force - Overwrite existing files without prompting
|
||||
# --skip - Skip upload if file already exists
|
||||
# --backup - Backup existing file before overwriting (.bak extension)
|
||||
# --rename - Automatically rename if file exists (adds numeric suffix)
|
||||
#
|
||||
# Examples:
|
||||
# upload_to_obsidian.sh temp/file.md
|
||||
# upload_to_obsidian.sh temp/file.md journal
|
||||
# upload_to_obsidian.sh temp/file.md main --force
|
||||
# upload_to_obsidian.sh temp/file.md work --skip
|
||||
|
||||
set -e
|
||||
|
||||
# Initialize variables
|
||||
FORCE=false
|
||||
SKIP=false
|
||||
BACKUP=false
|
||||
RENAME=false
|
||||
FILE_PATH=""
|
||||
SUBPATH="main"
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--force)
|
||||
FORCE=true
|
||||
shift
|
||||
;;
|
||||
--skip)
|
||||
SKIP=true
|
||||
shift
|
||||
;;
|
||||
--backup)
|
||||
BACKUP=true
|
||||
shift
|
||||
;;
|
||||
--rename)
|
||||
RENAME=true
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
echo "Error: Unknown option '$1'" >&2
|
||||
echo "Usage: $0 <file_path> [subpath] [--force|--skip|--backup|--rename]" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [ -z "$FILE_PATH" ]; then
|
||||
FILE_PATH="$1"
|
||||
elif [ "$SUBPATH" = "main" ]; then
|
||||
SUBPATH="$1"
|
||||
else
|
||||
echo "Error: Too many arguments" >&2
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate mutual exclusion of conflict handling options
|
||||
CONFLICT_OPTS_COUNT=0
|
||||
[ "$FORCE" = true ] && CONFLICT_OPTS_COUNT=$((CONFLICT_OPTS_COUNT + 1))
|
||||
[ "$SKIP" = true ] && CONFLICT_OPTS_COUNT=$((CONFLICT_OPTS_COUNT + 1))
|
||||
[ "$BACKUP" = true ] && CONFLICT_OPTS_COUNT=$((CONFLICT_OPTS_COUNT + 1))
|
||||
[ "$RENAME" = true ] && CONFLICT_OPTS_COUNT=$((CONFLICT_OPTS_COUNT + 1))
|
||||
|
||||
if [ $CONFLICT_OPTS_COUNT -gt 1 ]; then
|
||||
echo "Error: Conflict handling options are mutually exclusive." >&2
|
||||
echo "Use only one of: --force, --skip, --backup, --rename" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check file path argument
|
||||
if [ -z "$FILE_PATH" ]; then
|
||||
echo "Usage: $0 <file_path> [subpath] [--force|--skip|--backup|--rename]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate file exists
|
||||
if [ ! -f "$FILE_PATH" ]; then
|
||||
echo "Error: File '$FILE_PATH' does not exist" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get filename
|
||||
FILENAME=$(basename "$FILE_PATH")
|
||||
|
||||
# Function to find unique filename by adding numeric suffix
|
||||
# Sets global variables: TARGET_PATH, FILENAME
|
||||
find_unique_filename() {
|
||||
local original_filename="$1"
|
||||
local subpath="$2"
|
||||
local counter=1
|
||||
local base_name="${original_filename%.*}"
|
||||
local ext="${original_filename##*.}"
|
||||
|
||||
# Handle files without extension
|
||||
if [ "$base_name" = "$ext" ]; then
|
||||
base_name="$original_filename"
|
||||
ext=""
|
||||
else
|
||||
ext=".$ext"
|
||||
fi
|
||||
|
||||
while true; do
|
||||
local new_name="${base_name}-${counter}${ext}"
|
||||
local new_target="minio/obsidian/${subpath}/${new_name}"
|
||||
if ! mc stat "$new_target" >/dev/null 2>&1; then
|
||||
TARGET_PATH="$new_target"
|
||||
FILENAME="$new_name"
|
||||
echo "📝 Renamed to: $FILENAME"
|
||||
return 0
|
||||
fi
|
||||
counter=$((counter + 1))
|
||||
done
|
||||
}
|
||||
|
||||
# Construct target path
|
||||
TARGET_PATH="minio/obsidian/${SUBPATH}/${FILENAME}"
|
||||
|
||||
# Ensure subpath ends without trailing slash for mc
|
||||
TARGET_PATH=$(echo "$TARGET_PATH" | sed 's://*:/:g')
|
||||
|
||||
# Check if file already exists
|
||||
if mc stat "$TARGET_PATH" >/dev/null 2>&1; then
|
||||
# User explicitly specified --skip
|
||||
if [ "$SKIP" = true ]; then
|
||||
echo "ℹ️ File already exists, skipping upload: $TARGET_PATH"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# User explicitly specified --backup
|
||||
if [ "$BACKUP" = true ]; then
|
||||
BACKUP_PATH="${TARGET_PATH}.bak"
|
||||
echo "📦 Backing up existing file: $TARGET_PATH -> $BACKUP_PATH"
|
||||
mc cp "$TARGET_PATH" "$BACKUP_PATH"
|
||||
# User explicitly specified --rename
|
||||
elif [ "$RENAME" = true ]; then
|
||||
find_unique_filename "$FILENAME" "$SUBPATH"
|
||||
# User explicitly specified --force
|
||||
elif [ "$FORCE" = true ]; then
|
||||
echo "⚠️ Overwriting existing file: $TARGET_PATH"
|
||||
else
|
||||
# No explicit option - prompt user
|
||||
echo ""
|
||||
echo "⚠️ File '$FILENAME' already exists in vault '${SUBPATH}'"
|
||||
echo "Target: $TARGET_PATH"
|
||||
echo ""
|
||||
echo "Choose an action:"
|
||||
echo " [o] Overwrite - Replace existing file"
|
||||
echo " [s] Skip - Cancel upload"
|
||||
echo " [b] Backup - Backup existing file (.bak) then overwrite"
|
||||
echo " [r] Rename - Upload with new name (adds numeric suffix)"
|
||||
echo ""
|
||||
|
||||
while true; do
|
||||
read -p "Your choice [o/s/b/r]: " choice
|
||||
case "$choice" in
|
||||
o|O|overwrite)
|
||||
echo "Overwriting existing file..."
|
||||
break
|
||||
;;
|
||||
s|S|skip)
|
||||
echo "❌ Upload cancelled"
|
||||
exit 0
|
||||
;;
|
||||
b|B|backup)
|
||||
BACKUP_PATH="${TARGET_PATH}.bak"
|
||||
echo "📦 Backing up existing file: $TARGET_PATH -> $BACKUP_PATH"
|
||||
mc cp "$TARGET_PATH" "$BACKUP_PATH"
|
||||
break
|
||||
;;
|
||||
r|R|rename)
|
||||
find_unique_filename "$FILENAME" "$SUBPATH"
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Invalid choice. Please enter o, s, b, or r"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# Upload file
|
||||
mc cp "$FILE_PATH" "$TARGET_PATH"
|
||||
|
||||
echo "✅ Uploaded: $FILE_PATH -> $TARGET_PATH"
|
||||
Reference in New Issue
Block a user