From e50cee75f11f0a9b1b4006daec2381bb497e02f6 Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Fri, 6 Jun 2025 02:58:02 +0200 Subject: Rewrote EVERYTHING! WAY better now, but only works with ZOOM reports atm --- main.odin | 420 +++++++++++++++++++++++++++++++++++------------------- parts/end.html | 43 ++++-- parts/start2.html | 6 +- 3 files changed, 306 insertions(+), 163 deletions(-) diff --git a/main.odin b/main.odin index c25ee7e..1a4f4ef 100755 --- a/main.odin +++ b/main.odin @@ -5,10 +5,33 @@ import "core:os" import "core:path/filepath" import "core:strings" +/* +TODO: Fix sort direction toggle behavior +TODO: If submitted path is a directory, traverse the tree to find all .CSVs +TODO: Move info that's all the same throughout the table into the info header +*/ + +VERBOSE :: true + PART_ONE :: #load("parts/start.html", string) PART_TWO :: #load("parts/start2.html", string) PART_END :: #load("parts/end.html", string) +Info_Line :: struct { + field : string, + entry : string, +} + +Report :: struct { + title : string, + info_lines : []Info_Line, + header : []string, + table : [][]string, + column_count : int, + row_count : int, + tc_column_index : int, +} + main :: proc() { input_file_name : string @@ -29,180 +52,287 @@ main :: proc() { input_file_name = os.args[1] } + input_file_info, error := os.stat(input_file_name) + if error == os.ERROR_NONE { + parsed, ok_parse := parse(input_file_info.fullpath) + if !ok_parse { + fmt.printf("Parse failed: {}\n", input_file_info.fullpath) + } + output_name := fmt.aprintf("{}_Knekt.html", input_file_info.fullpath[:len(input_file_info.fullpath)-4]) + render(parsed, output_name) + } else { + fmt.printf("Skipped: {}\n", input_file_name) + } - // Reading CSV file + free_all(context.temp_allocator) +} + - data, ok := os.read_entire_file(input_file_name, context.allocator) +parse :: proc(path : string, device : Device = .UNSET) -> (Report, bool) { + device := device + output : Report = {} + data, ok := os.read_entire_file(path, context.temp_allocator) if !ok { - fmt.printf("ERROR: Could not read file: {}\n", os.args[1]) - return + fmt.printf("ERROR: Could not read file: {}\n", path) + return {}, false } - defer delete(data, context.allocator) + file_info, _ := os.lstat(path, context.temp_allocator) - total_output: string = PART_ONE // Page title - total_output = append(total_output, fmt.aprintf("{} - Lydrapport", input_file_name[len(input_file_name)-17:len(input_file_name)-11])) - total_output = append(total_output, PART_TWO) - it := string(data) - // First we do a pass to find out at what column the channels start and end, - // and to find out how many channels we actually use at maximum. - // This will let us compress the channel columns to only what is needed. - tracks_start := 0 - tracks_end := 0 - max_tracks := 0 - line_index := 0 + // STAGE 1 -------------------------------------------------------------- + // First, we detect what kind of sound report this is + line_number := 0 + it := string(data) for line in strings.split_lines_iterator(&it) { - tracks_seen_this_line := 0 - for element, e in strings.split(line, ",") { - if element == "Trk 1" { - tracks_start = e - fmt.printf("Tracks start at: {}\n", tracks_start) - } - if element == "Notes" { - tracks_end = e-1 - fmt.printf("Tracks end at: {}\n", tracks_end) - } - if e >= tracks_start && tracks_end >= e { // If there's anything in the range of where tracks are, count it - if element != "" do tracks_seen_this_line += 1 - } + if (device!=.UNSET) { break } + if line == "\"SOUND REPORT\"," { + device = .ZOOM + if VERBOSE do fmt.printf("Detected ZOOM from quotes and comma on line index {}\n", line_number) + } + if line == "\"ZOOM F8\"," { + device = .ZOOM + if VERBOSE do fmt.printf("Detected ZOOM from \"ZOOM F8\" on line index {}\n", line_number) + } + if line == "SOUND REPORT" { + device = .SOUND_DEVICES + if VERBOSE do fmt.printf("Detected SOUND_DEVICES from unquoted SOUND REPORT line index {}\n", line_number) } - // Update the max - if tracks_seen_this_line > max_tracks { - max_tracks = tracks_seen_this_line - fmt.printf("Highest track count so far found on line {}, with {} tracks.\n", line_index, max_tracks) - } - line_index += 1 + line_number += 1 } - potensial_tracks := tracks_end+1 - tracks_start - unused_tracks := potensial_tracks - max_tracks - - // Now we output the HTML. - it = string(data) - line_index = 0 - blank_lines := 0 - state : states = .TITLE - time_in_state := 0 - info_reading_to_field := true - for line in strings.split_lines_iterator(&it) { - //fmt.printf("state.{} line_index:{} t:{} blank_lines:{} : \t", state, line_index, time_in_state, blank_lines) - there_was_content_in_line := false + + + + + // STAGE 2 -------------------------------------------------------------- + // Measuring content for allocation + if device == .ZOOM { + output.column_count = 21 // Ugly magic number, could be fucked by firmware update + output.info_lines = make([]Info_Line, 2, context.temp_allocator) - // Writing the headers and section stuff. - #partial switch state { - case .HEADER: - if time_in_state == 1 do total_output = append(total_output, " \n \n \n") - - case .BODY: - total_output = append(total_output, " \n") + output.row_count = strings.count(string(data), "\n") - 7 // Ugly magic number, could be fucked by firmware update + output.table = make([][]string, output.row_count, context.temp_allocator) + output.header = make([]string, output.column_count, context.temp_allocator) + for &row in output.table { + row = make([]string, output.column_count, context.temp_allocator) } - - columns_skipped := 0 - for element, e in strings.split(line, ",") { - if len(element)> 0 do there_was_content_in_line = true - if len(element)> 0 do blank_lines = 0 - // Filling the row of data - switch state { - case .TITLE: - //fmt.printf("{} T ", element) - case .INFO: - //fmt.printf("{} I ", element) - - if len(element)>0 { - if info_reading_to_field { - total_output = append(total_output, fmt.aprintf("

{}", element)) - } else { - total_output = append(total_output, fmt.aprintf(" {}

\n", element[1:len(element)-1])) - } - info_reading_to_field = !info_reading_to_field - } - - case .HEADER: - //fmt.printf("{} H ", element) - if time_in_state == 1 { - if e == 4 { // Magic number to select the default sorting column - total_output = append(total_output, fmt.aprintf(" \n", element)) - } else if e <= tracks_start+max_tracks-1 || e > tracks_end { - total_output = append(total_output, fmt.aprintf(" \n", element)) + } + + + + // STAGE 3 -------------------------------------------------------------- + // Filling with data + + if VERBOSE do fmt.printf("Struct before main parse:\n%#v\n", output) + + first_channel_index := -1 + last_channel_index := -1 + + stage : Stages = .TITLE + #partial switch device { + case .SOUND_DEVICES: + fmt.printf("ERROR parsing {}: Sound Devices reports are not yet supported in this version.\n", path) + return {}, false + + + case .ZOOM: + fmt.printf("Parsing [{}] as ZOOM report.\n", path) + + // Getting title + if file_info.name[:8] == "F8n Pro_" { + output.title = file_info.name[8:len(file_info.name)-4] + } else if file_info.name[:4] == "F8n_" { // I don't own one, so I don't know if this is what the F8n does + output.title = file_info.name[4:len(file_info.name)-4] + } else if file_info.name[:3] == "F8_" { // TODO: Verify this is what the original F8 does + output.title = file_info.name[4:len(file_info.name)-4] + } + fmt.printf("Title: {}\n", output.title) + + line_index := 0 + info_line_index := 0 + body_line_index := 0 + it := string(data) + for line in strings.split_lines_iterator(&it) { + switch stage { + case .TITLE: + if line_index == 1 { // Ugly magic number, could be fucked by firmware update + stage = .INFO } - } - - case .BODY: - //fmt.printf("{} B ", element) - if e == tracks_end+1 { - for _ in 0..<(columns_skipped - unused_tracks) { // Add padding so note at end ligns up - total_output = append(total_output, " \n") + + case .INFO: + if line == "" { + stage = .HEADER + continue } - } - if element != "" { - - if e >= tracks_start { - total_output = append(total_output, fmt.aprintf(" \n", element[1:len(element)-1])) - /*} else if e == 0 { // Making filename a link to the file - total_output = append(total_output, fmt.aprintf(" \n", element, element))*/ - } else { - total_output = append(total_output, fmt.aprintf(" \n", element)) + line_elements := strings.split(line, ",") + if VERBOSE do fmt.printf(".INFO {}: {}\n", line_index, line_elements) + field_raw := line_elements[0] + entry_raw := line_elements[1] + field := line_elements[0][1:len(field_raw)-1] + entry := line_elements[1][1:len(entry_raw)-1] + output.info_lines[info_line_index].field = field + output.info_lines[info_line_index].entry = entry + info_line_index += 1 + + case .HEADER: + if VERBOSE do fmt.printf(".HEADER {}:", line_index) + // to skip empty entry after trailing comma we do a silly slice + for element, e in strings.split(line, ",")[:output.column_count] { + if VERBOSE do fmt.printf(" {}", element) + output.header[e] = element[1:len(element)-1] + + if element[:4] == "\"Tr " { + if first_channel_index==-1 do first_channel_index = e + last_channel_index = e + output.header[e] = fmt.aprintf("Trk {}", e-first_channel_index+1, allocator=context.temp_allocator) + } + if element == "\"Start TC\"" { + output.tc_column_index = e + } } + if VERBOSE do fmt.printf("\n") + stage = .BODY - } else { - columns_skipped += 1 - } + case .BODY: + if VERBOSE do fmt.printf("first_channel_index: {}\n", first_channel_index) + if VERBOSE do fmt.printf("last_channel_index: {}\n", last_channel_index) + if line == "" do break + if VERBOSE do fmt.printf(".BODY {}:", line_index) + // to skip empty entry after trailing comma we do a silly slice + for element, e in strings.split(line, ",")[:output.column_count] { + if VERBOSE do fmt.printf(" {}", element) + output.table[body_line_index][e] = element[1:len(element)-1] + } + if VERBOSE do fmt.printf("\n") + body_line_index += 1 + } + line_index += 1 + } + } + + + + + // STAGE 4 -------------------------------------------------------------- + // Cleanup! + if VERBOSE do fmt.printf("Struct before cleanup:\n%#v\n", output) + + // Stacking tracks to the left + for &line, l in output.table { + stacking_index := first_channel_index + for &field, f in line[first_channel_index:last_channel_index+1] { + if field != "" { + line[stacking_index] = field + stacking_index += 1 } } - // Advancing state machine and finishing off divs n stuff - switch state { - case .TITLE: - if line_index==1 { - state = .INFO - time_in_state = 0 - } - case .INFO: - if blank_lines > 1 { - total_output = append(total_output, " \n \n") - state = .HEADER - time_in_state = 0 - } - case .HEADER: - if blank_lines > 0 { - state = .BODY - time_in_state = 0 - total_output = append(total_output, " \n \n \n") - } - case .BODY: - total_output = append(total_output, " \n") + for &field, f in line[stacking_index:last_channel_index+1] { + field = "" } - if there_was_content_in_line { - blank_lines = 0 - } else { - blank_lines += 1 + } + + // Cleaning out unused columns + touched := make([]bool, output.column_count, context.temp_allocator) + for line, l in output.table { + for field, f in line { + if touched[f] do continue + if field != "" { + touched[f] = true + } + } + } + for &line, l in output.table { + stacking_index := 0 + for &field, f in line { + if touched[f] { + line[stacking_index] = field + stacking_index += 1 + } + } + for &field, f in line[stacking_index:] { + field = "" } - //fmt.printf("\n") - line_index += 1 - time_in_state += 1 } - total_output = append(total_output, PART_END) + stacking_index := 0 + for &field, f in output.header { + if touched[f] { + output.header[stacking_index] = field + stacking_index += 1 + } + } + for &field, f in output.header[stacking_index:] { + field = "" + } + new_column_count := 0 + for b in touched { + if b do new_column_count+=1 + } + output.column_count = new_column_count + + if VERBOSE do fmt.printf("Struct before output:\n%#v\n", output) + + return output, true +} + - output_file_name := fmt.aprintf("{}_Knekt.html", os.args[1][:len(os.args[1])-4]) - os.remove(output_file_name) - output_file_handle, _ := os.open(output_file_name, os.O_CREATE | os.O_RDWR, 0o777) - os.write_string(output_file_handle, total_output) - os.close(output_file_handle) +render :: proc(report : Report, path : string) { + // Now we output the HTML. + + builder := strings.builder_make(context.temp_allocator) + + strings.write_string(&builder, PART_ONE) + strings.write_string(&builder, report.title) + strings.write_string(&builder, " - Lydrapport") + strings.write_string(&builder, PART_TWO) + + + for line, l in report.info_lines { + strings.write_string(&builder, "

") + strings.write_string(&builder, line.field) + strings.write_string(&builder, " ") + strings.write_string(&builder, line.entry) + strings.write_string(&builder, "

\n") + } + strings.write_string(&builder, " \n \n
{}{}{}{}{}
\n \n \n") + + for field, f in report.header[:report.column_count] { + if f != report.tc_column_index { + strings.write_string(&builder, " \n") + } - fmt.printf("Wrote file: {}\n", output_file_name) + strings.write_string(&builder, " \n \n \n") - /*fmt.printf("\n\n\nTOTAL OUTPUT:\n\n\n") - fmt.println(total_output)*/ + for line, l in report.table { + strings.write_string(&builder, " \n") + for field, f in line[:report.column_count] { + strings.write_string(&builder, " \n") + + } + strings.write_string(&builder, " \n") + } + + strings.write_string(&builder, PART_END) + + output_text := strings.to_string(builder) + os.write_entire_file(path, transmute([]u8)output_text) } -append :: proc(input, to_add: string) -> string { - output := strings.concatenate({input, to_add}) - //delete(to_add) - //delete(input) - return output + +Device :: enum { + UNSET, + ZOOM, + SOUND_DEVICES, } -states :: enum { +Stages :: enum { TITLE, INFO, HEADER, diff --git a/parts/end.html b/parts/end.html index f964978..cc51027 100755 --- a/parts/end.html +++ b/parts/end.html @@ -1,33 +1,46 @@ - -
") + } else { + strings.write_string(&builder, " ") + } + strings.write_string(&builder, field) + strings.write_string(&builder, "
") + strings.write_string(&builder, field) + strings.write_string(&builder, "
- + + - diff --git a/parts/start2.html b/parts/start2.html index bd0021e..bd43709 100755 --- a/parts/start2.html +++ b/parts/start2.html @@ -98,6 +98,6 @@

Lydrapport

-
Version 0.2
- -
\ No newline at end of file +
Version 0.5
+
+
-- cgit v1.2.1