186 lines
3.8 KiB
JavaScript
186 lines
3.8 KiB
JavaScript
const { readFile } = require("node:fs/promises")
|
|
|
|
const FILES = [
|
|
"01.json",
|
|
"06.json",
|
|
"10.json"
|
|
]
|
|
|
|
// just some asumption, usually if there's a class in a certain day, the room is open for the entire day
|
|
const OPEN_TIME = "08:00"
|
|
const CLOSE_TIME = "17:00"
|
|
|
|
const DAY_ORDER = {
|
|
"Senin": 1,
|
|
"Selasa": 2,
|
|
"Rabu": 3,
|
|
"Kamis": 4,
|
|
"Jumat": 5,
|
|
"Sabtu": 6,
|
|
"Minggu": 7
|
|
}
|
|
|
|
|
|
async function loadFiles()
|
|
{
|
|
const results = await Promise.all(FILES.map(async file =>
|
|
{
|
|
const content = await readFile(file, "utf8")
|
|
const json = JSON.parse(content)
|
|
|
|
return json.data
|
|
}))
|
|
|
|
return results.flat()
|
|
}
|
|
|
|
function parseDateRoom(value)
|
|
{
|
|
/*
|
|
* Senin, 10:00-11:40 (A1.51 (Ged Baru))
|
|
* Selasa, 10.00-11.50 (A6.12 (Ged Baru))
|
|
*
|
|
* some entries is malformed (top 1 uni btw)
|
|
* so why even bother to match the closing ")" lolol
|
|
*/
|
|
|
|
const match = value.match(/^([^,]+),\s*(\d{2}[:.]\d{2})-(\d{2}[:.]\d{2})\s+\((.*)$/)
|
|
|
|
if (!match) return null
|
|
|
|
const [ , day, rawStart, rawEnd, rawRoom] = match
|
|
|
|
const start = rawStart.replace(".", ":")
|
|
const end = rawEnd.replace(".", ":")
|
|
|
|
/*
|
|
* Remove the outer ')' from normal entries.
|
|
*
|
|
* "(A1.51 (Ged Baru))"
|
|
* ^ remove only the final ')'
|
|
*
|
|
* For malformed entries:
|
|
* "(Lab A1.04+Lab A3.02 - Gabungan (Gd. Baru)"
|
|
* the final ')' is also removed.
|
|
*/
|
|
|
|
const room = rawRoom.endsWith(")") ? rawRoom.slice(0, -1) : rawRoom
|
|
|
|
return { day, start, end, room }
|
|
}
|
|
|
|
|
|
function timeToMinutes(time)
|
|
{
|
|
const [hours, minutes] = time.split(":").map(Number)
|
|
|
|
return hours * 60 + minutes
|
|
}
|
|
|
|
|
|
function buildSchedule(classes)
|
|
{
|
|
/*
|
|
* Map
|
|
* room
|
|
* -> day
|
|
* -> classes
|
|
*/
|
|
|
|
const schedule = new Map()
|
|
|
|
for (const classInfo of classes)
|
|
{
|
|
if (!Array.isArray(classInfo.dates_rooms))
|
|
{
|
|
console.warn(`Skipping ${classInfo.classname}: invalid dates_rooms`)
|
|
continue
|
|
}
|
|
|
|
for (const dateRoom of classInfo.dates_rooms)
|
|
{
|
|
const parsed = parseDateRoom(dateRoom)
|
|
|
|
if (!parsed)
|
|
{
|
|
console.warn(`Could not parse: ${dateRoom}`)
|
|
continue
|
|
}
|
|
|
|
const { day, start, end, room } = parsed
|
|
|
|
if (!schedule.has(room))
|
|
{
|
|
schedule.set(room, new Map())
|
|
}
|
|
|
|
const roomSchedule = schedule.get(room)
|
|
|
|
if (!roomSchedule.has(day))
|
|
{
|
|
roomSchedule.set(day, [])
|
|
}
|
|
|
|
roomSchedule.get(day).push({
|
|
start,
|
|
end,
|
|
className: classInfo.classname
|
|
})
|
|
}
|
|
}
|
|
|
|
return schedule
|
|
}
|
|
|
|
|
|
function formatSchedule(schedule)
|
|
{
|
|
const lines = []
|
|
|
|
const rooms = [...schedule.keys()].sort((a, b) => a.localeCompare(b))
|
|
|
|
for (const room of rooms)
|
|
{
|
|
const roomSchedule = schedule.get(room)
|
|
|
|
lines.push(`## ${room} \`${OPEN_TIME} - ${CLOSE_TIME}\``)
|
|
|
|
const days = [...roomSchedule.keys()].sort((a, b) => DAY_ORDER[a] - DAY_ORDER[b])
|
|
|
|
for (const day of days)
|
|
{
|
|
const classes = roomSchedule.get(day).sort((a, b) => timeToMinutes(a.start) - timeToMinutes(b.start))
|
|
|
|
lines.push(` - **${day}:**`)
|
|
|
|
for (const classInfo of classes)
|
|
{
|
|
lines.push(` - \`${classInfo.start} - ${classInfo.end}\`: **${classInfo.className}**`)
|
|
}
|
|
}
|
|
|
|
lines.push("\n")
|
|
}
|
|
|
|
return lines.join("\n")
|
|
}
|
|
|
|
|
|
/////////////////
|
|
|
|
async function main()
|
|
{
|
|
const cl = await loadFiles()
|
|
const sc = buildSchedule(cl)
|
|
|
|
console.log(formatSchedule(sc))
|
|
}
|
|
|
|
|
|
main().catch(error =>
|
|
{
|
|
console.error(error)
|
|
process.exitCode = 1
|
|
})
|
|
|