diff --git a/build-files.txt b/build-files.txt index ed0aaa791..e1ba691a1 100644 --- a/build-files.txt +++ b/build-files.txt @@ -34,12 +34,16 @@ source/dub/internal/sdlang/util.d source/dub/internal/undead/xml.d source/dub/internal/utils.d source/dub/internal/vibecompat/core/file.d -source/dub/internal/vibecompat/core/log.d source/dub/internal/vibecompat/data/json.d source/dub/internal/vibecompat/data/serialization.d source/dub/internal/vibecompat/data/utils.d source/dub/internal/vibecompat/inet/path.d source/dub/internal/vibecompat/inet/url.d +source/dub/internal/colorize/colors.d +source/dub/internal/colorize/cwrite.d +source/dub/internal/colorize/package.d +source/dub/internal/colorize/winterm.d +source/dub/internal/logging.d source/dub/package_.d source/dub/packagemanager.d source/dub/packagesupplier.d diff --git a/source/dub/commandline.d b/source/dub/commandline.d index e428d6156..671b715e7 100644 --- a/source/dub/commandline.d +++ b/source/dub/commandline.d @@ -12,9 +12,9 @@ import dub.dependency; import dub.dub; import dub.generators.generator; import dub.internal.vibecompat.core.file; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.data.json; import dub.internal.vibecompat.inet.path; +import dub.internal.logging; import dub.package_; import dub.packagemanager; import dub.packagesuppliers; @@ -165,6 +165,19 @@ struct CommandLineHandler { options.root_path = options.root_path.expandTilde.absolutePath.buildNormalizedPath; } + + final switch (options.colors_mode) with (options.colors) + { + case automatic: + // Use default determined in internal.logging.initLogging(). + break; + case on: + setLoggingColorsEnabled(true); // enable colors, no matter what + break; + case off: + setLoggingColorsEnabled(false); // disable colors, no matter what + break; + } } /** Get an instance of the requested command. @@ -398,6 +411,10 @@ int runDubCommandLine(string[] args) return [args0, "run", "-q", "--temp-build", "--single", file, "--"] ~ trailing; } + // Initialize the logging module, ensure that whether stdout/stderr are a TTY + // or not is detected in order to disable colors if the output isn't a console + initLogging(); + logDiagnostic("DUB version %s", getDUBVersion()); version(Windows){ @@ -536,6 +553,8 @@ struct CommonOptions { bool help, annotate, bare; string[] registry_urls; string root_path; + enum colors { automatic, on, off } // Style violation in support of invalid option error formatting. + colors colors_mode = colors.automatic; SkipPackageSuppliers skipRegistry = SkipPackageSuppliers.none; PlacementLocation placementLocation = PlacementLocation.user; @@ -563,6 +582,13 @@ struct CommonOptions { args.getopt("q|quiet", &quiet, ["Only print warnings and errors"]); args.getopt("verror", &verror, ["Only print errors"]); args.getopt("vquiet", &vquiet, ["Print no messages"]); + args.getopt("colors", &colors_mode, [ + "Configure colored output. Accepted values:", + " automatic: Colored output on console/terminal,", + " unless NO_COLOR is set and non-empty (default)", + " on: Force colors enabled", + " off: Force colors disabled" + ]); args.getopt("cache", &placementLocation, ["Puts any fetched packages in the specified location [local|system|user]."]); version_ = args.hasAppVersion; @@ -1762,7 +1788,7 @@ class UpgradeCommand : Command { enforceUsage(app_args.length == 0, "Unexpected application arguments."); enforceUsage(!m_verify, "--verify is not yet implemented."); enforce(loadCwdPackage(dub, true), "Failed to load package."); - logInfo("Upgrading project in %s", dub.projectPath.toNativeString()); + logInfo("Upgrading", Color.cyan, "project in %s", dub.projectPath.toNativeString().color(Mode.bold)); auto options = UpgradeOptions.upgrade|UpgradeOptions.select; if (m_missingOnly) options &= ~UpgradeOptions.upgrade; if (m_prerelease) options |= UpgradeOptions.preRelease; @@ -1875,9 +1901,11 @@ class FetchCommand : FetchRemoveCommand { } else { try { dub.fetch(name, Dependency.any, location, fetchOpts); + logInfo("Finished", Color.green, "%s fetched", name.color(Mode.bold)); logInfo( "Please note that you need to use `dub run ` " ~ - "or add it to dependencies of your package to actually use/run it."); + "or add it to dependencies of your package to actually use/run it. " + ); } catch(Exception e){ logInfo("Getting a release version failed: %s", e.msg); @@ -2086,10 +2114,10 @@ class ListCommand : Command { const pname = pinfo.name; const pvlim = Dependency(pinfo.version_ == "" ? "*" : pinfo.version_); enforceUsage(app_args.length == 0, "The list command supports no application arguments."); - logInfo("Packages present in the system and known to dub:"); + logInfoNoTag("Packages present in the system and known to dub:"); foreach (p; dub.packageManager.getPackageIterator()) { if ((pname == "" || pname == p.name) && pvlim.matches(p.version_)) - logInfo(" %s %s: %s", p.name, p.version_, p.path.toNativeString()); + logInfo(" %s %s: %s", p.name.color(Mode.bold), p.version_, p.path.toNativeString()); } logInfo(""); return 0; @@ -2124,9 +2152,9 @@ class SearchCommand : Command { justify += (~justify & 3) + 1; // round to next multiple of 4 foreach (desc, matches; res) { - logInfo("==== %s ====", desc); + logInfoNoTag("==== %s ====", desc); foreach (m; matches) - logInfo("%s%s", leftJustify(m.name ~ " (" ~ m.version_ ~ ")", justify), m.description); + logInfoNoTag(" %s%s", leftJustify(m.name ~ " (" ~ m.version_ ~ ")", justify), m.description); } return 0; } diff --git a/source/dub/compilers/compiler.d b/source/dub/compilers/compiler.d index ff59b26c5..a8dacd013 100644 --- a/source/dub/compilers/compiler.d +++ b/source/dub/compilers/compiler.d @@ -11,9 +11,10 @@ public import dub.compilers.buildsettings; deprecated("Please `import dub.dependency : Dependency` instead") public import dub.dependency : Dependency; public import dub.platform : BuildPlatform, matchesSpecification; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.path; +import dub.internal.logging; + import std.algorithm; import std.array; import std.exception; diff --git a/source/dub/compilers/dmd.d b/source/dub/compilers/dmd.d index 7c0bb32a8..4224518e4 100644 --- a/source/dub/compilers/dmd.d +++ b/source/dub/compilers/dmd.d @@ -10,8 +10,8 @@ module dub.compilers.dmd; import dub.compilers.compiler; import dub.compilers.utils; import dub.internal.utils; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.path; +import dub.internal.logging; import std.algorithm; import std.array; diff --git a/source/dub/compilers/gdc.d b/source/dub/compilers/gdc.d index fd47d0136..82216dfe2 100644 --- a/source/dub/compilers/gdc.d +++ b/source/dub/compilers/gdc.d @@ -10,8 +10,8 @@ module dub.compilers.gdc; import dub.compilers.compiler; import dub.compilers.utils; import dub.internal.utils; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.path; +import dub.internal.logging; import std.algorithm; import std.array; diff --git a/source/dub/compilers/ldc.d b/source/dub/compilers/ldc.d index f37409dea..848760910 100644 --- a/source/dub/compilers/ldc.d +++ b/source/dub/compilers/ldc.d @@ -10,8 +10,8 @@ module dub.compilers.ldc; import dub.compilers.compiler; import dub.compilers.utils; import dub.internal.utils; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.path; +import dub.internal.logging; import std.algorithm; import std.array; diff --git a/source/dub/compilers/utils.d b/source/dub/compilers/utils.d index e04711348..19b1419f3 100644 --- a/source/dub/compilers/utils.d +++ b/source/dub/compilers/utils.d @@ -9,10 +9,10 @@ module dub.compilers.utils; import dub.compilers.buildsettings; import dub.platform : BuildPlatform, archCheck, compilerCheck, platformCheck; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.path; -import std.algorithm : canFind, endsWith, filter; +import dub.internal.logging; +import std.algorithm : canFind, endsWith, filter; /** Alters the build options to comply with the specified build requirements. diff --git a/source/dub/dependency.d b/source/dub/dependency.d index 678d7bafd..27288baa4 100644 --- a/source/dub/dependency.d +++ b/source/dub/dependency.d @@ -8,12 +8,12 @@ module dub.dependency; import dub.internal.utils; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.core.file; import dub.internal.vibecompat.data.json; import dub.internal.vibecompat.inet.path; import dub.package_; import dub.semver; +import dub.internal.logging; import std.algorithm; import std.array; diff --git a/source/dub/dependencyresolver.d b/source/dub/dependencyresolver.d index 8ec82cc73..5cb2e134a 100644 --- a/source/dub/dependencyresolver.d +++ b/source/dub/dependencyresolver.d @@ -8,7 +8,7 @@ module dub.dependencyresolver; import dub.dependency; -import dub.internal.vibecompat.core.log; +import dub.internal.logging; import std.algorithm : all, canFind, filter, map, sort; import std.array : appender, array, join; diff --git a/source/dub/dub.d b/source/dub/dub.d index f0a8466a4..964059a54 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -12,9 +12,9 @@ import dub.dependency; import dub.dependencyresolver; import dub.internal.utils; import dub.internal.vibecompat.core.file; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.data.json; import dub.internal.vibecompat.inet.url; +import dub.internal.logging; import dub.package_; import dub.packagemanager; import dub.packagesuppliers; @@ -549,19 +549,20 @@ class Dub { if (basename == rootbasename) continue; if (!m_project.selections.hasSelectedVersion(basename)) { - logInfo("Package %s would be selected with version %s.", - basename, ver); + logInfo("Upgrade", Color.cyan, + "Package %s would be selected with version %s", basename, ver); any = true; continue; } auto sver = m_project.selections.getSelectedVersion(basename); if (!sver.path.empty || !sver.repository.empty) continue; if (ver.version_ <= sver.version_) continue; - logInfo("Package %s would be upgraded from %s to %s.", - basename, sver, ver); + logInfo("Upgrade", Color.cyan, + "%s would be upgraded from %s to %s.", + basename.color(Mode.bold), sver, ver); any = true; } - if (any) logInfo("Use \"dub upgrade\" to perform those changes."); + if (any) logInfo("Use \"dub upgrade\" to perform those changes"); return; } @@ -727,7 +728,7 @@ class Dub { /// Cleans intermediate/cache files of the given package void cleanPackage(NativePath path) { - logInfo("Cleaning package at %s...", path.toNativeString()); + logInfo("Cleaning", Color.green, "package at %s", path.toNativeString().color(Mode.bold)); enforce(!Package.findPackageFile(path).empty, "No package found.", path.toNativeString()); // TODO: clear target files and copy files @@ -793,13 +794,13 @@ class Dub { packageId, ver, placement); return existing; } else { - logInfo("Removing %s %s to prepare replacement with a new version.", packageId, ver); + logInfo("Removing", Color.yellow, "%s %s to prepare replacement with a new version", packageId.color(Mode.bold), ver); if (!m_dryRun) m_packageManager.remove(existing); } } - if (reason.length) logInfo("Fetching %s %s (%s)...", packageId, ver, reason); - else logInfo("Fetching %s %s...", packageId, ver); + if (reason.length) logInfo("Fetching", Color.yellow, "%s %s (%s)", packageId.color(Mode.bold), ver, reason); + else logInfo("Fetching", Color.yellow, "%s %s", packageId.color(Mode.bold), ver); if (m_dryRun) return null; logDebug("Acquiring package zip file"); @@ -851,7 +852,7 @@ class Dub { */ void remove(in Package pack) { - logInfo("Removing %s in %s", pack.name, pack.path.toNativeString()); + logInfo("Removing", Color.yellow, "%s (in %s)", pack.name.color(Mode.bold), pack.path.toNativeString()); if (!m_dryRun) m_packageManager.remove(pack); } @@ -914,7 +915,6 @@ class Dub { foreach(pack; packages) { try { remove(pack); - logInfo("Removed %s, version %s.", package_id, pack.version_); } catch (Exception e) { logError("Failed to remove %s %s: %s", package_id, pack.version_, e.msg); logInfo("Continuing with other packages (if any)."); @@ -1141,7 +1141,7 @@ class Dub { } //Act smug to the user. - logInfo("Successfully created an empty project in '%s'.", path.toNativeString()); + logInfo("Success", Color.green, "created empty project in %s", path.toNativeString().color(Mode.bold)); } private void runCustomInitialization(NativePath path, string type, string[] runArgs) diff --git a/source/dub/generators/build.d b/source/dub/generators/build.d index 490b47d12..09ef0fa42 100644 --- a/source/dub/generators/build.d +++ b/source/dub/generators/build.d @@ -12,8 +12,8 @@ import dub.compilers.utils; import dub.generators.generator; import dub.internal.utils; import dub.internal.vibecompat.core.file; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.path; +import dub.internal.logging; import dub.package_; import dub.packagemanager; import dub.project; @@ -82,8 +82,10 @@ class BuildGenerator : ProjectGenerator { enforce(!(settings.rdmd && root_ti.buildSettings.targetType == TargetType.none), "Building package with target type \"none\" with rdmd is not supported yet."); - logInfo("Performing \"%s\" build using %s for %-(%s, %).", - settings.buildType, settings.platform.compilerBinary, settings.platform.architecture); + logInfo("Starting", Color.light_green, + "Performing \"%s\" build using %s for %-(%s, %).", + settings.buildType.color(Color.magenta), settings.platform.compilerBinary, + settings.platform.architecture); bool any_cached = false; @@ -152,7 +154,9 @@ class BuildGenerator : ProjectGenerator { buildTargetRec(m_project.rootPackage.name); if (any_cached) { - logInfo("To force a rebuild of up-to-date targets, run again with --force."); + logInfo("Finished", Color.green, + "To force a rebuild of up-to-date targets, run again with --force" + ); } } } @@ -203,7 +207,7 @@ class BuildGenerator : ProjectGenerator { // run post-build commands if (!cached && buildsettings.postBuildCommands.length) { - logInfo("Running post-build commands..."); + logInfo("Post-build", Color.light_green, "Running commands"); runBuildCommands(CommandType.postBuild, buildsettings.postBuildCommands, pack, m_project, settings, buildsettings, [["DUB_BUILD_PATH" : target_path.parentPath.toNativeString.absolutePath]]); } @@ -224,7 +228,8 @@ class BuildGenerator : ProjectGenerator { else target_path = pack.path ~ format(".dub/build/%s/", build_id); if (!settings.force && isUpToDate(target_path, buildsettings, settings, pack, packages, additional_dep_files)) { - logInfo("%s %s: target for configuration \"%s\" is up to date.", pack.name, pack.version_, config); + logInfo("Up-to-date", Color.green, "%s %s: target for configuration [%s] is up to date.", + pack.name.color(Mode.bold), pack.version_, config.color(Color.blue)); logDiagnostic("Using existing build in %s.", target_path.toNativeString()); target_binary_path = target_path ~ settings.compiler.getTargetFileName(buildsettings, settings.platform); if (!settings.tempBuild) @@ -239,10 +244,10 @@ class BuildGenerator : ProjectGenerator { return false; } - logInfo("%s %s: building configuration \"%s\"...", pack.name, pack.version_, config); + logInfo("Building", Color.light_green, "%s %s: building configuration [%s]", pack.name.color(Mode.bold), pack.version_, config.color(Color.blue)); if( buildsettings.preBuildCommands.length ){ - logInfo("Running pre-build commands..."); + logInfo("Pre-build", Color.light_green, "Running commands"); runBuildCommands(CommandType.preBuild, buildsettings.preBuildCommands, pack, m_project, settings, buildsettings); } @@ -304,11 +309,11 @@ class BuildGenerator : ProjectGenerator { flags ~= mainsrc.relativeTo(cwd).toNativeString(); if (buildsettings.preBuildCommands.length){ - logInfo("Running pre-build commands..."); + logInfo("Pre-build", Color.light_green, "Running commands"); runCommands(buildsettings.preBuildCommands); } - logInfo("%s %s: building configuration \"%s\"...", pack.name, pack.version_, config); + logInfo("Building", Color.light_green, "%s %s [%s]", pack.name.color(Mode.bold), pack.version_, config.color(Color.blue)); logInfo("Running rdmd..."); logDiagnostic("rdmd %s", join(flags, " ")); @@ -335,7 +340,7 @@ class BuildGenerator : ProjectGenerator { f = fp.toNativeString(); } - logInfo("%s %s: building configuration \"%s\"...", pack.name, pack.version_, config); + logInfo("Building", Color.light_green, "%s %s [%s]", pack.name.color(Mode.bold), pack.version_, config.color(Color.blue)); // make all target/import paths relative string makeRelative(string path) { @@ -362,7 +367,7 @@ class BuildGenerator : ProjectGenerator { } if( buildsettings.preBuildCommands.length ){ - logInfo("Running pre-build commands..."); + logInfo("Pre-build", Color.light_green, "Running commands"); runBuildCommands(CommandType.preBuild, buildsettings.preBuildCommands, pack, m_project, settings, buildsettings); } @@ -525,7 +530,7 @@ class BuildGenerator : ProjectGenerator { auto objs = new string[](srcs.walkLength); void compileSource(size_t i, string src) { - logInfo("Compiling %s...", src); + logInfo("Compiling", Color.light_green, "%s", src); const objPath = pathToObjName(settings.platform, src); objs[i] = compileUnit(src, objPath, buildsettings, settings); } @@ -536,7 +541,7 @@ class BuildGenerator : ProjectGenerator { foreach (i, src; srcs.array) compileSource(i, src); } - logInfo("Linking..."); + logInfo("Linking", Color.light_green, "%s", buildsettings.targetName.color(Mode.bold)); lbuildsettings.sourceFiles = is_static_library ? [] : lbuildsettings.sourceFiles.filter!(f => isLinkerFile(settings.platform, f)).array; settings.compiler.setTarget(lbuildsettings, settings.platform); settings.compiler.prepareBuildSettings(lbuildsettings, settings.platform, BuildSetting.commandLineSeparate|BuildSetting.sourceFiles); @@ -575,7 +580,7 @@ class BuildGenerator : ProjectGenerator { settings.compiler.invoke(buildsettings, settings.platform, settings.compileCallback); if (generate_binary) { - logInfo("Linking..."); + logInfo("Linking", Color.light_green, "%s", buildsettings.targetName.color(Mode.bold)); settings.compiler.invokeLinker(lbuildsettings, settings.platform, [tempobj.toNativeString()], settings.linkCallback); } } @@ -592,7 +597,7 @@ class BuildGenerator : ProjectGenerator { } if (!exe_file_path.absolute) exe_file_path = cwd ~ exe_file_path; runPreRunCommands(m_project.rootPackage, m_project, settings, buildsettings); - logInfo("Running %s %s", exe_file_path.relativeTo(runcwd), run_args.join(" ")); + logInfo("Running", Color.green, "%s %s", exe_file_path.relativeTo(runcwd), run_args.join(" ")); string[string] env; foreach (aa; [buildsettings.environments, buildsettings.runEnvironments]) foreach (k, v; aa) diff --git a/source/dub/generators/cmake.d b/source/dub/generators/cmake.d index 595442dbd..3be3d625f 100644 --- a/source/dub/generators/cmake.d +++ b/source/dub/generators/cmake.d @@ -9,9 +9,9 @@ module dub.generators.cmake; import dub.compilers.buildsettings; import dub.generators.generator; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.core.file; import dub.internal.vibecompat.inet.path; +import dub.internal.logging; import dub.project; import std.algorithm: map, uniq; @@ -114,13 +114,16 @@ class CMakeGenerator: ProjectGenerator file.close; script.shrinkTo(0); scripts.put(filename); + + logInfo("Generated", Color.green, "%s.cmake", name); } if(!cmakeListsPath.existsFile) { logWarn("You must use a fork of CMake which has D support for these scripts to function properly."); logWarn("It is available at https://github.com/trentforkert/cmake"); - logInfo("Generating default CMakeLists.txt"); + logDiagnostic("Generating default CMakeLists.txt"); + script.put("cmake_minimum_required(VERSION 3.0)\n"); script.put("project(%s D)\n".format(m_project.rootPackage.name)); @@ -131,6 +134,8 @@ class CMakeGenerator: ProjectGenerator file.write(script.data); file.close; + + logInfo("Generated", Color.green, "CMakeLists.txt (default)"); } } } diff --git a/source/dub/generators/generator.d b/source/dub/generators/generator.d index 534b38c72..ba1427853 100644 --- a/source/dub/generators/generator.d +++ b/source/dub/generators/generator.d @@ -13,8 +13,8 @@ import dub.generators.build; import dub.generators.sublimetext; import dub.generators.visuald; import dub.internal.vibecompat.core.file; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.path; +import dub.internal.logging; import dub.package_; import dub.packagemanager; import dub.project; @@ -891,7 +891,7 @@ private void prepareGeneration(in Package pack, in Project proj, in GeneratorSet in BuildSettings buildsettings) { if (buildsettings.preGenerateCommands.length && !isRecursiveInvocation(pack.name)) { - logInfo("Running pre-generate commands for %s...", pack.name); + logInfo("Pre-gen", Color.light_green, "Running commands for %s", pack.name); runBuildCommands(CommandType.preGenerate, buildsettings.preGenerateCommands, pack, proj, settings, buildsettings); } } @@ -903,7 +903,7 @@ private void finalizeGeneration(in Package pack, in Project proj, in GeneratorSe in BuildSettings buildsettings, NativePath target_path, bool generate_binary) { if (buildsettings.postGenerateCommands.length && !isRecursiveInvocation(pack.name)) { - logInfo("Running post-generate commands for %s...", pack.name); + logInfo("Post-gen", Color.light_green, "Running commands for %s", pack.name); runBuildCommands(CommandType.postGenerate, buildsettings.postGenerateCommands, pack, proj, settings, buildsettings); } diff --git a/source/dub/generators/sublimetext.d b/source/dub/generators/sublimetext.d index f53ae530d..d46fbd996 100644 --- a/source/dub/generators/sublimetext.d +++ b/source/dub/generators/sublimetext.d @@ -9,9 +9,9 @@ module dub.generators.sublimetext; import dub.compilers.compiler; import dub.generators.generator; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.data.json; import dub.internal.vibecompat.inet.path; +import dub.internal.logging; import dub.packagemanager; import dub.project; @@ -45,7 +45,7 @@ class SublimeTextGenerator : ProjectGenerator { write(projectPath, jsonString.data); - logInfo("Project '%s' generated.", projectPath); + logInfo("Generated", Color.green, "%s", projectPath); } } diff --git a/source/dub/generators/visuald.d b/source/dub/generators/visuald.d index 7f4512237..3e90d6af1 100644 --- a/source/dub/generators/visuald.d +++ b/source/dub/generators/visuald.d @@ -11,7 +11,7 @@ import dub.compilers.compiler; import dub.generators.generator; import dub.internal.utils; import dub.internal.vibecompat.core.file; -import dub.internal.vibecompat.core.log; +import dub.internal.logging; import dub.package_; import dub.packagemanager; import dub.project; @@ -124,7 +124,7 @@ class VisualDGenerator : ProjectGenerator { sln.put(ret.data); sln.flush(); - logInfo("Solution '%s' generated.", solutionFileName()); + logInfo("Generated", Color.green, "%s (solution)", solutionFileName()); } diff --git a/source/dub/init.d b/source/dub/init.d index 876b79139..b4c887c98 100644 --- a/source/dub/init.d +++ b/source/dub/init.d @@ -8,7 +8,7 @@ module dub.init; import dub.internal.vibecompat.core.file; -import dub.internal.vibecompat.core.log; +import dub.internal.logging; import dub.package_ : PackageFormat, packageInfoFiles, defaultPackageFilename; import dub.recipe.packagerecipe; import dub.dependency; diff --git a/source/dub/internal/colorize/colors.d b/source/dub/internal/colorize/colors.d new file mode 100644 index 000000000..5d6729466 --- /dev/null +++ b/source/dub/internal/colorize/colors.d @@ -0,0 +1,118 @@ +/** + * Authors: Pedro Tacla Yamada + * Date: June 9, 2014 + * License: Licensed under the MIT license. See LICENSE for more information + * Version: 1.0.2 + */ +module dub.internal.colorize.colors; + +import std.string : format; + +private template color_type(int offset) +{ + enum type : int + { + init = 39 + offset, + + black = 30 + offset, + red = 31 + offset, + green = 32 + offset, + yellow = 33 + offset, + blue = 34 + offset, + magenta = 35 + offset, + cyan = 36 + offset, + white = 37 + offset, + + light_black = 90 + offset, + light_red = 91 + offset, + light_green = 92 + offset, + light_yellow = 93 + offset, + light_blue = 94 + offset, + light_magenta = 95 + offset, + light_cyan = 96 + offset, + light_white = 97 + offset + } +} + +alias color_type!0 .type fg; +alias color_type!10 .type bg; + +// Text modes +enum mode : int +{ + init = 0, + bold = 1, + underline = 4, + blink = 5, + swap = 7, + hide = 8 +} + +/** + * Wraps a string around color escape sequences. + * + * Params: + * str = The string to wrap with colors and modes + * c = The foreground color (see the fg enum type) + * b = The background color (see the bg enum type) + * m = The text mode (see the mode enum type) + * Example: + * --- + * writeln("This is blue".color(fg.blue)); + * writeln( + * color("This is red over green blinking", fg.blue, bg.green, mode.blink) + * ); + * --- + */ +string color( + const string str, + const fg c=fg.init, + const bg b=bg.init, + const mode m=mode.init +) pure +{ + return format("\033[%d;%d;%dm%s\033[0m", m, c, b, str); +} + +unittest +{ + import std.string : representation; + + string ret; + + ret = "This is yellow".color(fg.yellow); + assert(ret.representation == "\033[0;33;49mThis is yellow\033[0m".representation); + + ret = "This is light green".color(fg.light_green); + assert(ret.representation == "\033[0;92;49mThis is light green\033[0m".representation); + + ret = "This is light blue with red background".color(fg.light_blue, bg.red); + assert(ret.representation == "\033[0;94;41mThis is light blue with red background\033[0m".representation); + + ret = "This is red on blue blinking".color(fg.red, bg.blue, mode.blink); + assert(ret.representation == "\033[5;31;44mThis is red on blue blinking\033[0m".representation); +} + +string colorHelper(T)(const string str, const T t=T.init) pure + if(is(T : fg) || is(T : bg) || is(T : mode)) +{ + return format("\033[%dm%s\033[0m", t, str); +} + +alias background = colorHelper!bg; +alias foreground = colorHelper!fg; +alias style = colorHelper!mode; +alias color = colorHelper; + +unittest +{ + import std.string : representation; + + string ret; + + ret = "This is red on blue blinking" + .foreground(fg.red) + .background(bg.blue) + .style(mode.blink); + assert(ret.representation == "\033[5m\033[44m\033[31mThis is red on blue blinking\033[0m\033[0m\033[0m".representation); +} diff --git a/source/dub/internal/colorize/cwrite.d b/source/dub/internal/colorize/cwrite.d new file mode 100644 index 000000000..0f5d1f2f6 --- /dev/null +++ b/source/dub/internal/colorize/cwrite.d @@ -0,0 +1,74 @@ +/** + * Authors: ponce + * Date: July 28, 2014 + * License: Licensed under the MIT license. See LICENSE for more information + * Version: 1.0.2 + */ +module dub.internal.colorize.cwrite; + +import std.stdio : File, stdout; + +import dub.internal.colorize.winterm; + +/// Coloured write. +void cwrite(T...)(T args) if (!is(T[0] : File)) +{ + stdout.cwrite(args); +} + +/// Coloured writef. +void cwritef(Char, T...)(in Char[] fmt, T args) if (!is(T[0] : File)) +{ + stdout.cwritef(fmt, args); +} + +/// Coloured writefln. +void cwritefln(Char, T...)(in Char[] fmt, T args) +{ + stdout.cwritef(fmt ~ "\n", args); +} + +/// Coloured writeln. +void cwriteln(T...)(T args) +{ + // Most general instance + stdout.cwrite(args, '\n'); +} + +/// Coloured writef to a File. +void cwritef(Char, A...)(File f, in Char[] fmt, A args) +{ + import std.string : format; + auto s = format(fmt, args); + f.cwrite(s); +} + +/// Coloured writef to a File. +void cwrite(S...)(File f, S args) +{ + import std.conv : to; + + string s = ""; + foreach(arg; args) + s ~= to!string(arg); + + version(Windows) + { + WinTermEmulation winterm; + winterm.initialize(); + foreach(dchar c ; s) + { + auto charAction = winterm.feed(c); + final switch(charAction) with (WinTermEmulation.CharAction) + { + case drop: break; + case write: f.write(c); break; + case flush: f.flush(); break; + } + } + } + else + { + f.write(s); + } +} diff --git a/source/dub/internal/colorize/package.d b/source/dub/internal/colorize/package.d new file mode 100644 index 000000000..b4ff384b5 --- /dev/null +++ b/source/dub/internal/colorize/package.d @@ -0,0 +1,10 @@ +/** + * Authors: ponce + * Date: July 28, 2014 + * License: Licensed under the MIT license. See LICENSE for more information + * Version: 1.0.2 + */ +module dub.internal.colorize; + +public import dub.internal.colorize.colors; +public import dub.internal.colorize.cwrite; diff --git a/source/dub/internal/colorize/winterm.d b/source/dub/internal/colorize/winterm.d new file mode 100644 index 000000000..32b7be2be --- /dev/null +++ b/source/dub/internal/colorize/winterm.d @@ -0,0 +1,191 @@ +/** + * Authors: ponce + * Date: July 28, 2014 + * License: Licensed under the MIT license. See LICENSE for more information + * Version: 1.0.2 + */ +module dub.internal.colorize.winterm; + +version(Windows) +{ + import core.sys.windows.windows; + + // Patch for DMD 2.065 compatibility + static if( __VERSION__ < 2066 ) private enum nogc = 1; + + // This is a state machine to enable terminal colors on Windows. + // Parses and interpret ANSI/VT100 Terminal Control Escape Sequences. + // Only supports colour sequences, will output char incorrectly on invalid input. + struct WinTermEmulation + { + public: + @nogc void initialize() nothrow + { + // saves console attributes + _console = GetStdHandle(STD_OUTPUT_HANDLE); + _savedInitialColor = (0 != GetConsoleScreenBufferInfo(_console, &consoleInfo)); + _state = State.initial; + } + + @nogc ~this() nothrow + { + // Restore initial text attributes on release + if (_savedInitialColor) + { + SetConsoleTextAttribute(_console, consoleInfo.wAttributes); + _savedInitialColor = false; + } + } + + enum CharAction + { + write, + drop, + flush + } + + // Eat one character and update color state accordingly. + // Returns what to do with the fed character. + @nogc CharAction feed(dchar d) nothrow + { + final switch(_state) with (State) + { + case initial: + if (d == '\x1B') + { + _state = escaped; + return CharAction.flush; + } + break; + + case escaped: + if (d == '[') + { + _state = readingAttribute; + _parsedAttr = 0; + return CharAction.drop; + } + break; + + case readingAttribute: + if (d >= '0' && d <= '9') + { + _parsedAttr = _parsedAttr * 10 + (d - '0'); + return CharAction.drop; + } + else if (d == ';') + { + executeAttribute(_parsedAttr); + _parsedAttr = 0; + return CharAction.drop; + } + else if (d == 'm') + { + executeAttribute(_parsedAttr); + _state = State.initial; + return CharAction.drop; + } + break; + } + + return CharAction.write; + } + + private: + HANDLE _console; + bool _savedInitialColor; + CONSOLE_SCREEN_BUFFER_INFO consoleInfo; + State _state; + WORD _currentAttr; + int _parsedAttr; + + enum State + { + initial, + escaped, + readingAttribute + } + + @nogc void setForegroundColor(WORD fgFlags) nothrow + { + _currentAttr = _currentAttr & ~(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY); + _currentAttr = _currentAttr | fgFlags; + SetConsoleTextAttribute(_console, _currentAttr); + } + + @nogc void setBackgroundColor(WORD bgFlags) nothrow + { + _currentAttr = _currentAttr & ~(BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY); + _currentAttr = _currentAttr | bgFlags; + SetConsoleTextAttribute(_console, _currentAttr); + } + + // resets to the same foreground color that was set on initialize() + @nogc void resetForegroundColor() nothrow + { + if (!_savedInitialColor) + return; + + _currentAttr = _currentAttr & ~(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY); + _currentAttr = _currentAttr | (consoleInfo.wAttributes & (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY)); + SetConsoleTextAttribute(_console, _currentAttr); + } + + // resets to the same background color that was set on initialize() + @nogc void resetBackgroundColor() nothrow + { + if (!_savedInitialColor) + return; + + _currentAttr = _currentAttr & ~(BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY); + _currentAttr = _currentAttr | (consoleInfo.wAttributes & (BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY)); + SetConsoleTextAttribute(_console, _currentAttr); + } + + @nogc void executeAttribute(int attr) nothrow + { + switch (attr) + { + case 0: + // reset all attributes + SetConsoleTextAttribute(_console, consoleInfo.wAttributes); + break; + + default: + if ( (30 <= attr && attr <= 37) || (90 <= attr && attr <= 97) ) + { + WORD color = 0; + if (90 <= attr && attr <= 97) + { + color = FOREGROUND_INTENSITY; + attr -= 60; + } + attr -= 30; + color |= (attr & 1 ? FOREGROUND_RED : 0) | (attr & 2 ? FOREGROUND_GREEN : 0) | (attr & 4 ? FOREGROUND_BLUE : 0); + setForegroundColor(color); + } + else if (attr == 39) // fg.init + { + resetForegroundColor(); + } + + if ( (40 <= attr && attr <= 47) || (100 <= attr && attr <= 107) ) + { + WORD color = 0; + if (100 <= attr && attr <= 107) + { + color = BACKGROUND_INTENSITY; + attr -= 60; + } + attr -= 40; + color |= (attr & 1 ? BACKGROUND_RED : 0) | (attr & 2 ? BACKGROUND_GREEN : 0) | (attr & 4 ? BACKGROUND_BLUE : 0); + setBackgroundColor(color); + } + else if (attr == 49) // bg.init + { + resetBackgroundColor(); + } + } + } + } +} diff --git a/source/dub/internal/git.d b/source/dub/internal/git.d index 24e3e7bce..c8a371e4f 100644 --- a/source/dub/internal/git.d +++ b/source/dub/internal/git.d @@ -1,7 +1,7 @@ module dub.internal.git; import dub.internal.vibecompat.core.file; -import dub.internal.vibecompat.core.log; +import dub.internal.logging; import std.file; import std.string; diff --git a/source/dub/internal/logging.d b/source/dub/internal/logging.d new file mode 100644 index 000000000..ecada5562 --- /dev/null +++ b/source/dub/internal/logging.d @@ -0,0 +1,382 @@ +/** + Handles all the console output of the Dub package manager, by providing useful + methods for handling colored text. The module also disables colors when stdout + and stderr are not a TTY in order to avoid ASCII escape sequences in piped + output. The module can autodetect and configure itself in this regard by + calling initLogging() at the beginning of the program. But, whether to color + text or not can also be set manually with setLoggingColorsEnabled(bool). + + The output for the log levels error, warn and info is formatted like this: + + " " + '----------' + fixed width + + the "tag" part can be colored (most oftenly will be) and always has a fixed + width, which is defined as a const at the beginning of this module. + + The output for the log levels debug and diagnostic will be just the plain + string. + + There are some default tag string and color values for some logging levels: + - warn: "Warning", yellow bold + - error: "Error", red bold + + Actually, for error and warn levels, the tag color is fixed to the ones listed + above. + + Also, the default tag string for the info level is "" (the empty string) and + the default color is white (usually it's manually set when calling logInfo + with the wanted tag string, but this allows to just logInfo("text") without + having to worry about the tag if it's not needed). + + Usage: + After initializing the logging module with initLogging(), the functions + logDebug(..), logDiagnostic(..), logInfo(..), logWarning(..) and logError(..) + can be used to print log messages. Whether the messages are printed on stdout + or stderr depends on the log level (warning and error go to stderr). + The log(..) function can also be used. Check the signature and documentation + of the functions for more information. + + The minimum log level to print can be configured using setLogLevel(..), + and whether to color outputted text or not can be set with + setLoggingColorsEnabled(..) + + The color(str, color) function can be used to color text within a log + message, for instance like this: + + logInfo("Tag", Color.green, "My %s message", "colored".color(Color.red)) + + Copyright: © 2018 Giacomo De Lazzari + License: Subject to the terms of the MIT license, as written in the included LICENSE file. + Authors: Giacomo De Lazzari +*/ + +module dub.internal.logging; + +import std.stdio; +import std.array; +import std.format; +import std.string; + +import dub.internal.colorize : fg, mode; + +/** + An enum listing possible colors for terminal output, useful to set the color + of a tag. Re-exported from d-colorize in dub.internal.colorize. See the enum + definition there for a list of possible values. +*/ +public alias Color = fg; + +/** + An enum listing possible text "modes" for terminal output, useful to set the + text to bold, underline, blinking, etc... + Re-exported from d-colorize in dub.internal.colorize. See the enum definition + there for a list of possible values. +*/ +public alias Mode = mode; + +/// The tag width in chars, defined as a constant here +private const int TAG_WIDTH = 12; + +/// Possible log levels supported +enum LogLevel { + debug_, + diagnostic, + info, + warn, + error, + none +} + +// The current minimum log level to be printed +private shared LogLevel _minLevel = LogLevel.info; + +/* + Whether to print text with colors or not, defaults to true but will be set + to false in initLogging() if stdout or stderr are not a TTY (which means the + output is probably being piped and we don't want ASCII escape chars in it) +*/ +private shared bool _printColors = true; + +// isatty() is used in initLogging() to detect whether or not we are on a TTY +extern (C) int isatty(int); + +/** + This function must be called at the beginning for the program, before any + logging occurs. It will detect whether or not stdout/stderr are a console/TTY + and will consequently disable colored output if needed. Also, if a NO_COLOR + environment variable is defined, colors are disabled (https://no-color.org/). + + Forgetting to call the function will result in ASCII escape sequences in the + piped output, probably an undesirable thing. +*/ +void initLogging() +{ + import std.process : environment; + import core.stdc.stdio; + + _printColors = environment.get("NO_COLOR") == ""; + version (Windows) + { + version (CRuntime_DigitalMars) + { + if (!isatty(core.stdc.stdio.stdout._file) || + !isatty(core.stdc.stdio.stderr._file)) + _printColors = false; + } + else version (CRuntime_Microsoft) + { + if (!isatty(fileno(core.stdc.stdio.stdout)) || + !isatty(fileno(core.stdc.stdio.stderr))) + _printColors = false; + } + else + _printColors = false; + } + else version (Posix) + { + import core.sys.posix.unistd; + + if (!isatty(STDERR_FILENO) || !isatty(STDOUT_FILENO)) + _printColors = false; + } +} + +/// Sets the minimum log level to be printed +void setLogLevel(LogLevel level) nothrow +{ + _minLevel = level; +} + +/// Gets the minimum log level to be printed +LogLevel getLogLevel() +{ + return _minLevel; +} + +/// Set whether to print colors or not +void setLoggingColorsEnabled(bool enabled) +{ + _printColors = enabled; +} + +/** + Shorthand function to log a message with debug/diagnostic level, no tag string + or tag color required (since there will be no tag). + + Params: + level = The log level for the logged message + fmt = See http://dlang.org/phobos/std_format.html#format-string +*/ +void logDebug(T...)(string fmt, lazy T args) nothrow +{ + log(LogLevel.debug_, false, "", Color.init, fmt, args); +} + +/// ditto +void logDiagnostic(T...)(string fmt, lazy T args) nothrow +{ + log(LogLevel.diagnostic, false, "", Color.init, fmt, args); +} + +/** + Shorthand function to log a message with info level, with custom tag string + and tag color. + + Params: + tag = The string the tag at the beginning of the line should contain + tagColor = The color the tag string should have + level = The log level for the logged message + fmt = See http://dlang.org/phobos/std_format.html#format-string +*/ +void logInfo(T...)(string tag, Color tagColor, string fmt, lazy T args) nothrow +{ + log(LogLevel.info, false, tag, tagColor, fmt, args); +} + +/** + Shorthand function to log a message with info level, this version prints an + empty tag automatically (which is different from not having a tag - in this + case there will be an identation of TAG_WIDTH chars on the left anyway). + + Params: + level = The log level for the logged message + fmt = See http://dlang.org/phobos/std_format.html#format-string +*/ +void logInfo(T...)(string fmt, lazy T args) nothrow if (!is(T[0] : Color)) +{ + log(LogLevel.info, false, "", Color.init, fmt, args); +} + +/** + Shorthand function to log a message with info level, this version doesn't + print a tag at all, it effectively just prints the given string. + + Params: + level = The log level for the logged message + fmt = See http://dlang.org/phobos/std_format.html#format-string +*/ +void logInfoNoTag(T...)(string fmt, lazy T args) nothrow if (!is(T[0] : Color)) +{ + log(LogLevel.info, true, "", Color.init, fmt, args); +} + +/** + Shorthand function to log a message with warning level, with custom tag string. + The tag color is fixed to yellow. + + Params: + tag = The string the tag at the beginning of the line should contain + level = The log level for the logged message + fmt = See http://dlang.org/phobos/std_format.html#format-string +*/ +void logWarnTag(T...)(string tag, string fmt, lazy T args) nothrow +{ + log(LogLevel.warn, false, tag, Color.yellow, fmt, args); +} + +/** + Shorthand function to log a message with warning level, using the default + tag "Warning". The tag color is also fixed to yellow. + + Params: + level = The log level for the logged message + fmt = See http://dlang.org/phobos/std_format.html#format-string +*/ +void logWarn(T...)(string fmt, lazy T args) nothrow +{ + log(LogLevel.warn, false, "Warning", Color.yellow, fmt, args); +} + +/** + Shorthand function to log a message with error level, with custom tag string. + The tag color is fixed to red. + + Params: + tag = The string the tag at the beginning of the line should contain + level = The log level for the logged message + fmt = See http://dlang.org/phobos/std_format.html#format-string +*/ +void logErrorTag(T...)(string tag, string fmt, lazy T args) nothrow +{ + log(LogLevel.error, false, tag, Color.red, fmt, args); +} + +/** + Shorthand function to log a message with error level, using the default + tag "Error". The tag color is also fixed to red. + + Params: + level = The log level for the logged message + fmt = See http://dlang.org/phobos/std_format.html#format-string +*/ +void logError(T...)(string fmt, lazy T args) nothrow +{ + log(LogLevel.error, false, "Error", Color.red, fmt, args); +} + +/** + Log a message with the specified log level and with the specified tag string + and color. If the log level is debug or diagnostic, the tag is not printed + thus the tag string and tag color will be ignored. If the log level is error + or warning, the tag will be in bold text. Also the tag can be disabled (for + any log level) by passing true as the second argument. + + Params: + level = The log level for the logged message + disableTag = Setting this to true disables the tag, no matter what + tag = The string the tag at the beginning of the line should contain + tagColor = The color the tag string should have + fmt = See http://dlang.org/phobos/std_format.html#format-string +*/ +void log(T...)( + LogLevel level, + bool disableTag, + string tag, + Color tagColor, + string fmt, + lazy T args +) nothrow +{ + if (level < _minLevel) + return; + + auto hasTag = true; + if (level <= LogLevel.diagnostic) + hasTag = false; + if (disableTag) + hasTag = false; + + auto boldTag = false; + if (level >= LogLevel.warn) + boldTag = true; + + try + { + string result = format(fmt, args); + + if (hasTag) + result = tag.rightJustify(TAG_WIDTH, ' ').color(tagColor, boldTag ? Mode.bold : Mode.init) ~ " " ~ result; + + import dub.internal.colorize : cwrite; + + File output = (level <= LogLevel.info) ? stdout : stderr; + + if (output.isOpen) + { + output.cwrite(result, "\n"); + output.flush(); + } + } + catch (Exception e) + { + debug assert(false, e.msg); + } +} + +/** + Colors the specified string with the specified color. The function is used to + print colored text within a log message. The function also checks whether + color output is enabled or disabled (when not outputting to a TTY) and, in the + last case, just returns the plain string. This allows to use it like so: + + logInfo("Tag", Color.green, "My %s log message", "colored".color(Color.red)); + + without worring whether or not colored output is enabled or not. + + Also a mode can be specified, such as bold/underline/etc... + + Params: + str = The string to color + color = The color to apply + mode = An optional mode, such as bold/underline/etc... +*/ +string color(const string str, const Color c, const Mode m = Mode.init) +{ + import dub.internal.colorize; + + if (_printColors) + return dub.internal.colorize.color(str, c, bg.init, m); + else + return str; +} + +/** + This function is the same as the above one, but just accepts a mode. + It's useful, for instance, when outputting bold text without changing the + color. + + Params: + str = The string to color + mode = The mode, such as bold/underline/etc... +*/ +string color(const string str, const Mode m = Mode.init) +{ + import dub.internal.colorize; + + if (_printColors) + return dub.internal.colorize.color(str, fg.init, bg.init, m); + else + return str; +} diff --git a/source/dub/internal/utils.d b/source/dub/internal/utils.d index c53d7084c..bf27d195d 100644 --- a/source/dub/internal/utils.d +++ b/source/dub/internal/utils.d @@ -8,11 +8,11 @@ module dub.internal.utils; import dub.internal.vibecompat.core.file; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.data.json; import dub.internal.vibecompat.inet.url; import dub.compilers.buildsettings : BuildSettings; import dub.version_; +import dub.internal.logging; import core.time : Duration; import std.algorithm : canFind, startsWith; diff --git a/source/dub/internal/vibecompat/core/file.d b/source/dub/internal/vibecompat/core/file.d index 6ee345dd8..15ee2550e 100644 --- a/source/dub/internal/vibecompat/core/file.d +++ b/source/dub/internal/vibecompat/core/file.d @@ -9,7 +9,7 @@ module dub.internal.vibecompat.core.file; public import dub.internal.vibecompat.inet.url; -import dub.internal.vibecompat.core.log; +import dub.internal.logging; import std.conv; import core.stdc.stdio; diff --git a/source/dub/internal/vibecompat/core/log.d b/source/dub/internal/vibecompat/core/log.d deleted file mode 100644 index e48569e4d..000000000 --- a/source/dub/internal/vibecompat/core/log.d +++ /dev/null @@ -1,99 +0,0 @@ -/** - Central logging facility for vibe. - - Copyright: © 2012 rejectedsoftware e.K. - License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. - Authors: Sönke Ludwig -*/ -module dub.internal.vibecompat.core.log; - -import std.array; -import std.datetime; -import std.format; -import std.stdio; -import core.thread; - -private { - shared LogLevel s_minLevel = LogLevel.info; - shared LogLevel s_logFileLevel; -} - -/// Sets the minimum log level to be printed. -void setLogLevel(LogLevel level) nothrow -{ - s_minLevel = level; -} - -LogLevel getLogLevel() -{ - return s_minLevel; -} - -/** - Logs a message. - - Params: - level = The log level for the logged message - fmt = See http://dlang.org/phobos/std_format.html#format-string -*/ -void logDebug(T...)(string fmt, lazy T args) nothrow { log(LogLevel.debug_, fmt, args); } -/// ditto -void logDiagnostic(T...)(string fmt, lazy T args) nothrow { log(LogLevel.diagnostic, fmt, args); } -/// ditto -void logInfo(T...)(string fmt, lazy T args) nothrow { log(LogLevel.info, fmt, args); } -/// ditto -void logWarn(T...)(string fmt, lazy T args) nothrow { log(LogLevel.warn, fmt, args); } -/// ditto -void logError(T...)(string fmt, lazy T args) nothrow { log(LogLevel.error, fmt, args); } - -/// ditto -void log(T...)(LogLevel level, string fmt, lazy T args) -nothrow { - if( level < s_minLevel ) return; - string pref; - final switch( level ){ - case LogLevel.debug_: pref = "trc"; break; - case LogLevel.diagnostic: pref = "dbg"; break; - case LogLevel.info: pref = "INF"; break; - case LogLevel.warn: pref = "WRN"; break; - case LogLevel.error: pref = "ERR"; break; - case LogLevel.fatal: pref = "FATAL"; break; - case LogLevel.none: assert(false); - } - - try { - auto txt = appender!string(); - txt.reserve(256); - formattedWrite(txt, fmt, args); - - auto threadid = () @trusted { return cast(ulong)cast(void*)Thread.getThis(); } (); - auto fiberid = () @trusted { return cast(ulong)cast(void*)Fiber.getThis(); } (); - threadid ^= threadid >> 32; - fiberid ^= fiberid >> 32; - - if (level >= s_minLevel) { - File output; - if (level == LogLevel.info) () @trusted { output = stdout; } (); - else () @trusted { output = stderr; } (); - if (output.isOpen) { - output.writeln(txt.data); - output.flush(); - } - } - } catch( Exception e ){ - // this is bad but what can we do.. - debug assert(false, e.msg); - } -} - -/// Specifies the log level for a particular log message. -enum LogLevel { - debug_, - diagnostic, - info, - warn, - error, - fatal, - none -} - diff --git a/source/dub/internal/vibecompat/data/json.d b/source/dub/internal/vibecompat/data/json.d index b55166a7a..aced94025 100644 --- a/source/dub/internal/vibecompat/data/json.d +++ b/source/dub/internal/vibecompat/data/json.d @@ -869,7 +869,6 @@ Json parseJson(R)(ref R range, int* line = null, string filename = null) skipWhitespace(range, line); version(JsonLineNumbers) { - import dub.internal.vibecompat.core.log; int curline = line ? *line : 0; } diff --git a/source/dub/package_.d b/source/dub/package_.d index 40a8eda77..d5daff7cc 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -15,8 +15,8 @@ import dub.description; import dub.recipe.json; import dub.recipe.sdl; +import dub.internal.logging; import dub.internal.utils; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.core.file; import dub.internal.vibecompat.data.json; import dub.internal.vibecompat.inet.path; diff --git a/source/dub/packagemanager.d b/source/dub/packagemanager.d index 845f4cac0..4e1464ded 100644 --- a/source/dub/packagemanager.d +++ b/source/dub/packagemanager.d @@ -11,9 +11,9 @@ import dub.dependency; static import dub.dependency; import dub.internal.utils; import dub.internal.vibecompat.core.file; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.data.json; import dub.internal.vibecompat.inet.path; +import dub.internal.logging; import dub.package_; import std.algorithm : countUntil, filter, sort, canFind, remove; @@ -633,7 +633,7 @@ class PackageManager { logDebug("About to delete root folder for package '%s'.", pack.path); rmdirRecurse(pack.path.toNativeString()); - logInfo("Removed package: '%s'", pack.name); + logInfo("Removed", Color.yellow, "%s %s", pack.name.color(Mode.bold), pack.version_); } /// Compatibility overload. Use the version without a `force_remove` argument instead. diff --git a/source/dub/packagesuppliers/fallback.d b/source/dub/packagesuppliers/fallback.d index 39a61e62d..1103e376c 100644 --- a/source/dub/packagesuppliers/fallback.d +++ b/source/dub/packagesuppliers/fallback.d @@ -46,14 +46,14 @@ private template fallback(T, alias func) { import std.format : format; enum fallback = q{ - import dub.internal.vibecompat.core.log : logDiagnostic; + import dub.internal.logging : logDebug; Exception firstEx; try return m_suppliers[0].ps.%1$s(args); catch (Exception e) { - logDiagnostic("Package supplier %%s failed with '%%s', trying fallbacks.", + logDebug("Package supplier %%s failed with '%%s', trying fallbacks.", m_suppliers[0].ps.description, e.msg); firstEx = e; } @@ -65,13 +65,13 @@ private template fallback(T, alias func) continue; try { - scope (success) logDiagnostic("Fallback %%s succeeded", pair.ps.description); + scope (success) logDebug("Fallback %%s succeeded", pair.ps.description); return pair.ps.%1$s(args); } catch (Exception e) { pair.failTime = now; - logDiagnostic("Fallback package supplier %%s failed with '%%s'.", + logDebug("Fallback package supplier %%s failed with '%%s'.", pair.ps.description, e.msg); } } diff --git a/source/dub/packagesuppliers/filesystem.d b/source/dub/packagesuppliers/filesystem.d index fa752bd3b..fcb9efddf 100644 --- a/source/dub/packagesuppliers/filesystem.d +++ b/source/dub/packagesuppliers/filesystem.d @@ -9,7 +9,8 @@ import dub.packagesuppliers.packagesupplier; the form "[package name]-[version].zip". */ class FileSystemPackageSupplier : PackageSupplier { - import dub.internal.vibecompat.core.log; + import dub.internal.logging; + version (Have_vibe_core) import dub.internal.vibecompat.inet.path : toNativeString; import std.exception : enforce; private { diff --git a/source/dub/packagesuppliers/maven.d b/source/dub/packagesuppliers/maven.d index 387269dd1..489984443 100644 --- a/source/dub/packagesuppliers/maven.d +++ b/source/dub/packagesuppliers/maven.d @@ -11,8 +11,8 @@ import dub.packagesuppliers.packagesupplier; class MavenRegistryPackageSupplier : PackageSupplier { import dub.internal.utils : retryDownload, HTTPStatusException; import dub.internal.vibecompat.data.json : serializeToJson; - import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.url : URL; + import dub.internal.logging; import std.datetime : Clock, Duration, hours, SysTime, UTC; diff --git a/source/dub/packagesuppliers/registry.d b/source/dub/packagesuppliers/registry.d index 83b01e56c..a5afa644a 100644 --- a/source/dub/packagesuppliers/registry.d +++ b/source/dub/packagesuppliers/registry.d @@ -12,9 +12,9 @@ package enum PackagesPath = "packages"; */ class RegistryPackageSupplier : PackageSupplier { import dub.internal.utils : download, retryDownload, HTTPStatusException; - import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.data.json : parseJson, parseJsonString, serializeToJson; import dub.internal.vibecompat.inet.url : URL; + import dub.internal.logging; import std.uri : encodeComponent; import std.datetime : Clock, Duration, hours, SysTime, UTC; @@ -131,4 +131,3 @@ class RegistryPackageSupplier : PackageSupplier { .array; } } - diff --git a/source/dub/project.d b/source/dub/project.d index 2e184335b..8d9e419cb 100644 --- a/source/dub/project.d +++ b/source/dub/project.d @@ -13,9 +13,9 @@ import dub.description; import dub.generators.generator; import dub.internal.utils; import dub.internal.vibecompat.core.file; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.data.json; import dub.internal.vibecompat.inet.path; +import dub.internal.logging; import dub.package_; import dub.packagemanager; import dub.recipe.selection; diff --git a/source/dub/recipe/packagerecipe.d b/source/dub/recipe/packagerecipe.d index 14bb98305..99964099b 100644 --- a/source/dub/recipe/packagerecipe.d +++ b/source/dub/recipe/packagerecipe.d @@ -10,9 +10,9 @@ module dub.recipe.packagerecipe; import dub.compilers.compiler; import dub.compilers.utils : warnOnSpecialCompilerFlags; import dub.dependency; +import dub.internal.logging; import dub.internal.vibecompat.core.file; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.path; import std.algorithm : findSplit, sort; diff --git a/source/dub/recipe/sdl.d b/source/dub/recipe/sdl.d index 31ac460fb..b7de2d2b2 100644 --- a/source/dub/recipe/sdl.d +++ b/source/dub/recipe/sdl.d @@ -9,8 +9,8 @@ module dub.recipe.sdl; import dub.compilers.compiler; import dub.dependency; +import dub.internal.logging; import dub.internal.sdlang; -import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.path; import dub.recipe.packagerecipe; diff --git a/test/colored-output.sh b/test/colored-output.sh new file mode 100755 index 000000000..afc1bb373 --- /dev/null +++ b/test/colored-output.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +. $(dirname "${BASH_SOURCE[0]}")/common.sh + +cd ${CURR_DIR}/1-exec-simple + +# Test --colors=off disabling colors correctly +${DUB} build --colors=off --compiler=${DC} 2>&1 | { ! \grep $'^\x1b\[' -c; } + +# Test --colors=automatic detecting no TTY +${DUB} build --colors=automatic --compiler=${DC} 2>&1 | { ! \grep $'^\x1b\[' -c; } + +# Test no --colors= option defaulting to automatic +${DUB} build --compiler=${DC} 2>&1 | { ! \grep $'^\x1b\[' -c; } + +# Test --colors=on enabling colors in any case +${DUB} build --colors=on --compiler=${DC} 2>&1 | \grep $'^\x1b\[' -c diff --git a/test/expected-issue1037-output b/test/expected-issue1037-output index 06fdbb100..9cbae8475 100644 --- a/test/expected-issue1037-output +++ b/test/expected-issue1037-output @@ -1,3 +1,3 @@ -Unresolvable dependencies to package gitcompatibledubpackage: + Error Unresolvable dependencies to package gitcompatibledubpackage: b @DIR/b depends on gitcompatibledubpackage ~>1.0.2 issue1037-better-dependency-messages ~master depends on gitcompatibledubpackage 1.0.1 diff --git a/test/feat663-search.sh b/test/feat663-search.sh index 21cea3ede..77289a688 100755 --- a/test/feat663-search.sh +++ b/test/feat663-search.sh @@ -10,6 +10,6 @@ fi if ! OUTPUT=$(${DUB} search '"dub-registry"' -v 2>&1); then die $LINENO '`dub search "dub-registry"` failed' "$OUTPUT" fi -if ! grep -q '^dub-registry (.*)\s'<<<"$OUTPUT"; then - die $LINENO '`grep -q '"'"'^dub-registry (.*)\s'"'"'` failed' "$OUTPUT" +if ! grep -q '^\s\sdub-registry (.*)\s'<<<"$OUTPUT"; then + die $LINENO '`grep -q '"'"'^\s\sdub-registry (.*)\s'"'"'` failed' "$OUTPUT" fi