Subversion Repositories Koakuma

Rev

Rev 11 | Rev 13 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 nishi 1
#!/usr/bin/env tclsh
2
# $Id: koakuma.cgi.in 12 2024-10-02 03:25:33Z nishi $
3
 
4
set KOAKUMA_VERSION "1.00"
5
set components ""
6
 
7
proc exiting {code} {
8
	exit $code
9
}
10
 
3 nishi 11
proc loop_components {run} {
12
	global components
6 nishi 13
	foreach {name description version genre} $components {
3 nishi 14
		eval $run
15
	}
16
}
17
 
2 nishi 18
proc crash {reason} {
19
	global components KOAKUMA_VERSION
20
	puts stderr "----- Start Koakuma Crash dump log -----"
21
	puts stderr "Included components:"
3 nishi 22
	loop_components {
2 nishi 23
		puts stderr "	$name: $description, version $version"
24
	}
25
	puts stderr "Reason: $reason"
26
	puts stderr "----- End Koakuma Crash dump log -----"
27
	puts	"Content-Type: text/html"
28
	puts	"Status: 500 Internal Server Error"
29
	puts	""
30
	puts	"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">"
31
	puts	"<html>"
32
	puts	"	<head>"
33
	puts	"		<meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">"
34
	puts	"		<title>Oops</title>"
35
	puts	"	</head>"
36
	puts	"	<body>"
37
	puts	"		<h1>Oops</h1>"
38
	puts	"		<hr>"
39
	puts	"		Koakuma version $KOAKUMA_VERSION crashed, reason: <code>$reason</code><br>"
40
	puts	"		See the server error log for details."
41
	puts	"	</body>"
42
	puts	"</html>"
43
	exiting 1
44
}
45
 
3 nishi 46
if { ![info exists env(PATH_INFO)] } {
47
	puts "Status: 301 Moved Permanently"
48
	puts "Location: $env(SCRIPT_NAME)/"
49
	puts ""
50
	exiting 0
2 nishi 51
}
52
 
3 nishi 53
if { [catch {
54
	package require tdom
4 nishi 55
	dom createNodeCmd -tagName "rpc" elementNode rootXML
6 nishi 56
	dom createNodeCmd -tagName "project" elementNode keyProject
4 nishi 57
	dom createNodeCmd -tagName "version" -jsonType NONE elementNode keyVersion
5 nishi 58
	dom createNodeCmd -tagName "error" -jsonType NONE elementNode keyError
6 nishi 59
	dom createNodeCmd -tagName "name" -jsonType NONE elementNode keyName
60
	dom createNodeCmd -tagName "description" -jsonType NONE elementNode keyDescription
61
	dom createNodeCmd -tagName "vcs" -jsonType NONE elementNode keyVCS
62
	dom createNodeCmd -tagName "url" -jsonType NONE elementNode keyURL
4 nishi 63
	dom createNodeCmd -jsonType STRING textNode valueString
3 nishi 64
}] } {
65
	crash "Failed to load tDOM"
2 nishi 66
}
3 nishi 67
 
68
if { [catch {
69
	foreach path [glob @@PREFIX@@/lib/koakuma/component/*.tcl] {
70
		source "$path"
71
	}
72
}] } {
73
	crash "Could not load components"
2 nishi 74
}
3 nishi 75
 
5 nishi 76
set data ""
77
 
78
while { [gets stdin line] >= 0 } {
79
	if { "$data" == "" } {
80
		set data "$line"
81
	} else {
82
		set data "$data\n$line"
83
	}
84
}
85
 
3 nishi 86
set toc ""
87
set result ""
88
set content ""
89
 
6 nishi 90
proc write_db {data} {
91
	set fid [open "@@PREFIX@@/lib/koakuma/db/projects.db" "w"]
92
	puts $fid "$data"
93
	close $fid
94
}
95
 
96
proc readall_db {} {
97
	set data ""
98
	set fid [open "@@PREFIX@@/lib/koakuma/db/projects.db" "r"]
99
	while { [gets $fid line] >= 0 } {
100
		if { "$data" == "" } {
101
			set data "$line"
102
		} else {
103
			set data "$data\n$line"
104
		}
105
	}
106
	close $fid
107
	return "$data"
108
}
109
 
3 nishi 110
proc rputs {data} {
111
	global result
112
	if { "$result" == "" } {
113
		set result "$data"
114
	} else {
115
		set result "$result\n$data"
116
	}
117
}
118
 
119
proc tputs {data} {
120
	global content
121
	if { "$content" == "" } {
122
		set content "$data"
123
	} else {
124
		set content "$content\n$data"
125
	}
126
}
127
 
128
proc html_escape {data} {
129
	return "[regsub -all {&} "[regsub -all {>} "[regsub -all {<} "$data" "&lt;"]" "&gt;"]" "&amp;"]"
130
}
131
 
132
proc open_projects {} {
133
	while 1 {
134
		if { ![info exists "@@PREFIX@@/lib/koakuma/db/projects.lock"] } {
135
			break
136
		}
137
		set fid [open "@@PREFIX@@/lib/koakuma/db/projects.lock" "w"]
138
		if { ![info exists "/proc/[gets $fid line]"] } {
139
			close $fid
140
			break
141
		}
142
		after 10
143
		close $fid
144
	}
145
	set fid [open "@@PREFIX@@/lib/koakuma/db/projects.lock" "w"]
146
	puts $fid "[pid]"
147
	close $fid
148
}
149
 
150
proc scan_projects {run} {
151
	set fid [open "@@PREFIX@@/lib/koakuma/db/projects.db" "r"]
152
	set content ""
153
	while { [gets $fid line] >= 0 } {
154
		if { "$content" == "" } {
155
			set content "$line"
156
		} else {
157
			set content "$content\n$line"
158
		}
159
	}
160
	close $fid
161
	set dom [dom parse "$content"]
162
	set doc [$dom documentElement]
163
	foreach elem [$doc selectNodes "/projects/project"] {
164
		set name "[$elem selectNodes "string(name)"]"
165
		set description "[$elem selectNodes "string(description)"]"
166
		eval $run
167
	}
168
}
169
 
170
proc project_exists {projname} {
171
	set desc ""
172
	scan_projects {
173
		upvar 1 desc desc
174
		upvar 1 projname projname
175
		if { "$name" == "$projname" } {
176
			set desc "$description"
177
			break
178
		}
179
	}
180
	return "$desc"
181
}
182
 
183
proc close_projects {} {
184
	file delete "@@PREFIX@@/lib/koakuma/db/projects.lock"
185
}
186
 
187
proc start_html {title has_toc} {
12 nishi 188
	global toc env
3 nishi 189
	rputs	"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">"
190
	rputs	"<html>"
191
	rputs	"	<head>"
192
	rputs	"		<meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">"
193
	rputs	"		<title>$title - Koakuma</title>"
194
	rputs	"		<link rel=\"stylesheet\" href=\"/static/style.css\">"
195
	rputs	"	</head>"
196
	rputs	"	<body>"
197
	rputs	"		<a href=\"/koakuma\" id=\"gomain\">"
198
	rputs	"			<img src=\"/static/koakuma.png\" alt=\"Koakuma by Kasuya Baian\" height=\"192px\">"
199
	rputs	"		</a>"
200
	rputs	"		<div id=\"space\"></div>"
201
	rputs	"		<div id=\"title\">"
202
	rputs	"			Koakuma"
203
	rputs	"		</div>"
12 nishi 204
	rputs	"		<a href=\"$env(SCRIPT_NAME)\">Root</a>"
3 nishi 205
	rputs	"		<div id=\"clearfix\"></div>"
206
	if { "$has_toc" == "1" } {
207
		rputs	"		<div id=\"toc\">"
208
		rputs	"			<div id=\"toctitle\">TOC</div>"
209
		foreach sect $toc {
210
			if { "[string range "[regsub -all { } "$sect" "-"]" 0 0]" == "-" } {
211
				rputs "<a class=\"shiftlink\" href=\"#TOC-[regsub {^-} "[regsub -all { } "$sect" "-"]" ""]\">[regsub {^-} "$sect" ""]</a><br>"
212
			} else {
213
				rputs "<a href=\"#TOC-[regsub -all { } "$sect" "-"]\">$sect</a><br>"
214
			}
215
		}
216
		rputs	"		</div>"
217
	}
218
	if { "$has_toc" == "1" } {
219
		rputs	"		<div id=\"doc\">"
220
	} else {
221
		rputs	"		<div id=\"doc-notoc\">"
222
	}
223
	rputs	"			<h1>$title</h1>"
224
}
225
proc end_html {has_toc} {
226
	global KOAKUMA_VERSION
227
	rputs	"		</div>"
228
	rputs	"		<hr>"
229
	rputs	"		<i>Powered by <a href=\"http://nishi.boats/koakuma\">Koakuma</a> $KOAKUMA_VERSION</i>"
230
	rputs	"	</body>"
231
	rputs	"</html>"
232
}
233
 
234
proc add_toc {data} {
235
	global toc
236
	tputs	"<h2 id=\"TOC-[regsub -all { } "$data" "-"]\"><a href=\"#TOC-[regsub -all { } "$data" "-"]\">#</a> $data</h2>"
237
	lappend toc "$data"
238
}
239
 
240
proc add_toc2 {data} {
241
	global toc
242
	tputs	"<h3 id=\"TOC-[regsub -all { } "$data" "-"]\"><a href=\"#TOC-[regsub -all { } "$data" "-"]\">#</a> $data</h3>"
243
	lappend toc "-$data"
244
}
245
 
246
if { [catch {
247
	set path "[regsub -all {/+} "$env(PATH_INFO)" "/"]"
4 nishi 248
	if { [regexp {^/rpc(/.*)?$} "$path"] } {
249
		rputs "Content-Type: application/json"
250
	} else {
12 nishi 251
		if { ![regexp {/$} "$env(PATH_INFO)"] } {
252
			puts "Status: 301 Moved Permanently"
253
			puts "Location: $env(SCRIPT_NAME)$env(PATH_INFO)/"
254
			puts ""
255
			exiting 0
256
		}
4 nishi 257
		rputs "Content-Type: text/html"
258
	}
3 nishi 259
	if { "$path" == "/" } {
260
		add_toc "Tcl Information"
261
		tputs	"<table border=\"0\">"
262
		tputs	"	<tr>"
263
		tputs	"		<th>"
264
		tputs	"			Version"
265
		tputs	"		</th>"
266
		tputs	"		<td>"
267
		tputs	"			$tcl_version"
268
		tputs	"		</td>"
269
		tputs	"	</tr>"
270
		tputs	"	<tr>"
271
		tputs	"		<th>"
272
		tputs	"			Platform"
273
		tputs	"		</th>"
274
		tputs	"		<td>"
275
		tputs	"			$tcl_platform(os)/$tcl_platform(machine) $tcl_platform(osVersion)"
276
		tputs	"		</td>"
277
		tputs	"	</tr>"
278
		tputs	"</table>"
279
		add_toc "Components"
280
		loop_components {
281
			if { [llength [info procs "${name}_info"]] > 0 } {
282
				${name}_info
283
			}
284
		}
285
		set has_projects 0
286
		add_toc "Projects"
287
		open_projects
288
		scan_projects {
289
			upvar 1 has_projects has_projects
290
			if { "$has_projects" == "0" } {
291
				set has_projects 1
292
				tputs	"<table border=\"0\">"
293
			}
294
			tputs	"<tr>"
295
			tputs	"	<th><a href=\"/koakuma/project/$name\">$name</a></th>"
296
			tputs	"	<td>$description</td>"
297
			tputs	"</tr>"
298
		}
299
		close_projects
300
		if { "$has_projects" == "1" } {
301
			tputs	"</table>"
302
		} else {
303
			tputs	"No projects have been added, yet."
304
		}
305
 
306
		rputs ""
307
		start_html "Main" 1
308
		rputs "$content"
309
		end_html 1
4 nishi 310
	} elseif { [regexp {^/rpc(/.*)?$} "$path"] } {
311
		regexp {^/rpc(/.*)?$} "$path" -> api
312
		set doc [dom createDocumentNode]
5 nishi 313
		$doc appendFromScript {
314
			keyVersion {valueString "$KOAKUMA_VERSION"}
315
		}
4 nishi 316
		if { "$api" == "" || "$api" == "/" } {
5 nishi 317
			rputs ""
318
			rputs "[$doc asJSON]"
12 nishi 319
		} elseif { "$api" == "/launch-job" } {
320
			if { [catch {dom parse -json "$data" clidoc}] } {
321
				rputs "Status: 400 Bad Request"
322
				$doc appendFromScript {
323
					keyError {valueString "Bad JSON"}
324
				}
325
			} else {
326
				set projname "[$clidoc selectNodes "string(/name)"]"
327
				set builddesc "[$clidoc selectNodes "string(/description)"]"
328
				if { "$projname" == "" || "$builddesc" == "" } {
329
					rputs "Status: 400 Bad Request"
330
					$doc appendFromScript {
331
						keyError {valueString "Required field missing"}
332
					}
333
				} else {
334
					set has_name 0
335
					open_projects
336
					scan_projects {
337
						upvar 1 has_name has_name
338
						upvar 1 projname projname
339
						if { "$name" == "$projname" } {
340
							set has_name 1
341
							break
342
						}
343
					}
344
					close_projects
345
					if { $has_name == 0 } {
346
						rputs "Status: 400 Bad Request"
347
						$doc appendFromScript {
348
							keyError {valueString "Project does not exist"}
349
						}
350
					} else {
351
					}
352
				}
353
			}
354
			rputs ""
355
			rputs "[$doc asJSON]"
5 nishi 356
		} elseif { "$api" == "/create-project" } {
357
			if { [catch {dom parse -json "$data" clidoc}] } {
358
				rputs "Status: 400 Bad Request"
359
				$doc appendFromScript {
360
					keyError {valueString "Bad JSON"}
361
				}
362
			} else {
363
				set projname "[$clidoc selectNodes "string(/name)"]"
364
				set projdescription "[$clidoc selectNodes "string(/description)"]"
6 nishi 365
				set projvcs "[$clidoc selectNodes "string(/vcs)"]"
5 nishi 366
				set url "[$clidoc selectNodes "string(/url)"]"
6 nishi 367
				if { "$projname" == "" || "$projdescription" == "" || "$projvcs" == "" || "$url" == "" } {
5 nishi 368
					rputs "Status: 400 Bad Request"
369
					$doc appendFromScript {
370
						keyError {valueString "Required field missing"}
371
					}
372
				} else {
373
					set has_vcs 0
6 nishi 374
					set has_name 0
5 nishi 375
					loop_components {
376
						upvar 1 has_vcs has_vcs
6 nishi 377
						upvar 1 projvcs projvcs
378
						if { "$name" == "$projvcs" && "$genre" == "VCS" } {
5 nishi 379
							set has_vcs 1
380
							break
381
						}
382
					}
6 nishi 383
					open_projects
384
					scan_projects {
385
						upvar 1 has_name has_name
386
						upvar 1 projname projname
387
						if { "$name" == "$projname" } {
388
							set has_name 1
389
							break
390
						}
391
					}
392
					close_projects
5 nishi 393
					if { $has_vcs == 0 } {
394
						rputs "Status: 400 Bad Request"
395
						$doc appendFromScript {
396
							keyError {valueString "Not a valid VCS"}
397
						}
6 nishi 398
					} elseif { $has_name == 1 } {
399
						rputs "Status: 400 Bad Request"
400
						$doc appendFromScript {
401
							keyError {valueString "Project already exists"}
402
						}
5 nishi 403
					} else {
404
						open_projects
6 nishi 405
						set xml "[readall_db]"
406
						set xmldoc [dom parse "$xml"]
407
						set root [$xmldoc documentElement]
408
						$root appendFromScript {
409
							keyProject {
410
								keyName {valueString "$projname"}
411
								keyDescription {valueString "$projdescription"}
412
								keyVCS {valueString "$projvcs"}
413
								keyURL {valueString "$url"}
414
							}
415
						}
416
						write_db "[$xmldoc asXML]"
11 nishi 417
						file mkdir "@@PREFIX@@/lib/koakuma/db/data/$projname"
5 nishi 418
						close_projects
419
					}
420
				}
421
			}
422
			rputs ""
423
			rputs "[$doc asJSON]"
424
		} else {
4 nishi 425
			$root appendFromScript {
5 nishi 426
				keyError {valueString "No such endpoint"}
4 nishi 427
			}
5 nishi 428
			rputs "Status: 404 Not Found"
429
			rputs ""
4 nishi 430
			rputs "[$doc asJSON]"
431
		}
3 nishi 432
	} elseif { [regexp {^/project/[^/]+.*$} "$path"] } {
12 nishi 433
		regexp {^/project/([^/]+)(.*)$} "$path" -> projname projpath
3 nishi 434
		open_projects
435
		set has_project [project_exists "$projname"]
436
		close_projects
437
 
438
		if { "$has_project" != "" } {
12 nishi 439
			if { "$projpath" == "" || "$projpath" == "/" } {
440
				add_toc "Description"
441
				tputs "[html_escape "$has_project"]"
442
				add_toc "Details"
443
				tputs	"<table border=\"0\">"
444
				tputs	"	<tr>"
445
				tputs	"		<th>"
446
				tputs	"			Last run"
447
				tputs	"		</th>"
448
				tputs	"		<td>"
449
				if { [file exists "@@PREFIX@@/lib/koakuma/db/data/$projname/lastrun"] } {
450
					set fid [open "@@PREFIX@@/lib/koakuma/db/data/$projname/lastrun" "r"]
451
					set date "[clock format "[gets $fid]" -format "%a %b %d %H:%M:%S %Z %Y"]"
452
					close $fid
453
					tputs "$date"
454
				} else {
455
					tputs "No builds yet"
456
				}
457
				tputs	"			"
458
				tputs	"		</td>"
459
				tputs	"	</tr>"
460
				tputs	"	<tr>"
461
				tputs	"		<th>"
462
				tputs	"			Last successful run"
463
				tputs	"		</th>"
464
				tputs	"		<td>"
465
				if { [file exists "@@PREFIX@@/lib/koakuma/db/data/$projname/lastsuccessfulrun"] } {
466
					set fid [open "@@PREFIX@@/lib/koakuma/db/data/$projname/lastsuccessfulrun" "r"]
467
					set date "[clock format "[gets $fid]" -format "%a %b %d %H:%M:%S %Z %Y"]"
468
					close $fid
469
					tputs "$date"
470
				} else {
471
					tputs "No successful builds yet"
472
				}
473
				tputs	"			"
474
				tputs	"		</td>"
475
				tputs	"	</tr>"
476
				tputs	"</table>"
477
				add_toc "Build details"
478
 
479
				rputs ""
480
				start_html "Project: $projname" 1
481
				rputs "$content"
482
				end_html 1
11 nishi 483
			} else {
12 nishi 484
				tputs "I could not find the endpoint you were finding."
485
 
486
				rputs "Status: 404 Not Found"
487
				rputs ""
488
				start_html "Project: $projname" 1
489
				rputs "$content"
490
				end_html 1
11 nishi 491
			}
3 nishi 492
		} else {
493
			tputs "I could not find the project you were finding."
494
 
495
			rputs "Status: 404 Not Found"
496
			rputs ""
497
			start_html "Not Found" 0
498
			rputs "$content"
499
			end_html 0
500
		}
501
	} else {
502
		tputs "I could not find the content you were finding."
503
 
504
		rputs "Status: 404 Not Found"
505
		rputs ""
506
		start_html "Not Found" 0
507
		rputs "$content"
508
		end_html 0
509
	}
510
}] } {
511
	crash "Could not render the HTML"
512
} else {
513
	puts "$result"
514
}
515
exiting 0