|
|
#!/bin/bash |
|
|
|
|
|
|
|
|
|
|
|
set -e |
|
|
|
|
|
|
|
|
DRY_RUN=false |
|
|
BACKUP=false |
|
|
for arg in "$@"; do |
|
|
case $arg in |
|
|
--dry-run) |
|
|
DRY_RUN=true |
|
|
shift |
|
|
;; |
|
|
--backup) |
|
|
BACKUP=true |
|
|
shift |
|
|
;; |
|
|
*) |
|
|
;; |
|
|
esac |
|
|
done |
|
|
|
|
|
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" |
|
|
REPORTS_DIR="${PROJECT_ROOT}/tài nguyên/báo cáo" |
|
|
DOCS_DIR="${PROJECT_ROOT}/backend/docs" |
|
|
|
|
|
echo "📁 Tổ chức file markdown theo ngày và category..." |
|
|
if [ "$DRY_RUN" = true ]; then |
|
|
echo "🔍 DRY-RUN MODE: Chỉ preview, không di chuyển file" |
|
|
fi |
|
|
echo "" |
|
|
|
|
|
|
|
|
get_file_creation_date() { |
|
|
local file="$1" |
|
|
local git_date="" |
|
|
local birth_date="" |
|
|
local mod_date="" |
|
|
local meta_date="" |
|
|
local final_date="" |
|
|
|
|
|
|
|
|
if command -v git &> /dev/null && [ -d "${PROJECT_ROOT}/.git" ]; then |
|
|
git_date=$(cd "$PROJECT_ROOT" && git log --diff-filter=A --format="%ai" -- "$file" 2>/dev/null | tail -1 | cut -d' ' -f1) |
|
|
if [ -z "$git_date" ] || [[ ! "$git_date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then |
|
|
git_date="" |
|
|
fi |
|
|
fi |
|
|
|
|
|
|
|
|
if [[ "$OSTYPE" == "darwin"* ]]; then |
|
|
birth_date=$(stat -f "%SB" -t "%Y-%m-%d" "$file" 2>/dev/null || stat -f "%Sm" -t "%Y-%m-%d" -B "$file" 2>/dev/null || echo "") |
|
|
if [ -z "$birth_date" ]; then |
|
|
birth_date=$(stat -f "%Sm" -t "%Y-%m-%d" "$file" 2>/dev/null || echo "") |
|
|
fi |
|
|
else |
|
|
birth_date=$(stat -c "%y" "$file" 2>/dev/null | cut -d' ' -f1 || echo "") |
|
|
fi |
|
|
|
|
|
|
|
|
if [[ "$OSTYPE" == "darwin"* ]]; then |
|
|
mod_date=$(stat -f "%Sm" -t "%Y-%m-%d" "$file" 2>/dev/null || echo "") |
|
|
else |
|
|
mod_date=$(stat -c "%y" "$file" 2>/dev/null | cut -d' ' -f1 || echo "") |
|
|
fi |
|
|
|
|
|
|
|
|
if [ -f "$file" ]; then |
|
|
meta_date=$(grep -iE "(created|date|ngày):\s*[0-9]{4}-[0-9]{2}-[0-9]{2}" "$file" 2>/dev/null | head -1 | grep -oE "[0-9]{4}-[0-9]{2}-[0-9]{2}" | head -1 || echo "") |
|
|
fi |
|
|
|
|
|
|
|
|
if [ -n "$git_date" ]; then |
|
|
final_date="$git_date" |
|
|
elif [ -n "$birth_date" ]; then |
|
|
final_date="$birth_date" |
|
|
elif [ -n "$mod_date" ]; then |
|
|
final_date="$mod_date" |
|
|
elif [ -n "$meta_date" ]; then |
|
|
final_date="$meta_date" |
|
|
else |
|
|
final_date=$(date +"%Y-%m-%d") |
|
|
fi |
|
|
|
|
|
|
|
|
if [[ ! "$final_date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then |
|
|
final_date=$(date +"%Y-%m-%d") |
|
|
fi |
|
|
|
|
|
echo "$final_date" |
|
|
} |
|
|
|
|
|
|
|
|
detect_category() { |
|
|
local file="$1" |
|
|
local filename=$(basename "$file" | tr '[:upper:]' '[:lower:]') |
|
|
local category="" |
|
|
|
|
|
|
|
|
if [ -f "$file" ]; then |
|
|
meta_category=$(grep -iE "category:\s*[a-z]+" "$file" 2>/dev/null | head -1 | grep -oE "category:\s*([a-z_]+)" | cut -d: -f2 | tr -d ' ' || echo "") |
|
|
if [ -n "$meta_category" ]; then |
|
|
echo "$meta_category" |
|
|
return |
|
|
fi |
|
|
fi |
|
|
|
|
|
|
|
|
declare -A category_keywords |
|
|
category_keywords["database"]="database|postgresql|mysql|mongodb|redis|db_|_db|sql" |
|
|
category_keywords["backend"]="backend|api|server|django|flask|fastapi|be_|_be|endpoint" |
|
|
category_keywords["frontend"]="frontend|ui|react|vue|angular|component|fe_|_fe|interface" |
|
|
category_keywords["devops"]="devops|docker|kubernetes|ci/cd|deploy|jenkins|terraform" |
|
|
category_keywords["ml"]="ml|ai|model|training|neural|tensorflow|pytorch|embedding" |
|
|
category_keywords["plan"]="plan|roadmap|planning|strategy|milestone" |
|
|
category_keywords["setup"]="setup|config|install|installation|guide|tutorial" |
|
|
|
|
|
local max_matches=0 |
|
|
for cat in "${!category_keywords[@]}"; do |
|
|
matches=$(echo "$filename" | grep -oiE "${category_keywords[$cat]}" | wc -l | tr -d ' ') |
|
|
if [ "$matches" -gt "$max_matches" ]; then |
|
|
max_matches=$matches |
|
|
category="$cat" |
|
|
fi |
|
|
done |
|
|
|
|
|
|
|
|
if [ -z "$category" ] && [ -f "$file" ]; then |
|
|
local content=$(head -100 "$file" | tr '[:upper:]' '[:lower:]') |
|
|
max_matches=0 |
|
|
for cat in "${!category_keywords[@]}"; do |
|
|
matches=$(echo "$content" | grep -oiE "${category_keywords[$cat]}" | wc -l | tr -d ' ') |
|
|
if [ "$matches" -gt "$max_matches" ]; then |
|
|
max_matches=$matches |
|
|
category="$cat" |
|
|
fi |
|
|
done |
|
|
fi |
|
|
|
|
|
|
|
|
if [ -z "$category" ]; then |
|
|
category="general" |
|
|
fi |
|
|
|
|
|
echo "$category" |
|
|
} |
|
|
|
|
|
|
|
|
ensure_category_folder() { |
|
|
local date_folder="$1" |
|
|
local category="$2" |
|
|
local category_folder="${date_folder}/${category}" |
|
|
|
|
|
if [ ! -d "$category_folder" ]; then |
|
|
if [ "$DRY_RUN" = false ]; then |
|
|
mkdir -p "$category_folder" |
|
|
echo "✅ Đã tạo folder: $category_folder" |
|
|
|
|
|
|
|
|
cat > "${category_folder}/README.md" << EOF |
|
|
# Tài liệu ${category} - $(basename "$date_folder") |
|
|
|
|
|
Folder này chứa các file markdown về **${category}** được tạo trong ngày $(basename "$date_folder"). |
|
|
|
|
|
## Danh sách file |
|
|
|
|
|
$(find "$category_folder" -maxdepth 1 -name "*.md" ! -name README.md -type f | sed 's|.*/||' | sort | sed 's/^/- /') |
|
|
|
|
|
## Category |
|
|
${category} |
|
|
|
|
|
## Ngày tạo |
|
|
$(basename "$date_folder") |
|
|
EOF |
|
|
else |
|
|
echo "🔍 [DRY-RUN] Sẽ tạo folder: $category_folder" |
|
|
fi |
|
|
fi |
|
|
} |
|
|
|
|
|
|
|
|
move_markdown() { |
|
|
local source_file="$1" |
|
|
local target_dir="$2" |
|
|
local filename=$(basename "$source_file") |
|
|
local target_file="${target_dir}/${filename}" |
|
|
|
|
|
if [ -f "$target_file" ]; then |
|
|
if cmp -s "$source_file" "$target_file" 2>/dev/null; then |
|
|
echo "⚠️ File trùng nội dung: $target_file (xóa file gốc)" |
|
|
if [ "$DRY_RUN" = false ]; then |
|
|
rm "$source_file" |
|
|
fi |
|
|
return 1 |
|
|
else |
|
|
echo "⚠️ File đã tồn tại nhưng khác nội dung: $target_file" |
|
|
return 1 |
|
|
fi |
|
|
fi |
|
|
|
|
|
if [ "$BACKUP" = true ] && [ "$DRY_RUN" = false ]; then |
|
|
backup_dir="${PROJECT_ROOT}/.backup/markdowns/$(date +%Y%m%d_%H%M%S)" |
|
|
mkdir -p "$backup_dir" |
|
|
cp "$source_file" "${backup_dir}/${filename}" |
|
|
fi |
|
|
|
|
|
if [ "$DRY_RUN" = false ]; then |
|
|
mv "$source_file" "$target_file" |
|
|
echo "✅ Đã di chuyển: $filename → $target_dir" |
|
|
else |
|
|
echo "🔍 [DRY-RUN] Sẽ di chuyển: $filename → $target_dir" |
|
|
fi |
|
|
return 0 |
|
|
} |
|
|
|
|
|
|
|
|
if [ -d "$DOCS_DIR" ]; then |
|
|
echo "🔍 Tìm file markdown trong: $DOCS_DIR" |
|
|
|
|
|
find "$DOCS_DIR" -maxdepth 1 -name "*.md" -type f | while read -r file; do |
|
|
if [ -f "$file" ]; then |
|
|
filename=$(basename "$file") |
|
|
echo "" |
|
|
echo "📄 Xử lý: $filename" |
|
|
|
|
|
|
|
|
date_created=$(get_file_creation_date "$file") |
|
|
echo " 📅 Ngày: $date_created" |
|
|
|
|
|
|
|
|
category=$(detect_category "$file") |
|
|
echo " 📂 Category: $category" |
|
|
|
|
|
date_folder="${REPORTS_DIR}/${date_created}" |
|
|
ensure_category_folder "$date_folder" "$category" |
|
|
|
|
|
|
|
|
move_markdown "$file" "${date_folder}/${category}" |
|
|
fi |
|
|
done |
|
|
fi |
|
|
|
|
|
|
|
|
echo "" |
|
|
echo "🔍 Tìm file markdown lộn xộn khác..." |
|
|
|
|
|
EXCLUDE_DIRS=( |
|
|
"node_modules" |
|
|
"ops" |
|
|
"chatbot/training" |
|
|
".git" |
|
|
".venv" |
|
|
"__pycache__" |
|
|
".cursor" |
|
|
"tài nguyên/báo cáo" |
|
|
".backup" |
|
|
) |
|
|
|
|
|
find "$PROJECT_ROOT" -name "*.md" -type f | while read -r file; do |
|
|
skip=false |
|
|
for exclude in "${EXCLUDE_DIRS[@]}"; do |
|
|
if [[ "$file" == *"$exclude"* ]]; then |
|
|
skip=true |
|
|
break |
|
|
fi |
|
|
done |
|
|
|
|
|
if [[ "$file" == *"tài nguyên/báo cáo"* ]]; then |
|
|
skip=true |
|
|
fi |
|
|
|
|
|
if [ "$skip" = true ]; then |
|
|
continue |
|
|
fi |
|
|
|
|
|
filename=$(basename "$file") |
|
|
echo "" |
|
|
echo "📄 Xử lý: $filename" |
|
|
|
|
|
|
|
|
date_created=$(get_file_creation_date "$file") |
|
|
echo " 📅 Ngày: $date_created" |
|
|
|
|
|
|
|
|
category=$(detect_category "$file") |
|
|
echo " 📂 Category: $category" |
|
|
|
|
|
date_folder="${REPORTS_DIR}/${date_created}" |
|
|
ensure_category_folder "$date_folder" "$category" |
|
|
|
|
|
|
|
|
move_markdown "$file" "${date_folder}/${category}" |
|
|
done |
|
|
|
|
|
echo "" |
|
|
echo "✅ Hoàn tất tổ chức file markdown!" |
|
|
echo "" |
|
|
echo "📊 Thống kê:" |
|
|
for date_folder in "${REPORTS_DIR}"/20*; do |
|
|
if [ -d "$date_folder" ]; then |
|
|
date_name=$(basename "$date_folder") |
|
|
echo " 📁 $date_name:" |
|
|
for category_folder in "${date_folder}"/*; do |
|
|
if [ -d "$category_folder" ]; then |
|
|
category_name=$(basename "$category_folder") |
|
|
count=$(find "$category_folder" -maxdepth 1 -name "*.md" ! -name README.md -type f | wc -l | tr -d ' ') |
|
|
if [ "$count" -gt 0 ]; then |
|
|
echo " └─ $category_name: $count file(s)" |
|
|
fi |
|
|
fi |
|
|
done |
|
|
fi |
|
|
done |
|
|
|
|
|
if [ "$DRY_RUN" = true ]; then |
|
|
echo "" |
|
|
echo "💡 Chạy lại không có --dry-run để thực sự di chuyển file" |
|
|
fi |
|
|
|