# Copyright (C) Dominik Picheta. All rights reserved. # BSD License. Look at license.txt for more info. # # Rough rules/philosophy for the messages that Nimble displays are the following: # - Green is only shown when the requested operation is successful. # - Blue can be used to emphasise certain keywords, for example actions such # as "Downloading" or "Reading". # - Red is used when the requested operation fails with an error. # - Yellow is used for warnings. # # - Dim for LowPriority. # - Bright for HighPriority. # - Normal for MediumPriority. import logging, terminal, sets, strutils type CLI* = ref object level: Priority warnings: HashSet[(string, string)] suppressionCount: int ## Amount of messages which were not shown. Priority* = enum DebugPriority, LowPriority, MediumPriority, HighPriority DisplayType* = enum Error, Warning, Message, Success ForcePrompt* = enum dontForcePrompt, forcePromptYes, forcePromptNo const longestCategory = len("Downloading") foregrounds: array[Error .. Success, ForegroundColor] = [fgRed, fgYellow, fgCyan, fgGreen] styles: array[DebugPriority .. HighPriority, set[Style]] = [{styleDim}, {styleDim}, {}, {styleBright}] proc newCLI(): CLI = result = CLI( level: HighPriority, warnings: initSet[(string, string)](), suppressionCount: 0 ) var globalCLI = newCLI() proc calculateCategoryOffset(category: string): int = assert category.len <= longestCategory return longestCategory - category.len proc displayCategory(category: string, displayType: DisplayType, priority: Priority) = # Calculate how much the `category` must be offset to align along a center # line. let offset = calculateCategoryOffset(category) # Display the category. if priority != DebugPriority: setForegroundColor(stdout, foregrounds[displayType]) writeStyled("$1$2 " % [repeatChar(offset), category], styles[priority]) resetAttributes() proc displayLine(category, line: string, displayType: DisplayType, priority: Priority) = displayCategory(category, displayType, priority) # Display the message. echo(line) proc display*(category, msg: string, displayType = Message, priority = MediumPriority) = # Multiple warnings containing the same messages should not be shown. let warningPair = (category, msg) if displayType == Warning: if warningPair in globalCLI.warnings: return else: globalCLI.warnings.incl(warningPair) # Suppress this message if its priority isn't high enough. # TODO: Per-priority suppression counts? if priority < globalCLI.level: if priority != DebugPriority: globalCLI.suppressionCount.inc return # Display each line in the message. var i = 0 for line in msg.splitLines(): if len(line) == 0: continue displayLine(if i == 0: category else: "...", line, displayType, priority) i.inc proc displayDebug*(category, msg: string) = ## Convenience for displaying debug messages. display(category, msg, priority = DebugPriority) proc displayDebug*(msg: string) = ## Convenience for displaying debug messages with a default category. displayDebug("Debug:", msg) proc displayTip*() = ## Called just before Nimble exits. Shows some tips for the user, for example ## the amount of messages that were suppressed and how to show them. if globalCLI.suppressionCount > 0: let msg = "$1 messages have been suppressed, use --verbose to show them." % $globalCLI.suppressionCount display("Tip:", msg, Warning, HighPriority) proc prompt*(forcePrompts: ForcePrompt, question: string): bool = case forcePrompts of forcePromptYes: display("Prompt:", question & " -> [forced yes]", Warning, HighPriority) return true of forcePromptNo: display("Prompt:", question & " -> [forced no]", Warning, HighPriority) return false of dontForcePrompt: display("Prompt:", question & " [y/N]", Warning, HighPriority) displayCategory("Answer:", Warning, HighPriority) let yn = stdin.readLine() case yn.normalize of "y", "yes": return true of "n", "no": return false else: return false proc promptCustom*(question, default: string): string = if default == "": display("Prompt:", question, Warning, HighPriority) displayCategory("Answer:", Warning, HighPriority) let user = stdin.readLine() if user.len == 0: return promptCustom(question, default) else: return user else: display("Prompt:", question & " [" & default & "]", Warning, HighPriority) displayCategory("Answer:", Warning, HighPriority) let user = stdin.readLine() if user == "": return default else: return user proc setVerbosity*(level: Priority) = globalCLI.level = level when isMainModule: display("Reading", "config file at /Users/dom/.config/nimble/nimble.ini", priority = LowPriority) display("Reading", "official package list", priority = LowPriority) display("Downloading", "daemonize v0.0.2 using Git", priority = HighPriority) display("Warning", "dashes in package names will be deprecated", Warning, priority = HighPriority) display("Error", """Unable to read package info for /Users/dom/.nimble/pkgs/nimble-0.7.11 Reading as ini file failed with: Invalid section: . Evaluating as NimScript file failed with: Users/dom/.nimble/pkgs/nimble-0.7.11/nimble.nimble(3, 23) Error: cannot open 'src/nimblepkg/common'. """, Error, HighPriority)