ktu-papers-api / build_syllabus.py
GitHub Action
Deploy clean API instance with LFS and README metadata
d9bc8c4
Raw
History Blame Contribute Delete
17.8 kB
import json
import os
import re
# ==========================================
# CONFIGURATION
# ==========================================
# Point this to your new flat JSON generated by split_syllabus.py
INPUT_FILE = 'syllabus_flat.json'
OUTPUT_DIR = 'syllabus_out' # Rename to 'syllabus' before pushing to GitHub
# Load JSON Data
with open(INPUT_FILE, 'r', encoding='utf-8') as f:
flat_data = json.load(f)
os.makedirs(OUTPUT_DIR, exist_ok=True)
# ==========================================
# RESTRUCTURE FLAT JSON TO GROUPED DICTIONARY
# ==========================================
# Converts flat array into: { "Branch": { "S1": [subjects], "S2": [subjects] } }
data = {}
for item in flat_data:
branch = item['branch']
sem = item['semester']
if branch not in data:
data[branch] = {}
if sem not in data[branch]:
data[branch][sem] = []
data[branch][sem].append(item)
# ==========================================
# HELPER FUNCTIONS & SORTING
# ==========================================
def slugify(text):
text = text.lower()
text = re.sub(r'[^a-z0-9]+', '-', text)
return text.strip('-')
def clean_name(name):
return re.sub(r'\s*-\s*\(.*?\)', '', name).strip()
def get_branch_acronym(branch_name):
b = branch_name.upper()
if 'COMPUTER' in b: return 'CSE'
if 'MECHANICAL' in b: return 'ME'
if 'CIVIL' in b: return 'CE'
if 'ELECTRICAL AND ELECTRONICS' in b: return 'EEE'
if 'ELECTRONICS AND COMMUNICATION' in b: return 'ECE'
if 'INFORMATION TECHNOLOGY' in b: return 'IT'
return "".join([word[0] for word in branch_name.split() if word.isalpha()])
def get_branch_priority(branch_name):
b = branch_name.upper()
if 'COMPUTER' in b: return 1 # CSE First
if 'CIVIL' in b: return 2 # Civil Second
if 'MECHANICAL' in b: return 3
if 'ELECTRICAL AND ELECTRONICS' in b: return 4
if 'ELECTRONICS AND COMMUNICATION' in b: return 5
return 99
def get_branch_theme(branch_name):
b = branch_name.upper()
if 'COMPUTER' in b: return ('indigo', 'fa-laptop-code')
if 'CIVIL' in b: return ('emerald', 'fa-building')
if 'MECHANICAL' in b: return ('orange', 'fa-cogs')
if 'ELECTRICAL AND ELECTRONICS' in b: return ('amber', 'fa-bolt')
if 'ELECTRONICS AND COMMUNICATION' in b: return ('purple', 'fa-microchip')
return ('blue', 'fa-graduation-cap')
# ==========================================
# HTML TEMPLATES
# ==========================================
def get_header(title, desc):
return f"""<!DOCTYPE html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title}</title>
<meta name="description" content="{desc}">
<meta name="robots" content="index, follow">
<script src="/tailwind-local.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@700;900&family=Plus+Jakarta+Sans:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<style>
body {{ font-family: 'Plus Jakarta Sans', sans-serif; }}
.font-sporty {{ font-family: 'Orbitron', sans-serif; letter-spacing: 0.05em; }}
.bg-mesh {{
background-color: #f8fafc;
background-image: radial-gradient(at 40% 20%, hsla(228,100%,74%,0.15) 0px, transparent 50%),
radial-gradient(at 80% 0%, hsla(189,100%,56%,0.15) 0px, transparent 50%),
radial-gradient(at 0% 50%, hsla(228,100%,74%,0.15) 0px, transparent 50%);
}}
.no-scrollbar::-webkit-scrollbar {{ display: none; }}
.no-scrollbar {{ -ms-overflow-style: none; scrollbar-width: none; }}
/* Modern UI Hover Transitions */
.hub-card {{ transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); }}
.hub-card:hover {{ transform: translateY(-6px); }}
@keyframes fadeUpEntry {{ 0% {{ opacity: 0; transform: translateY(20px); }} 100% {{ opacity: 1; transform: translateY(0); }} }}
.animate-fade-up {{ opacity: 0; animation: fadeUpEntry 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; }}
.delay-100 {{ animation-delay: 100ms; }}
.delay-200 {{ animation-delay: 200ms; }}
</style>
<link rel="icon" href="/favicon.png" type="image/png">
<meta property="og:site_name" content="Kerala Timetable">
<meta property="og:image" content="https://www.keralatimetable.in/social-preview.png">
<meta property="og:type" content="website">
</head>
<body class="bg-mesh text-slate-800 antialiased flex flex-col min-h-screen relative overflow-x-hidden">
<div id="navigation-container" class="min-h-[73px] w-full"></div>
<main class="flex-grow w-full pb-20">
"""
def get_footer():
return f"""
</main>
<script src="/components.js?v=12"></script>
<script>if(typeof loadNavigation === 'function') loadNavigation('', '', false);</script>
</body>
</html>
"""
# ==========================================
# 1. GENERATE MAIN HUB (index.html)
# ==========================================
sorted_branches = sorted(data.keys(), key=lambda x: (get_branch_priority(x), x))
main_html = get_header("KTU B.Tech Syllabus 2024 Scheme | All Branches", "Download official KTU B.Tech syllabus PDFs for the 2024 scheme.")
main_html += """
<div class="w-full max-w-6xl mx-auto px-4 pt-12 pb-8 text-center relative z-20">
<div class="animate-fade-up inline-flex items-center gap-2 px-4 py-2 rounded-full bg-slate-800 border border-slate-700 text-white text-xs font-bold uppercase tracking-wider mb-6 shadow-sm">
<i class="fas fa-layer-group text-cyan-400"></i> Official Curriculum
</div>
<h1 class="animate-fade-up delay-100 text-5xl md:text-6xl font-black text-slate-900 tracking-tight leading-tight mb-4">
B.Tech Syllabus <span class="text-transparent bg-clip-text bg-gradient-to-r from-indigo-500 to-emerald-500">2024</span>
</h1>
<p class="animate-fade-up delay-200 text-slate-500 font-medium md:text-lg max-w-2xl mx-auto mb-10">
Select your engineering branch below to access beautifully organized, semester-wise syllabus PDFs.
</p>
</div>
<div class="w-full max-w-6xl mx-auto px-4 relative z-20 animate-fade-up delay-200">
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4 md:gap-5">
"""
for branch in sorted_branches:
branch_slug = slugify(branch)
branch_acronym = get_branch_acronym(branch)
color, icon = get_branch_theme(branch)
main_html += f"""
<a href="{branch_slug}/index.html" class="hub-card relative bg-white/80 backdrop-blur-xl border border-slate-200 rounded-3xl p-5 flex flex-col items-center justify-center text-center group overflow-hidden hover:shadow-[0_15px_30px_-10px_rgba(0,0,0,0.1)] hover:border-{color}-300">
<div class="absolute -top-8 -right-8 w-24 h-24 bg-{color}-400/20 rounded-full blur-2xl group-hover:bg-{color}-500/30 transition-all duration-500"></div>
<div class="absolute -bottom-8 -left-8 w-20 h-20 bg-slate-300/20 rounded-full blur-xl group-hover:bg-{color}-300/20 transition-all duration-500"></div>
<div class="relative w-12 h-12 bg-{color}-50 text-{color}-600 rounded-2xl flex items-center justify-center text-xl mb-3 group-hover:scale-110 group-hover:bg-{color}-600 group-hover:text-white transition-all duration-300 shadow-sm border border-{color}-100 z-10">
<i class="fas {icon}"></i>
</div>
<span class="relative text-[9px] font-black bg-slate-800 text-white px-2 py-0.5 rounded mb-2 tracking-widest shadow-sm z-10">B.TECH</span>
<h2 class="relative font-extrabold text-slate-800 text-base leading-snug group-hover:text-{color}-700 transition-colors z-10">{branch_acronym}</h2>
<p class="relative text-[10px] text-slate-500 mt-1 font-semibold leading-tight line-clamp-2 z-10">{branch}</p>
</a>
"""
main_html += "</div></div>" + get_footer()
with open(os.path.join(OUTPUT_DIR, 'index.html'), 'w', encoding='utf-8') as f:
f.write(main_html)
# ==========================================
# 2. GENERATE BRANCH & SEMESTER PAGES
# ==========================================
for branch in sorted_branches:
all_sems = data[branch]
branch_slug = slugify(branch)
branch_dir = os.path.join(OUTPUT_DIR, branch_slug)
os.makedirs(branch_dir, exist_ok=True)
branch_acronym = get_branch_acronym(branch)
color, icon = get_branch_theme(branch)
def get_sem_num(sem_str):
m = re.search(r'\d+', sem_str)
return int(m.group()) if m else 99
sorted_sem_keys = sorted(all_sems.keys(), key=get_sem_num)
branch_html = get_header(f"KTU B.Tech {branch_acronym} Syllabus 2024 Scheme", f"Download complete KTU B.Tech {branch_acronym} syllabus PDFs for the 2024 scheme. All semesters available.")
branch_html += f"""
<div class="w-full max-w-5xl mx-auto px-4 pt-10 pb-6 relative z-20 animate-fade-up">
<div class="flex items-center flex-wrap text-xs font-bold text-slate-400 uppercase tracking-wider mb-6 gap-2">
<a href="/syllabus/index.html" class="hover:text-{color}-600 transition-colors">Hub</a>
<i class="fas fa-chevron-right text-[8px]"></i>
<span class="text-slate-700 bg-slate-100 px-2 py-0.5 rounded">{branch_acronym}</span>
</div>
<div class="flex items-center gap-4 mb-4">
<div class="w-12 h-12 bg-{color}-100 text-{color}-600 rounded-xl flex items-center justify-center text-2xl shadow-sm border border-{color}-200">
<i class="fas {icon}"></i>
</div>
<div>
<h1 class="text-3xl md:text-4xl font-black text-slate-900 leading-tight">KTU {branch_acronym} Syllabus</h1>
<p class="text-slate-500 font-medium mt-1">B.Tech 2024 Scheme • {branch}</p>
</div>
</div>
<div class="w-full h-px bg-slate-200 mt-8 mb-8"></div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-5">
"""
for semester in sorted_sem_keys:
subjects_list = all_sems[semester]
sem_slug = slugify(semester)
short_sem = semester.replace('Semester ', 'S')
sub_count = len(subjects_list)
branch_html += f"""
<a href="{sem_slug}.html" class="hub-card relative rounded-3xl p-6 overflow-hidden group border border-slate-200 bg-white/80 hover:border-transparent">
<div class="absolute inset-0 bg-gradient-to-br from-{color}-500 to-{color}-600 opacity-0 group-hover:opacity-100 transition-opacity duration-500 z-0"></div>
<div class="absolute top-2 right-4 text-7xl font-black text-slate-900 opacity-[0.15] group-hover:opacity-[0.25] group-hover:text-white transition-all duration-500 select-none z-0">
{short_sem}
</div>
<div class="relative z-10 flex flex-col h-full pt-8">
<h3 class="font-black text-4xl text-slate-800 group-hover:text-white transition-colors duration-300 mb-1">{short_sem}</h3>
<p class="text-xs font-bold text-slate-400 group-hover:text-{color}-100 uppercase tracking-widest transition-colors duration-300">
{sub_count} Subjects
</p>
<div class="absolute bottom-0 right-0 w-8 h-8 bg-slate-100 group-hover:bg-white/20 rounded-full flex items-center justify-center text-slate-400 group-hover:text-white transition-all duration-500 -translate-x-4 opacity-0 group-hover:translate-x-0 group-hover:opacity-100">
<i class="fas fa-arrow-right text-xs"></i>
</div>
</div>
</a>
"""
sem_page_title = f"KTU B.Tech {short_sem} {branch_acronym} Syllabus 2024 Scheme"
sem_page_desc = f"Download individual subject syllabus PDFs for KTU B.Tech {short_sem} {branch_acronym} (2024 Scheme)."
sem_html = get_header(sem_page_title, sem_page_desc)
sem_html += f"""
<div class="w-full max-w-6xl mx-auto px-4 pt-10 pb-6 relative z-20 animate-fade-up">
<div class="flex items-center flex-wrap text-xs font-bold text-slate-400 uppercase tracking-wider mb-8 gap-2">
<a href="/syllabus/index.html" class="hover:text-{color}-600 transition-colors">Hub</a>
<i class="fas fa-chevron-right text-[8px]"></i>
<a href="index.html" class="hover:text-{color}-600 transition-colors bg-slate-100 hover:bg-{color}-50 px-2 py-0.5 rounded transition-all">{branch_acronym}</a>
<i class="fas fa-chevron-right text-[8px]"></i>
<span class="text-slate-700 bg-slate-200 px-2 py-0.5 rounded">{short_sem}</span>
</div>
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-8 bg-white/60 backdrop-blur-xl border border-white shadow-[0_10px_30px_rgba(0,0,0,0.03)] p-6 rounded-3xl">
<div class="flex items-center gap-4">
<div class="w-14 h-14 bg-gradient-to-br from-{color}-500 to-{color}-600 text-white rounded-2xl flex items-center justify-center text-2xl shadow-lg shadow-{color}-500/30">
<span class="font-sporty font-black">{short_sem}</span>
</div>
<div>
<h1 class="text-2xl md:text-3xl font-black text-slate-900 leading-tight">{branch_acronym} Syllabus</h1>
<p class="text-slate-500 font-medium text-sm mt-0.5">2024 Scheme • {sub_count} Subjects Available</p>
</div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
"""
for subject in subjects_list:
# Re-mapped to the new JSON keys
sub_name = clean_name(subject.get('subject_name', 'Unknown Subject'))
sub_code = subject.get('course_code', 'KTU')
# Make sure we map the path correctly assuming your site structure
sub_link = "/" + subject.get('pdf_file', '#')
if sub_link == '/#':
btn_classes = "w-full bg-slate-100 text-slate-400 cursor-not-allowed font-bold text-sm px-4 py-2.5 rounded-xl flex items-center justify-center gap-2 border border-slate-200"
btn_text = "Link Unavailable"
target = ""
else:
btn_classes = f"w-full bg-slate-50 hover:bg-{color}-600 text-slate-600 hover:text-white border border-slate-200 hover:border-{color}-600 font-bold text-sm px-4 py-2.5 rounded-xl flex items-center justify-center gap-2 transition-all shadow-sm hover:shadow-lg hover:shadow-{color}-500/20"
btn_text = "Download PDF"
target = 'target="_blank"'
sem_html += f"""
<div class="hub-card bg-white/90 backdrop-blur-md rounded-2xl border border-slate-200 shadow-sm flex flex-col p-5 group relative overflow-hidden">
<div class="absolute -bottom-4 -right-2 text-7xl font-black text-slate-900 opacity-[0.02] pointer-events-none select-none z-0 transition-transform group-hover:scale-110 group-hover:-rotate-3">
{short_sem}
</div>
<div class="relative z-10 flex-grow flex flex-col">
<div class="flex items-center flex-wrap gap-2 mb-4">
<span class="px-2.5 py-1 rounded-md text-[10px] font-black uppercase tracking-wider bg-slate-800 text-white shadow-sm">B.TECH</span>
<span class="px-2.5 py-1 rounded-md text-[10px] font-black uppercase tracking-wider bg-{color}-100 text-{color}-700 border border-{color}-200">{short_sem}</span>
<span class="px-2.5 py-1 rounded-md text-[10px] font-bold uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200 font-sporty">{sub_code}</span>
</div>
<h3 class="text-base font-extrabold text-slate-800 leading-tight mb-6 pr-2 flex-grow">{sub_name}</h3>
<a href="{sub_link}" {target} class="{btn_classes}">
<i class="fas fa-file-pdf"></i> {btn_text}
</a>
</div>
</div>
"""
sem_html += "</div></div>" + get_footer()
with open(os.path.join(branch_dir, f"{sem_slug}.html"), 'w', encoding='utf-8') as f:
f.write(sem_html)
branch_html += "</div></div>" + get_footer()
with open(os.path.join(branch_dir, 'index.html'), 'w', encoding='utf-8') as f:
f.write(branch_html)
print("✅ SUCCESS! The compact, Auto-Sorted UI Syllabus site is generated in the 'syllabus_out' folder.")