Skip to content

Commit b3906e7

Browse files
feat: automic project creation scripts
1 parent 76ee24d commit b3906e7

2 files changed

Lines changed: 1472 additions & 0 deletions

File tree

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# ── 路径推断 ─────────────────────────────────────────────────────────────────
5+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
7+
TUTORIALS_BASE="${REPO_ROOT}/codes_and_assets/stm32f1_tutorials"
8+
TEMPLATE_DIR="${TUTORIALS_BASE}/0_start_our_tutorial"
9+
TEMPLATE_PROJECT_NAME="stm32_demo"
10+
11+
# ── 颜色定义 ─────────────────────────────────────────────────────────────────
12+
readonly RED='\033[0;31m'
13+
readonly GREEN='\033[0;32m'
14+
readonly YELLOW='\033[1;33m'
15+
readonly CYAN='\033[0;36m'
16+
readonly BOLD='\033[1m'
17+
readonly NC='\033[0m'
18+
19+
# ── 辅助函数 ─────────────────────────────────────────────────────────────────
20+
msg_info() { printf "${CYAN}[INFO]${NC} %s\n" "$*"; }
21+
msg_success() { printf "${GREEN}[OK]${NC} %s\n" "$*"; }
22+
msg_warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$*" >&2; }
23+
msg_error() { printf "${RED}[ERROR]${NC} %s\n" "$*" >&2; }
24+
die() { msg_error "$@"; exit 1; }
25+
26+
# ── 验证目录名格式: N_description ──────────────────────────────────────────
27+
validate_name() {
28+
local name="$1"
29+
if [[ ! "$name" =~ ^[1-9][0-9]*_[a-z][a-z0-9_]*$ ]]; then
30+
die "Invalid name '${name}'. Expected format: N_description (e.g., 2_uart, 10_timer_interrupt). \
31+
Only lowercase letters, digits, underscores after the prefix."
32+
fi
33+
}
34+
35+
# ── 目录名 → CMake 项目名 (去掉前缀编号) ────────────────────────────────────
36+
dir_name_to_cmake_name() {
37+
local name="$1"
38+
echo "${name#*_}"
39+
}
40+
41+
# ── 自动检测下一个可用编号 ──────────────────────────────────────────────────
42+
get_next_number() {
43+
local max=0
44+
for dir in "$TUTORIALS_BASE"/*; do
45+
[[ -d "$dir" ]] || continue
46+
local base
47+
base="$(basename "$dir")"
48+
if [[ "$base" =~ ^([0-9]+)_ ]]; then
49+
local num="${BASH_REMATCH[1]}"
50+
(( num > max )) && max=$num
51+
fi
52+
done
53+
echo $(( max + 1 ))
54+
}
55+
56+
# ── 列出现有教程 ─────────────────────────────────────────────────────────────
57+
list_tutorials() {
58+
printf "\n${BOLD}Existing tutorials:${NC}\n"
59+
for dir in "$TUTORIALS_BASE"/*; do
60+
[[ -d "$dir" ]] || continue
61+
local base
62+
base="$(basename "$dir")"
63+
local cmake_name="(template)"
64+
if [[ "$base" =~ ^[0-9]+_(.+)$ ]]; then
65+
cmake_name="${BASH_REMATCH[1]}"
66+
fi
67+
printf " %-25s %s\n" "$base" "$cmake_name"
68+
done
69+
printf "\n"
70+
}
71+
72+
# ── 使用帮助 ─────────────────────────────────────────────────────────────────
73+
show_help() {
74+
cat <<'EOF'
75+
Usage: create_tutorial.sh [OPTIONS]
76+
77+
Create a new STM32F1 tutorial project from the template (0_start_our_tutorial).
78+
79+
Options:
80+
-n, --name NAME Create tutorial with specified name (non-interactive)
81+
-l, --list List existing tutorial directories
82+
--dry-run Show what would be done without making changes
83+
-h, --help Show this help message
84+
85+
Naming convention:
86+
Directory: N_description (e.g., 2_uart, 10_timer_interrupt)
87+
CMake name: description (auto-derived from directory name)
88+
89+
Examples:
90+
# Interactive mode - auto-detect next number
91+
./create_tutorial.sh
92+
93+
# Non-interactive: create 2_uart
94+
./create_tutorial.sh --name 2_uart
95+
96+
# List existing tutorials
97+
./create_tutorial.sh --list
98+
99+
# Dry run
100+
./create_tutorial.sh --dry-run --name 2_uart
101+
EOF
102+
}
103+
104+
# ── 参数解析 ─────────────────────────────────────────────────────────────────
105+
DIR_NAME=""
106+
DRY_RUN=false
107+
ACTION=""
108+
109+
while [[ $# -gt 0 ]]; do
110+
case "$1" in
111+
-n|--name)
112+
[[ $# -lt 2 ]] && die "--name requires an argument"
113+
DIR_NAME="$2"
114+
shift 2
115+
;;
116+
-l|--list)
117+
ACTION="list"
118+
shift
119+
;;
120+
--dry-run)
121+
DRY_RUN=true
122+
shift
123+
;;
124+
-h|--help)
125+
show_help
126+
exit 0
127+
;;
128+
*)
129+
die "Unknown option: $1. Use --help for usage."
130+
;;
131+
esac
132+
done
133+
134+
# ── 列出教程 ─────────────────────────────────────────────────────────────────
135+
if [[ "$ACTION" == "list" ]]; then
136+
list_tutorials
137+
exit 0
138+
fi
139+
140+
# ── 预检查 ───────────────────────────────────────────────────────────────────
141+
[[ -d "$TUTORIALS_BASE" ]] || die "Tutorials base not found: ${TUTORIALS_BASE}"
142+
[[ -d "$TEMPLATE_DIR" ]] || die "Template not found: ${TEMPLATE_DIR}"
143+
144+
# ── 交互模式:自动推断名称 ──────────────────────────────────────────────────
145+
if [[ -z "$DIR_NAME" ]]; then
146+
list_tutorials
147+
148+
local next_num
149+
next_num="$(get_next_number)"
150+
printf "${BOLD}Next available number:${NC} ${CYAN}${next_num}${NC}\n\n"
151+
152+
read -rp "Enter tutorial description (e.g., uart, timer_interrupt): " desc
153+
[[ -z "$desc" ]] && die "Description cannot be empty."
154+
155+
# 清理输入:转小写、空格换下划线、去掉非法字符
156+
desc="$(echo "$desc" | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | tr -cd 'a-z0-9_')"
157+
158+
DIR_NAME="${next_num}_${desc}"
159+
160+
local cmake_name
161+
cmake_name="$(dir_name_to_cmake_name "$DIR_NAME")"
162+
printf "\n Directory: ${BOLD}${DIR_NAME}${NC}"
163+
printf "\n CMake name: ${BOLD}${cmake_name}${NC}\n\n"
164+
165+
read -rp "Proceed? [Y/n] " confirm
166+
[[ "${confirm,,}" == "n" ]] && { msg_info "Aborted."; exit 0; }
167+
fi
168+
169+
# ── 验证名称 ─────────────────────────────────────────────────────────────────
170+
validate_name "$DIR_NAME"
171+
172+
TARGET_DIR="${TUTORIALS_BASE}/${DIR_NAME}"
173+
CMAKE_NAME="$(dir_name_to_cmake_name "$DIR_NAME")"
174+
175+
# ── 检查目标是否已存在 ───────────────────────────────────────────────────────
176+
if [[ -d "$TARGET_DIR" ]]; then
177+
die "Directory already exists: ${TARGET_DIR}\nRemove it first or choose a different name."
178+
fi
179+
180+
# ── Dry-run 模式 ─────────────────────────────────────────────────────────────
181+
if [[ "$DRY_RUN" == true ]]; then
182+
printf "\n${BOLD}[DRY RUN] Would perform:${NC}\n"
183+
printf " Create: %s\n" "$TARGET_DIR"
184+
printf " Copy: %s/ (excluding build/ .cache/)\n" "$TEMPLATE_DIR"
185+
printf " Replace: 'stm32_demo' -> '%s' in CMakeLists.txt and .vscode/launch.json\n" "$CMAKE_NAME"
186+
printf "\n"
187+
exit 0
188+
fi
189+
190+
# ── 清理 trap:中断时删除不完整目录 ──────────────────────────────────────────
191+
COPY_STARTED=false
192+
cleanup() {
193+
if $COPY_STARTED && [[ -d "$TARGET_DIR" ]]; then
194+
msg_warn "Interrupted. Removing incomplete directory: ${TARGET_DIR}"
195+
rm -rf "$TARGET_DIR"
196+
fi
197+
}
198+
trap cleanup EXIT
199+
200+
# ── 复制模板 ─────────────────────────────────────────────────────────────────
201+
COPY_STARTED=true
202+
msg_info "Creating: ${TARGET_DIR}"
203+
204+
if command -v rsync &>/dev/null; then
205+
rsync -a --exclude='build/' --exclude='.cache/' "$TEMPLATE_DIR/" "$TARGET_DIR/"
206+
else
207+
# 回退方案:逐项复制,排除 build/ 和 .cache/
208+
mkdir -p "$TARGET_DIR"
209+
for item in "$TEMPLATE_DIR"/*; do
210+
cp -r "$item" "$TARGET_DIR/"
211+
done
212+
# 复制隐藏文件/目录
213+
for item in "$TEMPLATE_DIR"/.*; do
214+
base="$(basename "$item")"
215+
[[ "$base" == "." || "$base" == ".." || "$base" == ".cache" ]] && continue
216+
cp -r "$item" "$TARGET_DIR/"
217+
done
218+
fi
219+
220+
msg_success "Files copied (build/ and .cache/ excluded)."
221+
222+
# ── 替换项目名: CMakeLists.txt ───────────────────────────────────────────────
223+
sed -i "s/project(${TEMPLATE_PROJECT_NAME} /project(${CMAKE_NAME} /" \
224+
"${TARGET_DIR}/CMakeLists.txt"
225+
msg_success "CMakeLists.txt: project name -> ${CMAKE_NAME}"
226+
227+
# ── 替换项目名: .vscode/launch.json ─────────────────────────────────────────
228+
sed -i "s/${TEMPLATE_PROJECT_NAME}/${CMAKE_NAME}/g" \
229+
"${TARGET_DIR}/.vscode/launch.json"
230+
msg_success "launch.json: executable -> ${CMAKE_NAME}.elf"
231+
232+
# ── 验证替换结果 ─────────────────────────────────────────────────────────────
233+
if grep -rq "$TEMPLATE_PROJECT_NAME" "${TARGET_DIR}/CMakeLists.txt" \
234+
"${TARGET_DIR}/.vscode/launch.json" 2>/dev/null; then
235+
msg_warn "Some occurrences of '${TEMPLATE_PROJECT_NAME}' remain. Manual review needed."
236+
fi
237+
238+
# ── 完成 ─────────────────────────────────────────────────────────────────────
239+
TARGET_DIR="" # 清空,防止 EXIT trap 误删
240+
msg_success "Done! Tutorial created at: codes_and_assets/stm32f1_tutorials/${DIR_NAME}"
241+
printf "\n ${CYAN}cd codes_and_assets/stm32f1_tutorials/%s && mkdir build && cd build && cmake ..${NC}\n" "${DIR_NAME}"

0 commit comments

Comments
 (0)