๐Ÿ” ํŒŒ์ด์ฌ ๋ ˆํฌ์ง€ํ† ๋ฆฌ ์ž๋™ ๋ถ„์„ ์Šคํฌ๋ฆฝํŠธ: ์‹คํ–‰ ์˜ˆ์‹œ + ์ƒ์„ธ ํ•ด์„ค + ์ „์ฒด ์ฝ”๋“œ

ํ‚ค์›Œ๋“œ: ํŒŒ์ด์ฌ ์ฝ”๋“œ ๋ถ„์„, ๋ ˆํฌ์ง€ํ† ๋ฆฌ ํ—ฌ์Šค์ฒดํฌ, LOC, ์˜์กด์„ฑ ๋ถ„์„, ๋ณต์žก๋„, ์œ ์ง€๋ณด์ˆ˜์„ฑ, pytest, AST, ๊ทธ๋ž˜ํ”„ ์‹œ๊ฐํ™”

ํŒŒ์ด์ฌ ํ”„๋กœ์ ํŠธ์˜ ์ƒํƒœ๋ฅผ ํ•œ ๋ฒˆ์— ํŒŒ์•…ํ•˜๊ณ  ์‹ถ์„ ๋•Œ๊ฐ€ ๋งŽ์ฃ . ์ด ๊ธ€์€ ๊ทธ๋Ÿฐ ์ˆœ๊ฐ„์„ ์œ„ํ•ด ์ค€๋น„ํ•œ **์ž๋™ ๋ถ„์„ ์Šคํฌ๋ฆฝํŠธ(analyze_repo.sh)**์˜ ์‹คํ–‰ ์˜ˆ์‹œ, ์ƒ์„ธ ๋™์ž‘ ํ•ด์„ค, ๊ทธ๋ฆฌ๊ณ  ์ „์ฒด ์ฝ”๋“œ๊นŒ์ง€ ํ•œ ๋ฒˆ์— ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฝํŠธ๋Š” pygount/radon/pydeps/pipreqs/pytest/ripgrep ๋“ฑ ๋„๊ตฌ๋ฅผ ์กฐํ•ฉํ•ด ๋ ˆํฌ์ง€ํ† ๋ฆฌ ํ—ฌ์Šค ๋ฆฌํฌํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.


๐Ÿš€ ํ•œ๋ˆˆ์— ๋ณด๋Š” ๊ธฐ๋Šฅ ์š”์•ฝ

  • ๋””๋ ‰ํ„ฐ๋ฆฌ ํŠธ๋ฆฌ & ํŒŒ์ผ ํฌ๊ธฐ: tree / find, du
  • LOC(์ฝ”๋“œ ๋ผ์ธ) ํ†ต๊ณ„: pygount (์–ธ์–ด๋ณ„/์ฃผ์„/๋นˆ ์ค„)
  • ์˜์กด์„ฑ ๋ถ„์„: pipreqs(์ถ”์ •), pipdeptree(์‹ค์ œ ์„ค์น˜ ํŠธ๋ฆฌ)
  • ๋ณต์žก๋„/์œ ์ง€๋ณด์ˆ˜์„ฑ: radon cc, radon mi
  • ์ž„ํฌํŠธ ๊ทธ๋ž˜ํ”„: pydeps  Graphviz SVG
  • ํ…Œ์ŠคํŠธ ์ˆ˜์ง‘: pytest --collect-only
  • TODO/FIXME ์Šค์บ”: ripgrep(๋˜๋Š” grep)
  • AST ์ธ๋ฑ์Šค: ํ•จ์ˆ˜/ํด๋ž˜์Šค๋ฅผ JSON์œผ๋กœ ์ƒ‰์ธ
  • Fallback ์˜์กด์„ฑ ์ˆ˜์ง‘๊ธฐ: ๋ฌธ๋ฒ• ์˜ค๋ฅ˜ ํŒŒ์ผ๋„ ๊ด€๋Œ€ํ•˜๊ฒŒ import ์ถ”์ถœ

โš™๏ธ ์„ค์น˜ (1ํšŒ)

python -m venv .venv && source .venv/bin/activate
pip install --upgrade pip
pip install pipreqs pipdeptree radon pydeps pygount pytest

# ์˜์กด(๊ถŒ์žฅ)
# - graphviz: pydeps๊ฐ€ SVG/DOT ์ถœ๋ ฅ์— ํ•„์š”
# - tree: ๋””๋ ‰ํ„ฐ๋ฆฌ ๋งต ์ƒ์„ฑ
# - ripgrep: TODO/FIXME ๊ฒ€์ƒ‰ ๊ฐ€์†(์—†์œผ๋ฉด grep๋กœ ๋Œ€์ฒด)
# Fedora/RHEL/CentOS:
#   sudo dnf install graphviz tree ripgrep -y
# Ubuntu/Debian:
#   sudo apt-get update && sudo apt-get install graphviz tree ripgrep -y

โ–ถ๏ธ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•

chmod +x analyze_repo.sh
./analyze_repo.sh

์—…๋กœ๋“œ ๊ถŒ์žฅ ์„ธํŠธ(์š”์•ฝ ๋ฆฌํฌํŠธ)

  • analysis_out/repo_map.txt, file_sizes.txt, loc_summary.txt
  • pipdeptree.txt, complexity.json, maintainability.json
  • imports.svg(์ƒ์„ฑ ์‹œ), test_collection.txt, todos.txt, ast_index.json
  • requirements_inferred.txt ๋˜๋Š” requirements_inferred_fallback.txt

๐Ÿงฉ ์‹คํ–‰ ์˜ˆ์‹œ & ๊ฒฐ๊ณผ ํ•ด์„ค (์š”์ )

  1. ๋ ˆํฌ ํŠธ๋ฆฌ/ํŒŒ์ผ ํฌ๊ธฐ → ๊ตฌ์กฐ ํŒŒ์•… & ์šฉ๋Ÿ‰ ์ƒ์œ„ ํŒŒ์ผ ํ™•์ธ
  2. LOC ํ†ต๊ณ„ → ์ฝ”๋“œ/์ฃผ์„/๋นˆ ์ค„ ๋ฐธ๋Ÿฐ์Šค ์ง„๋‹จ
  3. ์˜์กด์„ฑ → “์‹ค์ œ ์„ค์น˜ ํŠธ๋ฆฌ” vs “์ฝ”๋“œ๊ฐ€ ์š”๊ตฌํ•˜๋Š” ํŒจํ‚ค์ง€” ๋น„๊ต
  4. ๋ณต์žก๋„/์œ ์ง€๋ณด์ˆ˜์„ฑ → ๋ฆฌํŒฉํ„ฐ๋ง ์šฐ์„ ์ˆœ์œ„ ์„ ์ • ๊ทผ๊ฑฐ
  5. ์ž„ํฌํŠธ ๊ทธ๋ž˜ํ”„ → ๊ฒฐํ•ฉ๋„ ๋†’์€ ๋ชจ๋“ˆ ์‹๋ณ„
  6. ํ…Œ์ŠคํŠธ ์ˆ˜์ง‘ → ํ…Œ์ŠคํŠธ ์กด์žฌ/๋ช…๋ช… ๊ทœ์น™ ํŒŒ์•…
  7. TODO/FIXME → ์ž‘์—… ๋ฐฑ๋กœ๊ทธ ์ถ”์ถœ
  8. AST ์ธ๋ฑ์Šค → ํ•จ์ˆ˜/ํด๋ž˜์Šค ํƒ์ƒ‰ ๋ฐ ๋ฌธ์„œํ™” ์ž๋™ํ™” ํ† ๋Œ€

๐Ÿ’ก ์‹ค์ „ ํŒ

  • IGNORE_PIPREQS์— ๋ฌธ๋ฒ• ์˜ค๋ฅ˜/์‹คํ—˜์šฉ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด pipreqs ์‹คํŒจ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • --max-bacon=2๋กœ import ๊ทธ๋ž˜ํ”„ ๊นŠ์ด ์ œํ•œ → ๋ณต์žกํ•œ ๊ทธ๋ž˜ํ”„ ๊ฐ€๋…์„ฑ ๊ฐœ์„ .
  • CI ํŒŒ์ดํ”„๋ผ์ธ์— ์ด ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์—ฐ๊ฒฐํ•ด ์ •๊ธฐ ๋ฆฌํฌํŠธ๋ฅผ ๋งŒ๋“ค๋ฉด ํŒ€ ์ƒ์‚ฐ์„ฑ์ด ์˜ฌ๋ผ๊ฐ‘๋‹ˆ๋‹ค.

๐Ÿ“œ analyze_repo.sh ์ „์ฒด ์ฝ”๋“œ

์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ๋ ˆํฌ์ง€ํ† ๋ฆฌ ๋ฃจํŠธ์— analyze_repo.sh๋กœ ์ €์žฅํ•˜์„ธ์š”.

#!/usr/bin/env bash
set -euo pipefail

# === ์„ค์ • ===
OUT="analysis_out"
IGNORE_PIPREQS="main_bat.py"    # pipreqs๊ฐ€ ๊นจ์ง€๋Š” ํŒŒ์ผ/ํด๋”๋ฅผ ,๋กœ ์ถ”๊ฐ€(์˜ˆ: "main_bat.py,build,dist")
SKIP_DIRS=".git,.venv,__pycache__,.mypy_cache,.pytest_cache,${OUT}"
PY_EXT=".py"

mkdir -p "$OUT"

# === ์œ ํ‹ธ ===
have() { command -v "$1" >/dev/null 2>&1; }

note() { echo -e "\n[INFO] $*"; }
warn() { echo -e "\n[WARN] $*" >&2; }
fail() { echo -e "\n[ERROR] $*" >&2; exit 1; }

# === 1) ๋ ˆํฌ ํŠธ๋ฆฌ & ํŒŒ์ผ ํฌ๊ธฐ ===
note "๋ ˆํฌ ๋งต/ํŒŒ์ผ ํฌ๊ธฐ ์ˆ˜์ง‘..."
{
  echo "### REPO TREE"
  if have tree; then
    tree -a -I "$(echo "$SKIP_DIRS" | tr , '|')" || true
  else
    warn "'tree'๊ฐ€ ์—†์–ด 'find'๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค(๊ฐ„๋žต ์ถœ๋ ฅ). 'sudo dnf/apt install tree' ๊ถŒ์žฅ."
    find . -path "./.git" -prune -o -path "./${OUT}" -prune -o -print
  fi
} > "$OUT/repo_map.txt"

du -ah --max-depth=2 2>/dev/null | sort -h > "$OUT/file_sizes.txt" || {
  warn "du ์˜ต์…˜ ๋ฏธ์ง€์› ํ™˜๊ฒฝ. ๋Œ€์ฒด ๋ชจ๋“œ๋กœ ํŒŒ์ผ ํฌ๊ธฐ ์ง‘๊ณ„."
  find . -type f -not -path "./${OUT}/*" -not -path "./.git/*" -exec du -ah {} + | sort -h > "$OUT/file_sizes.txt" || true
}

# === 2) LOC/์–ธ์–ด ํ†ต๊ณ„ (pygount) ===
note "LOC/์–ธ์–ด ํ†ต๊ณ„(pygount)..."
pygount . \
  --format=summary \
  --folders-to-skip "$SKIP_DIRS" \
  > "$OUT/loc_summary.txt"

# (์„ ํƒ) ํŒŒ์ผ๋ณ„ ์ƒ์„ธ ํ•„์š” ์‹œ ์ฃผ์„ ํ•ด์ œ
# pygount . \
#   --format=cloc-xml \
#   --folders-to-skip "$SKIP_DIRS" \
#   > "$OUT/loc_by_file.cloc.xml"

# === 3) ์˜์กด์„ฑ(์ถ”์ • + ์‹ค์ œ ํŠธ๋ฆฌ) ===
note "์˜์กด์„ฑ ์ถ”์ •(pipreqs) ๋ฐ ์„ค์น˜ ํŠธ๋ฆฌ(pipdeptree)..."
# pipreqs๋Š” ๋ฌธ๋ฒ• ์—๋Ÿฌ ํŒŒ์ผ์—์„œ ์ค‘๋‹จ๋  ์ˆ˜ ์žˆ์–ด --ignore ์‚ฌ์šฉ, ์‹คํŒจํ•ด๋„ ๊ณ„์†
pipreqs . \
  --force \
  --encoding=utf-8 \
  --ignore "$IGNORE_PIPREQS" \
  --savepath "$OUT/requirements_inferred.txt" || warn "pipreqs ์‹คํŒจ(๋ฌด์‹œํ•˜๊ณ  ๊ณ„์†). --ignore ๋ชฉ๋ก์„ ๋Š˜๋ฆฌ๊ฑฐ๋‚˜ fallback ์‚ฌ์šฉ ๊ถŒ์žฅ."

# requirements.txt๊ฐ€ ์žˆ๋‹ค๋ฉด ์„ค์น˜ ํ›„ ์˜์กด ํŠธ๋ฆฌ ์ถœ๋ ฅ(์‹คํŒจ ํ—ˆ์šฉ)
if [[ -f requirements.txt ]]; then
  pip install -r requirements.txt || warn "requirements ์„ค์น˜ ์ค‘ ๊ฒฝ๊ณ  ๋ฐœ์ƒ(๊ณ„์† ์ง„ํ–‰)."
fi
pipdeptree > "$OUT/pipdeptree.txt" || warn "pipdeptree ์‹คํŒจ(๊ณ„์†)."

# === 4) ๋ณต์žก๋„/์œ ์ง€๋ณด์ˆ˜์„ฑ (radon) ===
note "๋ณต์žก๋„/์œ ์ง€๋ณด์ˆ˜์„ฑ(radon)..."
radon cc -s -a -j . > "$OUT/complexity.json"    || warn "radon cc ์‹คํŒจ(๊ณ„์†)."
radon mi -s -j . > "$OUT/maintainability.json"  || warn "radon mi ์‹คํŒจ(๊ณ„์†)."

# === 5) import ๊ทธ๋ž˜ํ”„ (pydeps) ===
note "import ๊ทธ๋ž˜ํ”„(pydeps → SVG)..."
if have dot; then
  :
else
  warn "graphviz(dot) ๋ฏธ์„ค์น˜. 'sudo dnf/apt install graphviz' ๊ถŒ์žฅ. ์‹œ๋„๋Š” ํ•ด๋ด…๋‹ˆ๋‹ค."
fi
# ์ตœ์‹  pydeps: -T svg -o ํŒŒ์ผ
if have pydeps; then
  pydeps . --noshow --max-bacon=2 -T svg -o "$OUT/imports.svg" || warn "pydeps ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ ์‹คํŒจ(๊ณ„์†)."
else
  warn "pydeps ๋ฏธ์„ค์น˜. 'pip install pydeps' ํ›„ ์žฌ์‹œ๋„ ๊ฐ€๋Šฅ."
fi

# === 6) ํ…Œ์ŠคํŠธ ์ˆ˜์ง‘ (pytest) ===
note "pytest ํ…Œ์ŠคํŠธ ์ˆ˜์ง‘..."
pytest --collect-only -q > "$OUT/test_collection.txt" || warn "pytest ์ˆ˜์ง‘ ์‹คํŒจ(๊ณ„์†)."

# === 7) TODO/FIXME/HACK/XXX ๊ฒ€์ƒ‰ ===
note "TODO/FIXME/HACK/XXX ํ‚ค์›Œ๋“œ ๊ฒ€์ƒ‰..."
if have rg; then
  rg -n "TODO|FIXME|HACK|XXX" --glob "!${OUT}/**" --glob "!.git/**" > "$OUT/todos.txt" || true
else
  grep -RInE "TODO|FIXME|HACK|XXX" . \
    --exclude-dir="$OUT" --exclude-dir=".git" --exclude-dir=".venv" \
    > "$OUT/todos.txt" || true
fi

# === 8) AST ์ธ๋ฑ์Šค(ํ•จ์ˆ˜/ํด๋ž˜์Šค) ===
note "AST ์ธ๋ฑ์Šค ์ƒ์„ฑ(ํ•จ์ˆ˜/ํด๋ž˜์Šค)..."
python - << 'PY' || warn "AST ์ธ๋ฑ์Šค ์ƒ์„ฑ ์ค‘ ๋ฌธ์ œ(๊ณ„์†)."
import os, ast, json
skip = set(s.strip() for s in ".git,.venv,__pycache__,.mypy_cache,.pytest_cache,analysis_out".split(","))
index=[]
for root,_,files in os.walk("."):
    if any(k in root for k in skip): 
        continue
    for f in files:
        if f.endswith(".py"):
            p=os.path.join(root,f)
            try:
                with open(p,"r",encoding="utf-8",errors="ignore") as fh:
                    src = fh.read()
                try:
                    m=ast.parse(src, filename=p)
                except SyntaxError as e:
                    # ๋ฌธ๋ฒ• ์—๋Ÿฌ๋Š” ๊ธฐ๋ก๋งŒ ๋‚จ๊ธฐ๊ณ  ๊ฑด๋„ˆ๋œ€
                    index.append({"path":p,"error":f"SyntaxError: {e}"})
                    continue
                funcs=[n.name for n in ast.walk(m) if isinstance(n,ast.FunctionDef)]
                clss=[n.name for n in ast.walk(m) if isinstance(n,ast.ClassDef)]
                index.append({"path":p,"functions":funcs,"classes":clss})
            except Exception as e:
                index.append({"path":p,"error":str(e)})
open("analysis_out/ast_index.json","w",encoding="utf-8").write(json.dumps(index,ensure_ascii=False,indent=2))
PY

# === 9) (์˜ต์…˜) pipreqs Fallback: ๊ด€๋Œ€ํ•œ import ์ˆ˜์ง‘๊ธฐ ===
note "pipreqs Fallback(๊ด€๋Œ€ํ•œ import ์ˆ˜์ง‘๊ธฐ) ์‹คํ–‰..."
python - << 'PY' || warn "Fallback import ์ˆ˜์ง‘ ์‹คํŒจ(๊ณ„์†)."
import os, ast, tokenize, io
root="."
skip=set(s.strip() for s in ".git,.venv,__pycache__,.mypy_cache,.pytest_cache,analysis_out".split(","))
imports=set()
stdlib={"os","sys","json","re","subprocess","pathlib","typing","logging","datetime","time","math","itertools","functools","collections","dataclasses","ast","tokenize","io","enum","statistics","decimal","random","uuid"}

def tolerant_parse(text):
    try:
        return ast.parse(text)
    except SyntaxError:
        return None

for r,_,fs in os.walk(root):
    if any(k in r for k in skip): 
        continue
    for f in fs:
        if f.endswith(".py"):
            p=os.path.join(r,f)
            try:
                txt=open(p,"r",encoding="utf-8",errors="ignore").read()
            except Exception:
                continue
            m=tolerant_parse(txt)
            if m:
                for n in ast.walk(m):
                    if isinstance(n, ast.Import):
                        for a in n.names:
                            imports.add(a.name.split(".")[0])
                    elif isinstance(n, ast.ImportFrom) and n.module:
                        imports.add(n.module.split(".")[0])
            else:
                # ๊ฐ„๋‹จ ๋ผ์ธ ์Šค์บ”(๋ฌธ๋ฒ• ์—๋Ÿฌ ํŒŒ์ผ์šฉ)
                for line in txt.splitlines():
                    line=line.strip()
                    if line.startswith("import "):
                        for part in line[len("import "):].split(","):
                            imports.add(part.strip().split(".")[0])
                    elif line.startswith("from ") and " import " in line:
                        mod=line.split()[1]
                        imports.add(mod.split(".")[0])

third=sorted(m for m in imports if m and m not in stdlib)
open("analysis_out/requirements_inferred_fallback.txt","w",encoding="utf-8").write("\n".join(third))
print(f"[fallback] third-party candidates: {len(third)} → analysis_out/requirements_inferred_fallback.txt")
PY

# === ์™„๋ฃŒ ์š”์•ฝ ===
note "์™„๋ฃŒ! ์‚ฐ์ถœ๋ฌผ ๋ชฉ๋ก:"
ls -lh "$OUT" || true
echo
echo "์—…๋กœ๋“œ ๊ถŒ์žฅ ํŒŒ์ผ:"
echo "- $OUT/repo_map.txt"
echo "- $OUT/file_sizes.txt"
echo "- $OUT/loc_summary.txt"
echo "- $OUT/pipdeptree.txt"
echo "- $OUT/complexity.json"
echo "- $OUT/maintainability.json"
echo "- $OUT/imports.svg (์žˆ์œผ๋ฉด)"
echo "- $OUT/test_collection.txt"
echo "- $OUT/todos.txt"
echo "- $OUT/ast_index.json"
echo "- $OUT/requirements_inferred.txt (์žˆ์œผ๋ฉด)"
echo "- $OUT/requirements_inferred_fallback.txt (๋ณด์กฐ)"

 


โœ… ์š”์•ฝ & ๊ฒฐ๋ก 

  • ์ด ์Šคํฌ๋ฆฝํŠธ ํ•˜๋‚˜๋กœ ํ”„๋กœ์ ํŠธ ์ƒํƒœ ์ง„๋‹จ์„ ์ž๋™ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • CI์— ๋ถ™์ด๋ฉด ์ •๊ธฐ ๊ฑด๊ฐ• ๋ฆฌํฌํŠธ๋ฅผ ์ž๋™ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ณ , ์‹ ๊ทœ ์ธ์ˆ˜/๋ ˆ๊ฑฐ์‹œ ๊ด€๋ฆฌ/๋ฆฌํŒฉํ„ฐ๋ง ๊ธฐํš์— ํƒ์›”ํ•ฉ๋‹ˆ๋‹ค.
  • ํ•„์š”์‹œ ํŒŒ์ผ๋ณ„ LOC CSV๋‚˜ ํ˜ธ์ถœ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ๊ธฐ ๋“ฑ ํ™•์žฅ๋„ ์†์‰ฝ๊ฒŒ ์ถ”๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

๋ณธ ๊ธ€์€ chatgpt๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

'etc' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

ssh key๋กœ ์„œ๋ฒ„ ์ ‘์†  (0) 2020.05.18
bash Prompt ์„ค์ •  (0) 2020.05.18
ubuntu user ์ƒ์„ฑ  (0) 2020.04.20
git command  (0) 2020.03.16
ansible๋กœ ubuntu user ์ƒ์„ฑ  (0) 2020.02.19
Posted by jerymy
,