diff --git a/coverage/config.py b/coverage/config.py index 7c7dbe19f..bc0684f7f 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -238,6 +238,7 @@ def __init__(self) -> None: self.html_skip_empty: Optional[bool] = None self.html_title = "Coverage report" self.show_contexts = False + self.theme = None # Defaults for [xml] self.xml_output = "coverage.xml" @@ -411,6 +412,7 @@ def copy(self) -> CoverageConfig: # [html] ("extra_css", "html:extra_css"), + ("theme", "html:theme"), ("html_dir", "html:directory"), ("html_skip_covered", "html:skip_covered", "boolean"), ("html_skip_empty", "html:skip_empty", "boolean"), diff --git a/coverage/html.py b/coverage/html.py index 7b827a794..cfce881c5 100644 --- a/coverage/html.py +++ b/coverage/html.py @@ -217,6 +217,10 @@ class HtmlReporter: "favicon_32.png", ] + THEMES = { + "github_dark_dimmed": "github_dark_dimmed.css" + } + def __init__(self, cov: Coverage) -> None: self.coverage = cov self.config = self.coverage.config @@ -239,6 +243,15 @@ def __init__(self, cov: Coverage) -> None: else: self.extra_css = None + self.theme = None + + if self.config.theme in self.THEMES: + self.theme = self.THEMES[self.config.theme] + + elif self.config.theme: + self.coverage._message(f"Unknown theme '{self.config.theme}'") + self.coverage._message(f"Available themes: {list(self.THEMES.keys())}") + self.data = self.coverage.get_data() self.has_arcs = self.data.has_arcs() @@ -263,6 +276,7 @@ def __init__(self, cov: Coverage) -> None: "title": title, "time_stamp": format_local_datetime(datetime.datetime.now()), "extra_css": self.extra_css, + "theme": self.theme, "has_arcs": self.has_arcs, "show_contexts": self.config.show_contexts, @@ -340,6 +354,12 @@ def make_local_static_report_files(self) -> None: for static in self.STATIC_FILES: shutil.copyfile(data_filename(static), os.path.join(self.directory, static)) + # If a theme is set, copy the corresponding css file + if self.theme: + shutil.copyfile( + data_filename(os.path.join("themes", self.theme)), + os.path.join(self.directory, self.theme)) + # Only write the .gitignore file if the directory was originally empty. # .gitignore can't be copied from the source tree because it would # prevent the static files from being checked in. diff --git a/coverage/htmlfiles/index.html b/coverage/htmlfiles/index.html index bde46eafe..c66048cb9 100644 --- a/coverage/htmlfiles/index.html +++ b/coverage/htmlfiles/index.html @@ -8,6 +8,9 @@ {{ title|escape }} + {% if theme %} + + {% endif %} {% if extra_css %} {% endif %} diff --git a/coverage/htmlfiles/pyfile.html b/coverage/htmlfiles/pyfile.html index bc8fa697d..10c22621b 100644 --- a/coverage/htmlfiles/pyfile.html +++ b/coverage/htmlfiles/pyfile.html @@ -8,6 +8,9 @@ Coverage for {{relative_filename|escape}}: {{nums.pc_covered_str}}% + {% if theme %} + + {% endif %} {% if extra_css %} {% endif %} diff --git a/coverage/htmlfiles/themes/github_dark_dimmed.css b/coverage/htmlfiles/themes/github_dark_dimmed.css new file mode 100644 index 000000000..e2ce33e94 --- /dev/null +++ b/coverage/htmlfiles/themes/github_dark_dimmed.css @@ -0,0 +1,87 @@ +/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ +/* Don't edit this .css file. Edit the .scss file instead! */ +:root { color-scheme: dark; } + +body { background-color: #22272e; color: #adbac7; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; } + +html > body { font-size: 12px; } + +html > body.indexfile { font-size: 16px; } + +p { font-size: inherit; line-height: 20px; } + +a:active, a:focus { outline: 2px solid #539bf5; outline-offset: -2px; } + +body.indexfile header h1 { flex-grow: 1; } +body.indexfile header .content { display: flex; align-items: center; padding: 16px; gap: 16px; flex-wrap: wrap; } +body.indexfile header .content .text { margin: 0; order: 2; flex-basis: 100%; } + +header { background-color: #2d333b; border-bottom-color: #444c56; } +header p.text { color: #adbac7; } +header.sticky { height: unset; } +header.sticky .text { display: none; } + +footer .content { color: #768390; } + +body.indexfile #filter_container { float: initial; margin: 0; } + +#filter_container input { width: 10em; font-size: 14px; line-height: 20px; padding: 5px 12px; color: #adbac7; border: 2px solid #444c56; background-color: #22272e; border-radius: 6px; vertical-align: middle; transition: 80ms cubic-bezier(0.33, 1, 0.68, 1); transition-property: color,background-color,box-shadow,border-color; } +#filter_container input:focus:not(:focus-visible) { outline: none; box-shadow: inset 0 0 0 1px transparent; } +#filter_container input:focus-visible { outline: none; box-shadow: inset 0 0 0 1px #539bf5; } + +header button { padding: 5px 16px; font-size: 14px; font-weight: 500; line-height: 20px; vertical-align: middle; border: 1px solid; border-radius: 6px; border-color: rgba(205, 217, 229, 0.1); appearance: none; } +header button:active, header button:focus { outline: 2px solid #539bf5; outline-offset: -2px; } +header button.run { background-color: #57ab5a; color: #ffffff; } +header button.run.show_run { background-color: #8ddb8c; border-color: rgba(205, 217, 229, 0.1); } +header button.mis { background-color: #c93c37; color: #222; } +header button.mis.show_mis { background-color: #f47067; border-color: rgba(205, 217, 229, 0.1); } +header button.exc { background-color: #373e47; color: #adbac7; } +header button.exc.show_exc { background-color: #545d68; border-color: rgba(205, 217, 229, 0.1); } +header button.par { background-color: #c69026; color: #444; } +header button.par.show_par { background-color: #eac55f; border-color: rgba(205, 217, 229, 0.1); } + +#help_panel { background-color: #2d333b; border-radius: 6px; border-color: #444c56; color: #adbac7; padding: 8px 16px; } + +body.indexfile #help_panel_wrapper { float: initial; order: 1; } + +#help_panel_wrapper label { cursor: pointer; } + +#help_panel .legend { font-size: 12px; font-weight: 600; } + +kbd { color: #adbac7; background-color: #2d333b; display: inline-block; padding: 3px 5px; font: 11px ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; line-height: 10px; vertical-align: middle; border: 1px solid rgba(99, 110, 123, 0.4); border-radius: 6px; box-shadow: inset 0 -1px 0 rgba(99, 110, 123, 0.4); } + +#source { font-family: SFMono-Regular, SF Mono, Menlo, Consolas, "Liberation Mono", monospace; } +#source p .n.highlight { background-color: rgba(255, 213, 79, 0.3); } +#source p .t { border-left-color: #444c56; } +#source p .t:hover { background-color: rgba(174, 124, 20, 0.15); } +#source p .t .com { color: #768390; } +#source p .t .key { color: #f47067; font-weight: normal; } +#source p .t .str { color: #96d0ff; } +#source p.mis .t { border-left-color: rgba(229, 83, 75, 0.3); } +#source p.mis.show_mis .t { background-color: rgba(229, 83, 75, 0.15); } +#source p.mis.show_mis .t:hover { background-color: rgba(229, 83, 75, 0.6); } +#source p.run .t { border-left-color: rgba(87, 171, 90, 0.3); } +#source p.run.show_run .t { background-color: rgba(87, 171, 90, 0.15); } +#source p.run.show_run .t:hover { background-color: rgba(87, 171, 90, 0.6); } +#source p.exc .t { border-left-color: rgba(118, 131, 144, 0.3); } +#source p.exc.show_exc .t { background-color: rgba(118, 131, 144, 0.15); } +#source p.exc.show_exc .t:hover { background-color: rgba(118, 131, 144, 0.6); } +#source p.par .t { border-left-color: rgba(174, 124, 20, 0.3); } +#source p.par.show_par .t { background-color: rgba(174, 124, 20, 0.15); } +#source p.par.show_par .t:hover { background-color: rgba(174, 124, 20, 0.6); } +#source p .annotate { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; color: #adbac7; } +#source p input ~ .r label.ctx:hover { background-color: rgba(65, 132, 228, 0.15); color: #adbac7; } +#source p input:checked ~ .r label.ctx { background-color: rgba(65, 132, 228, 0.15); color: #adbac7; } +#source p label.ctxt { color: #adbac7; } +#source p .ctxs { background-color: rgba(65, 132, 228, 0.15); color: #adbac7; } + +#index { font-family: SFMono-Regular, SF Mono, Menlo, Consolas, "Liberation Mono", monospace; } +#index td, #index th { border-bottom-color: #444c56; padding: 8px; } +#index th { color: #adbac7; } +#index th:hover, #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { background-color: #2d333b; } +#index tr.total td, #index tr.total_dynamic td { border-top-color: #444c56; } +#index tr.file:hover { background-color: #2d333b; } + +#scroll_marker { background-color: #22272e; border-color: #444c56; } +#scroll_marker .marker { background-color: #444c56; } diff --git a/coverage/htmlfiles/themes/github_dark_dimmed.scss b/coverage/htmlfiles/themes/github_dark_dimmed.scss new file mode 100644 index 000000000..ba53f900f --- /dev/null +++ b/coverage/htmlfiles/themes/github_dark_dimmed.scss @@ -0,0 +1,450 @@ +/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ + +// CSS styles for coverage.py HTML reports. +// GitHub Dark dimmed theme (implemented by Lord Baryhobal) + +// When you edit this file, you need to run "make css" to get the CSS file +// generated, and then check in both the .scss and the .css files. + +// When working on the file, this command is useful: +// sass --watch --style=compact --sourcemap=none --no-cache coverage/htmlfiles/themes/github_dark_dimmed.scss:coverage/htmfiles/themes/github_dark_dimmed.css +// +// OR you can process sass purely in python with `pip install pysass`, then: +// pysassc --style=compact coverage/htmlfiles/themes/github_dark_dimmed.scss coverage/htmlfiles/themes/github_dark_dimmed.css + +// Ignore this comment, it's for the CSS output file: +/* Don't edit this .css file. Edit the .scss file instead! */ + +// +// Declare colors and variables +// + +$font-normal: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; +$font-code: SFMono-Regular, SF Mono, Menlo, Consolas, "Liberation Mono", monospace; +$font-kbd: 11px ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; + +$bg: #22272e; +$bg2: #2d333b; +$border: #444c56; +$color: #adbac7; +$comment: #768390; +$string: #96d0ff; +$keyword: #f47067; + +$green: #57ab5a; +$yellow: #c69026; +$red: #c93c37; +$grey: #373e47; + +$green-active: #8ddb8c; +$yellow-active: #eac55f; +$red-active: #f47067; +$grey-active: #545d68; + +$btn-border: rgba(#cdd9e5, 0.1); + +$green-txt: #ffffff; +$yellow-txt: #444; +$red-txt: #222; +$grey-txt: #adbac7; + +$green2: $green; +$yellow2: #ae7c14; +$red2: #e5534b; +$grey2: $comment; + +$footer: #768390; +$line-hover: rgba($yellow2, 0.15); +$line-green: rgba($green2, 0.15); +$line-green-border: rgba($green2, 0.30); +$line-green-hover: rgba($green2, 0.60); + +$line-yellow: rgba($yellow2, 0.15); +$line-yellow-border: rgba($yellow2, 0.30); +$line-yellow-hover: rgba($yellow2, 0.60); + +$line-red: rgba($red2, 0.15); +$line-red-border: rgba($red2, 0.30); +$line-red-hover: rgba($red2, 0.60); + +$line-grey: rgba($grey2, 0.15); +$line-grey-border: rgba($grey2, 0.30); +$line-grey-hover: rgba($grey2, 0.60); + +$line-blue: rgba(#4184e4, 0.15); + +$outline: #539bf5; + +$overlay: #2d333b; +$border-kbd: rgba(#636e7b, 0.4); + +$highlight: rgba(#ffd54f, 0.3); + +// +// Mixins and utilities +// + +// Add visual outline to navigable elements on focus improve accessibility. +@mixin focus-border { + &:active, &:focus { + outline: 2px solid $outline; + outline-offset: -2px; + } +} + +// Page-wide styles +:root { + color-scheme: dark; +} + +body { + background-color: $bg; + color: $color; + font-family: $font-normal; +} +html > body { + font-size: 12px; +} + +html > body.indexfile { + font-size: 16px; +} + +p { + font-size: inherit; + line-height: 20px; +} + +a { + @include focus-border; +} + +// Page structure +body.indexfile header { + h1 { + flex-grow: 1; + } + + .content { + display: flex; + align-items: center; + padding: 16px; + gap: 16px; + flex-wrap: wrap; + + .text { + margin: 0; + order: 2; + flex-basis: 100%; + } + } +} + +header { + background-color: $bg2; + border-bottom-color: $border; + + p.text { + color: $color; + } + + &.sticky { + height: unset; + + .text { + display: none; + } + } +} + +footer { + .content { + color: $footer; + } +} + +// Header styles +body.indexfile #filter_container { + float: initial; + margin: 0; +} + + +#filter_container { + input { + width: 10em; + font-size: 14px; + line-height: 20px; + padding: 5px 12px; + color: $color; + border: 2px solid $border; + background-color: $bg; + border-radius: 6px; + vertical-align: middle; + transition: 80ms cubic-bezier(0.33, 1, 0.68, 1); + transition-property: color,background-color,box-shadow,border-color; + + &:focus:not(:focus-visible) { + outline: none; + box-shadow: inset 0 0 0 1px transparent; + } + + &:focus-visible { + outline: none; + box-shadow: inset 0 0 0 1px $outline; + } + } +} + + +header button { + padding: 5px 16px; + font-size: 14px; + font-weight: 500; + line-height: 20px; + vertical-align: middle; + border: 1px solid; + border-radius: 6px; + border-color: $btn-border; + appearance: none; + + @include focus-border; + + &.run { + background-color: $green; + color: $green-txt; + &.show_run { + background-color: $green-active; + border-color: $btn-border; + } + } + &.mis { + background-color: $red; + color: $red-txt; + &.show_mis { + background-color: $red-active; + border-color: $btn-border; + } + } + &.exc { + background-color: $grey; + color: $grey-txt; + &.show_exc { + background-color: $grey-active; + border-color: $btn-border; + } + } + &.par { + background-color: $yellow; + color: $yellow-txt; + &.show_par { + background-color: $yellow-active; + border-color: $btn-border; + } + } +} + +// Yellow post-it things. +%popup { + background-color: $overlay; + border-radius: 6px; + border-color: $border; + color: $color; + padding: 8px 16px; +} + +// Help panel +body.indexfile #help_panel_wrapper { + float: initial; + order: 1; +} + +#help_panel_wrapper { + label { + cursor: pointer; + } +} + +#help_panel { + @extend %popup; + + .legend { + font-size: 12px; + font-weight: 600; + } +} + +kbd { + color: $color; + background-color: $bg2; + display: inline-block; + padding: 3px 5px; + font: $font-kbd; + line-height: 10px; + vertical-align: middle; + border: 1px solid $border-kbd; + border-radius: 6px; + box-shadow: inset 0 -1px 0 $border-kbd; +} + + +// Source file styles + +// The slim bar at the left edge of the source lines, colored by coverage. +$border-indicator-width: .2em; + +#source { + font-family: $font-code; + + p { + // position relative makes position:absolute pop-ups appear in the right place. + .n { + &.highlight { + background-color: $highlight; + } + } + + .t { + border-left-color: $border; + + &:hover { + background-color: $line-hover; + } + + // Syntax coloring + .com { + color: $comment; + } + .key { + color: $keyword; + font-weight: normal; + } + .str { + color: $string; + } + } + + &.mis { + .t { + border-left-color: $line-red-border; + } + + &.show_mis .t { + background-color: $line-red; + + &:hover { + background-color: $line-red-hover; + } + } + } + + &.run { + .t { + border-left-color: $line-green-border; + } + + &.show_run .t { + background-color: $line-green; + + &:hover { + background-color: $line-green-hover; + } + } + } + + &.exc { + .t { + border-left-color: $line-grey-border; + } + + &.show_exc .t { + background-color: $line-grey; + + &:hover { + background-color: $line-grey-hover; + } + } + } + + &.par { + .t { + border-left-color: $line-yellow-border; + } + + &.show_par .t { + background-color: $line-yellow; + + &:hover { + background-color: $line-yellow-hover; + } + } + + } + + .annotate { + font-family: $font-normal; + color: $color; + } + + input { + & ~ .r label.ctx { + &:hover { + background-color: $line-blue; + color: $color; + } + } + + &:checked ~ .r label.ctx { + background-color: $line-blue; + color: $color; + } + } + + label.ctxt { + color: $color; + } + + .ctxs { + background-color: $line-blue; + color: $color; + } + } +} + + +// index styles +#index { + font-family: $font-code; + + td, th { + border-bottom-color: $border; + padding: 8px; + } + th { + color: $color; + + &:hover, + &[aria-sort="ascending"], &[aria-sort="descending"] { + background-color: $bg2; + } + } + + tr.total td, + tr.total_dynamic td { + border-top-color: $border; + } + tr.file:hover { + background-color: $bg2; + } +} + +// scroll marker styles +#scroll_marker { + background-color: $bg; + border-color: $border; + + .marker { + background-color: $border; + } +} diff --git a/setup.py b/setup.py index 536b64ac9..dd22a7fd7 100644 --- a/setup.py +++ b/setup.py @@ -86,7 +86,7 @@ package_data={ 'coverage': [ - 'htmlfiles/*.*', + 'htmlfiles/**/*.*', 'py.typed', ] },