From f1fec98524e86fd4e8e8dfbcdf94806861545cc7 Mon Sep 17 00:00:00 2001 From: rexim Date: Mon, 7 Oct 2024 09:29:11 +0700 Subject: [PATCH] Assign `funcs` var in `main` func to break the cyclic initialization --- markut.go | 775 +++++++++++++++++++++++++++--------------------------- 1 file changed, 390 insertions(+), 385 deletions(-) diff --git a/markut.go b/markut.go index 269bc14..95bd5cf 100644 --- a/markut.go +++ b/markut.go @@ -226,376 +226,7 @@ type Func struct { Run func(context *EvalContext, command string, token Token) bool } -var funcs = map[string]Func{ - "chat": { - Description: "Load a chat log file generated by https://www.twitchchatdownloader.com/$SPOILER$ which is going to be used by the subsequent `chunk` func calls to include certain messages into the subtitles generated by the `markut chat` subcommand. There could be only one loaded chat log at a time. Repeated calls to the `chat` func replace the currently loaded chat log with another one. The already defined chunks keep the copy of the logs that were loaded at the time of their definition.", - Signature: " --", - Category: "Chat", - Run: func(context *EvalContext, command string, token Token) bool { - args, err := context.typeCheckArgs(token.Loc, TokenString) - if err != nil { - fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) - fmt.Printf("%s\n", err) - return false - } - path := args[0] - context.chatLog, err = loadTwitchChatDownloaderCSVButParseManually(string(path.Text)) - if err != nil { - fmt.Printf("%s: ERROR: could not load the chat logs: %s\n", path.Loc, err) - return false - } - return true - }, - }, - "chat_offset": { - Description: "Offsets the timestamps of the currently loaded chat log$SPOILER$ by removing all the messages between `start` and `end` Timestamps", - Category: "Chat", - Signature: " --", - Run: func(context *EvalContext, command string, token Token) bool { - // // TODO: this check does not make any sense when there are several chat commands - // if len(context.chunks) > 0 { - // fmt.Printf("%s: ERROR: chat offset should be applied after `chat` commands but before any `chunks` commands. This is due to `chunk` commands making copies of the chat slices that are not affected by the consequent chat offsets\n", token.Loc); - // return false; - // } - - args, err := context.typeCheckArgs(token.Loc, TokenTimestamp, TokenTimestamp) - if err != nil { - fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) - fmt.Printf("%s\n", err) - return false - } - - start := args[1] - end := args[0] - - if start.Timestamp < 0 { - fmt.Printf("%s: ERROR: the start of the chat offset is negative %s\n", start.Loc, millisToTs(start.Timestamp)); - return false - } - - if end.Timestamp < 0 { - fmt.Printf("%s: ERROR: the end of the chat offset is negative %s\n", end.Loc, millisToTs(end.Timestamp)); - return false - } - - if start.Timestamp > end.Timestamp { - fmt.Printf("%s: ERROR: the end of the chat offset %s is earlier than its start %s\n", end.Loc, millisToTs(end.Timestamp), millisToTs(start.Timestamp)); - fmt.Printf("%s: NOTE: the start is located here\n", start.Loc); - return false - } - - chatLen := len(context.chatLog) - if chatLen > 0 { - last := context.chatLog[chatLen-1].TimeOffset - before := sliceChatLog(context.chatLog, 0, start.Timestamp) - after := sliceChatLog(context.chatLog, end.Timestamp, last) - delta := end.Timestamp - start.Timestamp - for i := range after { - after[i].TimeOffset -= delta - } - context.chatLog = append(before, after...) - } - - return true - }, - }, - "no_chat": { - Description: "Clears out the current loaded chat log$SPOILER$ as if nothing is loaded", - Category: "Chat", - Signature: "--", - Run: func(context *EvalContext, command string, token Token) bool { - context.chatLog = []ChatMessage{} - return true - }, - }, - "chunk": { - Description: "Define a chunk$SPOILER$ between `start` and `end` timestamp for the current input defined by the `input` func", - Category: "Chunk", - Signature: " --", - Run: func(context *EvalContext, command string, token Token) bool { - args, err := context.typeCheckArgs(token.Loc, TokenTimestamp, TokenTimestamp) - if err != nil { - fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) - fmt.Printf("%s\n", err) - return false - } - - start := args[1] - end := args[0] - - if start.Timestamp < 0 { - fmt.Printf("%s: ERROR: the start of the chunk is negative %s\n", start.Loc, millisToTs(start.Timestamp)); - return false - } - - if end.Timestamp < 0 { - fmt.Printf("%s: ERROR: the end of the chunk is negative %s\n", end.Loc, millisToTs(end.Timestamp)); - return false - } - - if start.Timestamp > end.Timestamp { - fmt.Printf("%s: ERROR: the end of the chunk %s is earlier than its start %s\n", end.Loc, millisToTs(end.Timestamp), millisToTs(start.Timestamp)); - fmt.Printf("%s: NOTE: the start is located here\n", start.Loc); - return false - } - - chunk := Chunk{ - Loc: token.Loc, - Start: start.Timestamp, - End: end.Timestamp, - InputPath: context.inputPath, - ChatLog: sliceChatLog(context.chatLog, start.Timestamp, end.Timestamp), - } - - context.chunks = append(context.chunks, chunk) - - for _, chapter := range context.chapStack { - if chapter.Timestamp < chunk.Start || chunk.End < chapter.Timestamp { - fmt.Printf("%s: ERROR: the timestamp %s of chapter \"%s\" is outside of the the current chunk\n", chapter.Loc, millisToTs(chapter.Timestamp), chapter.Label) - fmt.Printf("%s: NOTE: which starts at %s\n", start.Loc, millisToTs(start.Timestamp)) - fmt.Printf("%s: NOTE: and ends at %s\n", end.Loc, millisToTs(end.Timestamp)) - return false - } - - context.chapters = append(context.chapters, Chapter{ - Loc: chapter.Loc, - Timestamp: chapter.Timestamp - chunk.Start + context.chapOffset, - Label: chapter.Label, - }) - } - - context.chapOffset += chunk.End - chunk.Start - - context.chapStack = []Chapter{} - return true - }, - - }, - "blur": { - Description: "Blur the last defined chunk$SPOILER$. Useful for bluring out sensitive information.", - Signature: "--", - Category: "Chunk", - Run: func(context *EvalContext, command string, token Token) bool { - if len(context.chunks) == 0 { - fmt.Printf("%s: ERROR: no chunks defined for a blur\n", token.Loc) - return false - } - context.chunks[len(context.chunks)-1].Blur = true - return true - }, - }, - "removed": { - Description: "Remove the last defined chunk$SPOILER$. Useful for disabling a certain chunk, so you can reenable it later if needed.", - Signature: "--", - Category: "Chunk", - Run: func(context *EvalContext, command string, token Token) bool { - if len(context.chunks) == 0 { - fmt.Printf("%s: ERROR: no chunks defined for removal\n", token.Loc) - return false - } - context.chunks = context.chunks[:len(context.chunks)-1] - return true - }, - }, - "unfinished": { - Description: "Mark the last defined chunk as unfinished$SPOILER$. This is used by the `markut watch` subcommand. `markut watch` does not render any unfinished chunks and keeps monitoring the MARKUT file until there is no unfinished chunks.", - Signature: "--", - Category: "Chunk", - Run: func(context *EvalContext, command string, token Token) bool { - if len(context.chunks) == 0 { - fmt.Printf("%s: ERROR: no chunks defined for marking as unfinished\n", token.Loc) - return false - } - context.chunks[len(context.chunks)-1].Unfinished = true - return true - }, - }, - "video_codec": { - Description: "Set the value of the output video codec flag (-c:v). Default is \""+DefaultVideoCodec+"\".", - Signature: " --", - Category: "FFmpeg Arguments", - Run: func(context *EvalContext, command string, token Token) bool { - args, err := context.typeCheckArgs(token.Loc, TokenString) - if err != nil { - fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) - fmt.Printf("%s\n", err) - return false - } - context.VideoCodec = string(args[0].Text) - return true; - }, - }, - "video_bitrate": { - Description: "Set the value of the output video bitrate flag (-vb). Default is \""+DefaultVideoBitrate+"\".", - Signature: " --", - Category: "FFmpeg Arguments", - Run: func(context *EvalContext, command string, token Token) bool { - args, err := context.typeCheckArgs(token.Loc, TokenString) - if err != nil { - fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) - fmt.Printf("%s\n", err) - return false - } - context.VideoBitrate = string(args[0].Text) - return true; - }, - }, - "audio_codec": { - Description: "Set the value of the output audio codec flag (-c:a). Default is \""+DefaultAudioCodec+"\".", - Signature: " --", - Category: "FFmpeg Arguments", - Run: func(context *EvalContext, command string, token Token) bool { - args, err := context.typeCheckArgs(token.Loc, TokenString) - if err != nil { - fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) - fmt.Printf("%s\n", err) - return false - } - context.AudioCodec = string(args[0].Text) - return true; - }, - }, - "audio_bitrate": { - Description: "Set the value of the output audio bitrate flag (-ab). Default is \""+DefaultAudioBitrate+"\".", - Signature: " --", - Category: "FFmpeg Arguments", - Run: func(context *EvalContext, command string, token Token) bool { - args, err := context.typeCheckArgs(token.Loc, TokenString) - if err != nil { - fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) - fmt.Printf("%s\n", err) - return false - } - context.AudioBitrate = string(args[0].Text) - return true; - }, - }, - "outf": { - Description: "Append extra output flag", - Signature: " --", - Category: "FFmpeg Arguments", - Run: func(context *EvalContext, command string, token Token) bool { - args, err := context.typeCheckArgs(token.Loc, TokenString) - if err != nil { - fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) - fmt.Printf("%s\n", err) - return false - } - outFlag := args[0] - context.ExtraOutFlags = append(context.ExtraOutFlags, string(outFlag.Text)) - return true; - }, - }, - "inf": { - Description: "Append extra input flag", - Signature: " --", - Category: "FFmpeg Arguments", - Run: func(context *EvalContext, command string, token Token) bool { - args, err := context.typeCheckArgs(token.Loc, TokenString) - if err != nil { - fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) - fmt.Printf("%s\n", err) - return false - } - inFlag := args[0] - context.ExtraInFlags = append(context.ExtraInFlags, string(inFlag.Text)) - return true; - }, - }, - "over": { - Description: "Copy the argument below the top fo the stack on top", - Signature: " -- ", - Category: "Stack", - Run: func(context *EvalContext, command string, token Token) bool { - arity := 2 - if len(context.argsStack) < arity { - fmt.Printf("%s: Expected %d arguments but got %d", token.Loc, arity, len(context.argsStack)); - return false; - } - n := len(context.argsStack) - context.argsStack = append(context.argsStack, context.argsStack[n-2]); - return true; - }, - }, - "dup": { - Description: "Duplicate the argument on top of the stack", - Signature: " -- ", - Category: "Stack", - Run: func(context *EvalContext, command string, token Token) bool { - arity := 1 - if len(context.argsStack) < arity { - fmt.Printf("%s: Expected %d arguments but got %d", token.Loc, arity, len(context.argsStack)); - return false; - } - n := len(context.argsStack) - // TODO: the location of the dupped value should be the location of the "dup" token - context.argsStack = append(context.argsStack, context.argsStack[n-1]) - return true - }, - }, - "input": { - Description: "Set the current input for the consequent chunks.", - Category: "Misc", - Signature: " --", - Run: func(context *EvalContext, command string, token Token) bool { - args, err := context.typeCheckArgs(token.Loc, TokenString) - if err != nil { - fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) - fmt.Printf("%s\n", err) - return false - } - path := args[0] - if len(path.Text) == 0 { - fmt.Printf("%s: ERROR: cannot set empty input path\n", path.Loc); - return false - } - context.inputPath = string(path.Text) - return true - }, - }, - "chapter": { - Description: "Define a new YouTube chapter for within a chunk for `markut summary` command.", - Category: "Misc", - Signature: " --", - Run: func(context *EvalContext, command string, token Token) bool { - args, err := context.typeCheckArgs(token.Loc, TokenString, TokenTimestamp) - if err != nil { - fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) - fmt.Printf("%s\n", err) - return false - } - context.chapStack = append(context.chapStack, Chapter{ - Loc: args[1].Loc, - Label: string(args[0].Text), - Timestamp: args[1].Timestamp, - }) - return true - }, - }, - "cut": { - Description: "Define a new cut for `markut cut` command.", - Category: "Misc", - Signature: " --", - Run: func(context *EvalContext, command string, token Token) bool { - args, err := context.typeCheckArgs(token.Loc, TokenTimestamp) - if err != nil { - fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) - fmt.Printf("%s\n", err) - return false - } - pad := args[0] - if len(context.chunks) == 0 { - fmt.Printf("%s: ERROR: no chunks defined for a cut\n", token.Loc) - return false - } - context.cuts = append(context.cuts, Cut{ - chunk: len(context.chunks) - 1, - pad: pad.Timestamp, - }) - return true - }, - }, -} +var funcs map[string]Func; // This function is compatible with the format https://www.twitchchatdownloader.com/ generates. // It does not use encoding/csv because that website somehow generates unparsable garbage. @@ -696,21 +327,6 @@ func (context *EvalContext) evalMarkutFile(path string) bool { context.argsStack = append(context.argsStack, token) case TokenSymbol: command := string(token.Text) - // TODO: Moving `include` func to funcs map, create an initialization loop. Figure out how to fix that. - if command == "include" { - args, err := context.typeCheckArgs(token.Loc, TokenString) - if err != nil { - fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) - fmt.Printf("%s\n", err) - return false - } - path := args[0] - if !context.evalMarkutFile(string(path.Text)) { - return false - } - return true - } - f, ok := funcs[command]; if !ok { fmt.Printf("%s: ERROR: Unknown command %s\n", token.Loc, command) @@ -1388,6 +1004,395 @@ func main() { os.Exit(1) } + funcs = map[string]Func{ + "chat": { + Description: "Load a chat log file generated by https://www.twitchchatdownloader.com/$SPOILER$ which is going to be used by the subsequent `chunk` func calls to include certain messages into the subtitles generated by the `markut chat` subcommand. There could be only one loaded chat log at a time. Repeated calls to the `chat` func replace the currently loaded chat log with another one. The already defined chunks keep the copy of the logs that were loaded at the time of their definition.", + Signature: " --", + Category: "Chat", + Run: func(context *EvalContext, command string, token Token) bool { + args, err := context.typeCheckArgs(token.Loc, TokenString) + if err != nil { + fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) + fmt.Printf("%s\n", err) + return false + } + path := args[0] + context.chatLog, err = loadTwitchChatDownloaderCSVButParseManually(string(path.Text)) + if err != nil { + fmt.Printf("%s: ERROR: could not load the chat logs: %s\n", path.Loc, err) + return false + } + return true + }, + }, + "chat_offset": { + Description: "Offsets the timestamps of the currently loaded chat log$SPOILER$ by removing all the messages between `start` and `end` Timestamps", + Category: "Chat", + Signature: " --", + Run: func(context *EvalContext, command string, token Token) bool { + // // TODO: this check does not make any sense when there are several chat commands + // if len(context.chunks) > 0 { + // fmt.Printf("%s: ERROR: chat offset should be applied after `chat` commands but before any `chunks` commands. This is due to `chunk` commands making copies of the chat slices that are not affected by the consequent chat offsets\n", token.Loc); + // return false; + // } + + args, err := context.typeCheckArgs(token.Loc, TokenTimestamp, TokenTimestamp) + if err != nil { + fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) + fmt.Printf("%s\n", err) + return false + } + + start := args[1] + end := args[0] + + if start.Timestamp < 0 { + fmt.Printf("%s: ERROR: the start of the chat offset is negative %s\n", start.Loc, millisToTs(start.Timestamp)); + return false + } + + if end.Timestamp < 0 { + fmt.Printf("%s: ERROR: the end of the chat offset is negative %s\n", end.Loc, millisToTs(end.Timestamp)); + return false + } + + if start.Timestamp > end.Timestamp { + fmt.Printf("%s: ERROR: the end of the chat offset %s is earlier than its start %s\n", end.Loc, millisToTs(end.Timestamp), millisToTs(start.Timestamp)); + fmt.Printf("%s: NOTE: the start is located here\n", start.Loc); + return false + } + + chatLen := len(context.chatLog) + if chatLen > 0 { + last := context.chatLog[chatLen-1].TimeOffset + before := sliceChatLog(context.chatLog, 0, start.Timestamp) + after := sliceChatLog(context.chatLog, end.Timestamp, last) + delta := end.Timestamp - start.Timestamp + for i := range after { + after[i].TimeOffset -= delta + } + context.chatLog = append(before, after...) + } + + return true + }, + }, + "no_chat": { + Description: "Clears out the current loaded chat log$SPOILER$ as if nothing is loaded", + Category: "Chat", + Signature: "--", + Run: func(context *EvalContext, command string, token Token) bool { + context.chatLog = []ChatMessage{} + return true + }, + }, + "chunk": { + Description: "Define a chunk$SPOILER$ between `start` and `end` timestamp for the current input defined by the `input` func", + Category: "Chunk", + Signature: " --", + Run: func(context *EvalContext, command string, token Token) bool { + args, err := context.typeCheckArgs(token.Loc, TokenTimestamp, TokenTimestamp) + if err != nil { + fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) + fmt.Printf("%s\n", err) + return false + } + + start := args[1] + end := args[0] + + if start.Timestamp < 0 { + fmt.Printf("%s: ERROR: the start of the chunk is negative %s\n", start.Loc, millisToTs(start.Timestamp)); + return false + } + + if end.Timestamp < 0 { + fmt.Printf("%s: ERROR: the end of the chunk is negative %s\n", end.Loc, millisToTs(end.Timestamp)); + return false + } + + if start.Timestamp > end.Timestamp { + fmt.Printf("%s: ERROR: the end of the chunk %s is earlier than its start %s\n", end.Loc, millisToTs(end.Timestamp), millisToTs(start.Timestamp)); + fmt.Printf("%s: NOTE: the start is located here\n", start.Loc); + return false + } + + chunk := Chunk{ + Loc: token.Loc, + Start: start.Timestamp, + End: end.Timestamp, + InputPath: context.inputPath, + ChatLog: sliceChatLog(context.chatLog, start.Timestamp, end.Timestamp), + } + + context.chunks = append(context.chunks, chunk) + + for _, chapter := range context.chapStack { + if chapter.Timestamp < chunk.Start || chunk.End < chapter.Timestamp { + fmt.Printf("%s: ERROR: the timestamp %s of chapter \"%s\" is outside of the the current chunk\n", chapter.Loc, millisToTs(chapter.Timestamp), chapter.Label) + fmt.Printf("%s: NOTE: which starts at %s\n", start.Loc, millisToTs(start.Timestamp)) + fmt.Printf("%s: NOTE: and ends at %s\n", end.Loc, millisToTs(end.Timestamp)) + return false + } + + context.chapters = append(context.chapters, Chapter{ + Loc: chapter.Loc, + Timestamp: chapter.Timestamp - chunk.Start + context.chapOffset, + Label: chapter.Label, + }) + } + + context.chapOffset += chunk.End - chunk.Start + + context.chapStack = []Chapter{} + return true + }, + + }, + "blur": { + Description: "Blur the last defined chunk$SPOILER$. Useful for bluring out sensitive information.", + Signature: "--", + Category: "Chunk", + Run: func(context *EvalContext, command string, token Token) bool { + if len(context.chunks) == 0 { + fmt.Printf("%s: ERROR: no chunks defined for a blur\n", token.Loc) + return false + } + context.chunks[len(context.chunks)-1].Blur = true + return true + }, + }, + "removed": { + Description: "Remove the last defined chunk$SPOILER$. Useful for disabling a certain chunk, so you can reenable it later if needed.", + Signature: "--", + Category: "Chunk", + Run: func(context *EvalContext, command string, token Token) bool { + if len(context.chunks) == 0 { + fmt.Printf("%s: ERROR: no chunks defined for removal\n", token.Loc) + return false + } + context.chunks = context.chunks[:len(context.chunks)-1] + return true + }, + }, + "unfinished": { + Description: "Mark the last defined chunk as unfinished$SPOILER$. This is used by the `markut watch` subcommand. `markut watch` does not render any unfinished chunks and keeps monitoring the MARKUT file until there is no unfinished chunks.", + Signature: "--", + Category: "Chunk", + Run: func(context *EvalContext, command string, token Token) bool { + if len(context.chunks) == 0 { + fmt.Printf("%s: ERROR: no chunks defined for marking as unfinished\n", token.Loc) + return false + } + context.chunks[len(context.chunks)-1].Unfinished = true + return true + }, + }, + "video_codec": { + Description: "Set the value of the output video codec flag (-c:v). Default is \""+DefaultVideoCodec+"\".", + Signature: " --", + Category: "FFmpeg Arguments", + Run: func(context *EvalContext, command string, token Token) bool { + args, err := context.typeCheckArgs(token.Loc, TokenString) + if err != nil { + fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) + fmt.Printf("%s\n", err) + return false + } + context.VideoCodec = string(args[0].Text) + return true; + }, + }, + "video_bitrate": { + Description: "Set the value of the output video bitrate flag (-vb). Default is \""+DefaultVideoBitrate+"\".", + Signature: " --", + Category: "FFmpeg Arguments", + Run: func(context *EvalContext, command string, token Token) bool { + args, err := context.typeCheckArgs(token.Loc, TokenString) + if err != nil { + fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) + fmt.Printf("%s\n", err) + return false + } + context.VideoBitrate = string(args[0].Text) + return true; + }, + }, + "audio_codec": { + Description: "Set the value of the output audio codec flag (-c:a). Default is \""+DefaultAudioCodec+"\".", + Signature: " --", + Category: "FFmpeg Arguments", + Run: func(context *EvalContext, command string, token Token) bool { + args, err := context.typeCheckArgs(token.Loc, TokenString) + if err != nil { + fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) + fmt.Printf("%s\n", err) + return false + } + context.AudioCodec = string(args[0].Text) + return true; + }, + }, + "audio_bitrate": { + Description: "Set the value of the output audio bitrate flag (-ab). Default is \""+DefaultAudioBitrate+"\".", + Signature: " --", + Category: "FFmpeg Arguments", + Run: func(context *EvalContext, command string, token Token) bool { + args, err := context.typeCheckArgs(token.Loc, TokenString) + if err != nil { + fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) + fmt.Printf("%s\n", err) + return false + } + context.AudioBitrate = string(args[0].Text) + return true; + }, + }, + "outf": { + Description: "Append extra output flag", + Signature: " --", + Category: "FFmpeg Arguments", + Run: func(context *EvalContext, command string, token Token) bool { + args, err := context.typeCheckArgs(token.Loc, TokenString) + if err != nil { + fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) + fmt.Printf("%s\n", err) + return false + } + outFlag := args[0] + context.ExtraOutFlags = append(context.ExtraOutFlags, string(outFlag.Text)) + return true; + }, + }, + "inf": { + Description: "Append extra input flag", + Signature: " --", + Category: "FFmpeg Arguments", + Run: func(context *EvalContext, command string, token Token) bool { + args, err := context.typeCheckArgs(token.Loc, TokenString) + if err != nil { + fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) + fmt.Printf("%s\n", err) + return false + } + inFlag := args[0] + context.ExtraInFlags = append(context.ExtraInFlags, string(inFlag.Text)) + return true; + }, + }, + "over": { + Description: "Copy the argument below the top fo the stack on top", + Signature: " -- ", + Category: "Stack", + Run: func(context *EvalContext, command string, token Token) bool { + arity := 2 + if len(context.argsStack) < arity { + fmt.Printf("%s: Expected %d arguments but got %d", token.Loc, arity, len(context.argsStack)); + return false; + } + n := len(context.argsStack) + context.argsStack = append(context.argsStack, context.argsStack[n-2]); + return true; + }, + }, + "dup": { + Description: "Duplicate the argument on top of the stack", + Signature: " -- ", + Category: "Stack", + Run: func(context *EvalContext, command string, token Token) bool { + arity := 1 + if len(context.argsStack) < arity { + fmt.Printf("%s: Expected %d arguments but got %d", token.Loc, arity, len(context.argsStack)); + return false; + } + n := len(context.argsStack) + // TODO: the location of the dupped value should be the location of the "dup" token + context.argsStack = append(context.argsStack, context.argsStack[n-1]) + return true + }, + }, + "input": { + Description: "Set the current input for the consequent chunks.", + Category: "Misc", + Signature: " --", + Run: func(context *EvalContext, command string, token Token) bool { + args, err := context.typeCheckArgs(token.Loc, TokenString) + if err != nil { + fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) + fmt.Printf("%s\n", err) + return false + } + path := args[0] + if len(path.Text) == 0 { + fmt.Printf("%s: ERROR: cannot set empty input path\n", path.Loc); + return false + } + context.inputPath = string(path.Text) + return true + }, + }, + "chapter": { + Description: "Define a new YouTube chapter for within a chunk for `markut summary` command.", + Category: "Misc", + Signature: " --", + Run: func(context *EvalContext, command string, token Token) bool { + args, err := context.typeCheckArgs(token.Loc, TokenString, TokenTimestamp) + if err != nil { + fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) + fmt.Printf("%s\n", err) + return false + } + context.chapStack = append(context.chapStack, Chapter{ + Loc: args[1].Loc, + Label: string(args[0].Text), + Timestamp: args[1].Timestamp, + }) + return true + }, + }, + "cut": { + Description: "Define a new cut for `markut cut` command.", + Category: "Misc", + Signature: " --", + Run: func(context *EvalContext, command string, token Token) bool { + args, err := context.typeCheckArgs(token.Loc, TokenTimestamp) + if err != nil { + fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) + fmt.Printf("%s\n", err) + return false + } + pad := args[0] + if len(context.chunks) == 0 { + fmt.Printf("%s: ERROR: no chunks defined for a cut\n", token.Loc) + return false + } + context.cuts = append(context.cuts, Cut{ + chunk: len(context.chunks) - 1, + pad: pad.Timestamp, + }) + return true + }, + }, + "include": { + Description: "Include another MARKUT file", + Category: "Misc", + Signature: " --", + Run: func(context *EvalContext, command string, token Token) bool { + args, err := context.typeCheckArgs(token.Loc, TokenString) + if err != nil { + fmt.Printf("%s: ERROR: type check failed for %s\n", token.Loc, command) + fmt.Printf("%s\n", err) + return false + } + path := args[0] + if !context.evalMarkutFile(string(path.Text)) { + return false + } + return true + }, + }, + } + name := os.Args[1]; args := os.Args[2:]; subcommand, ok := Subcommands[name];