Skip to content

Commit

Permalink
Merge pull request #113 from sadielbartholomew/ascii-banner
Browse files Browse the repository at this point in the history
Improve command output with text banner, colour and alignment
  • Loading branch information
abhidg authored Dec 2, 2024
2 parents 34c951e + fc105a5 commit 99737e8
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 21 deletions.
63 changes: 56 additions & 7 deletions cats/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .configure import get_runtime_config
from .forecast import CarbonIntensityAverageEstimate
from .optimise_starttime import get_avg_estimates # noqa: F401
from .constants import CATS_ASCII_BANNER_COLOUR, CATS_ASCII_BANNER_NO_COLOUR

__version__ = "1.0.0"

Expand Down Expand Up @@ -181,6 +182,17 @@ def positive_integer(string):
type=positive_integer,
help="Amount of memory used by the job, in GB",
)
parser.add_argument(
"-n",
"--no-colour",
action="store_true",
help="Disable all terminal output colouring",
)
parser.add_argument(
"--no-color",
action="store_true",
help="Disable all terminal output colouring (alias to --no-colour)",
)

return parser

Expand All @@ -194,18 +206,40 @@ class CATSOutput:
location: str
countryISO3: str
emmissionEstimate: Optional[Estimates] = None
colour: bool = False

def __str__(self) -> str:
out = f"""Best job start time {self.carbonIntensityOptimal.start}
Carbon intensity if job started now = {self.carbonIntensityNow.value:.2f} gCO2eq/kWh
Carbon intensity at optimal time = {self.carbonIntensityOptimal.value:.2f} gCO2eq/kWh"""
if self.colour:
# Default colour
col_normal = "\33[0m" # reset any colour

# Colours to indicate optimal/better results
col_dt_opt = "\33[32m" # green i.e. 'good' in traffic light rating
col_ci_opt = "\33[32m" # green
col_ee_opt = "\33[32m" # green

# Colours to indicate original and non-optimal results
col_ci_now = "\33[31m" # red i.e. 'bad' in traffic light rating
col_ee_now = "\33[31m" # red
else:
col_normal = ""
col_dt_opt = ""
col_ci_opt = ""
col_ci_now = ""
col_ee_now = ""
col_ee_opt = ""

out = f"""
Best job start time = {col_dt_opt}{self.carbonIntensityOptimal.start}{col_normal}
Carbon intensity if job started now = {col_ci_now}{self.carbonIntensityNow.value:.2f} gCO2eq/kWh{col_normal}
Carbon intensity at optimal time = {col_ci_opt}{self.carbonIntensityOptimal.value:.2f} gCO2eq/kWh{col_normal}"""

if self.emmissionEstimate:
out += f"""
Estimated emissions if job started now = {self.emmissionEstimate.now}
Estimated emissions at optimal time = {self.emmissionEstimate.best} (- {self.emmissionEstimate.savings})"""
Estimated emissions if job started now = {col_ee_now}{self.emmissionEstimate.now}{col_normal}
Estimated emissions at optimal time = {col_ee_opt}{self.emmissionEstimate.best} (- {self.emmissionEstimate.savings}){col_normal}"""

out += "\n\nUse --format=json to get this in machine readable format"
logging.info("Use '--format=json' to get this in machine readable format")
return out

def to_json(self, dateformat: str = "", **kwargs) -> str:
Expand All @@ -221,6 +255,15 @@ def to_json(self, dateformat: str = "", **kwargs) -> str:
return json.dumps(data, **kwargs)


def print_banner(disable_colour):
"""Print an ASCII art banner with the CATS title, optionally in colour.
"""
if disable_colour:
print(CATS_ASCII_BANNER_NO_COLOUR)
else:
print(CATS_ASCII_BANNER_COLOUR)


def schedule_at(
output: CATSOutput, args: list[str], at_command: str = "at"
) -> Optional[str]:
Expand Down Expand Up @@ -250,6 +293,11 @@ def schedule_at(
def main(arguments=None) -> int:
parser = parse_arguments()
args = parser.parse_args(arguments)
colour_output = args.no_colour or args.no_color

# Print CATS ASCII art banner, before any output from printing or logging
print_banner(colour_output)

if args.command and not args.scheduler:
print(
"cats: To run a command with the -c or --command option, you must\n"
Expand Down Expand Up @@ -286,7 +334,8 @@ def main(arguments=None) -> int:
# Find best possible average carbon intensity, along
# with corresponding job start time.
now_avg, best_avg = get_avg_estimates(CI_forecast, duration=duration)
output = CATSOutput(now_avg, best_avg, location, "GBR")
output = CATSOutput(
now_avg, best_avg, location, "GBR", colour=not colour_output)

################################
## Calculate carbon footprint ##
Expand Down
4 changes: 2 additions & 2 deletions cats/configure.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""This module exports a function :py:func:`configure
<cats.configure.configure>` that processes both command line arguments
and configuration file. This function returns a runtime configuration
for cats to make a request to a carcon intensity forecast provider. A
for cats to make a request to a carbon intensity forecast provider. A
runtime configuration consits of:
- location (postcode)
- job duration
- Interface to carbon intensity forecast provider (See TODO)
- interface to carbon intensity forecast provider (See TODO)
"""

Expand Down
19 changes: 19 additions & 0 deletions cats/constants.py
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@
MEMORY_POWER_PER_GB = 0.3725 # W/GB

CATS_ASCII_BANNER_COLOUR = """
\33[34mThe\33[35m.\33[34m\33[0m____ \33[35m..... \33[0m__ \33[35m.... \33[0m________ \33[35m. \33[0m______\33[35m...
\33[35m.. \33[0m/ __)\33[35m.....\33[0m/ \\\33[35m....\33[0m(__ __)\33[35m.\33[0m) ____)\33[35m....
\33[35m..\33[0m| /\33[35m.......\33[0m/ \\\33[35m......\33[0m| |\33[35m...\33[0m( (___\33[35m........
\33[35m..\33[0m| |\33[34mlimate\33[35m.\33[0m/ () \\\33[34mware\33[35m.\33[0m| |\33[34mask\33[35m.\33[0m\\___ \\\33[34mcheduler
\33[35m..\33[0m| \\__\33[35m...\33[0m| __ |\33[35m....\33[0m| |\33[35m....\33[0m____) )\33[35m....
\33[35m...\33[0m\\ )\33[35m..\33[0m| (\33[35m..\33[0m) |\33[35m....\33[0m| |\33[35m...\33[0m( (\33[35m..\33[0m
"""

# Same as above but without the colour codes, so not given custom colouring
CATS_ASCII_BANNER_NO_COLOUR = """
The.____ ..... __ .... ________ . ______ ...
.. / __)...../ \\....(__ __).) ____)....
..| /......./ \\......| |...( (___........
..| |limate./ () \\ware.| |ask.\\___ \\cheduler
..| \\__...| __ |....| |....____) )....
...\\ )..| (..) |....| |...( (..
"""
22 changes: 10 additions & 12 deletions tests/test_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,19 @@
[
(
OUTPUT,
"""Best job start time 2024-03-16 02:00:00
"""
Best job start time = 2024-03-16 02:00:00
Carbon intensity if job started now = 50.00 gCO2eq/kWh
Carbon intensity at optimal time = 20.00 gCO2eq/kWh
Use --format=json to get this in machine readable format""",
Carbon intensity at optimal time = 20.00 gCO2eq/kWh""",
),
(
OUTPUT_WITH_EMISSION_ESTIMATE,
"""Best job start time 2024-03-16 02:00:00
"""
Best job start time = 2024-03-16 02:00:00
Carbon intensity if job started now = 50.00 gCO2eq/kWh
Carbon intensity at optimal time = 20.00 gCO2eq/kWh
Estimated emissions if job started now = 19
Estimated emissions at optimal time = 9 (- 10)
Use --format=json to get this in machine readable format""",
Estimated emissions at optimal time = 9 (- 10)""",
),
],
)
Expand All @@ -62,13 +60,13 @@ def test_string_repr(output, expected):
OUTPUT,
"""{"carbonIntensityNow": {"end": "2024-03-15T17:00:00", "start": "2024-03-15T16:00:00", "value": 50}, """
""""carbonIntensityOptimal": {"end": "2024-03-16T03:00:00", "start": "2024-03-16T02:00:00", "value": 20}, """
""""countryISO3": "GBR", "emmissionEstimate": null, "location": "OX1"}""",
""""colour": false, "countryISO3": "GBR", "emmissionEstimate": null, "location": "OX1"}""",
),
(
OUTPUT_WITH_EMISSION_ESTIMATE,
"""{"carbonIntensityNow": {"end": "2024-03-15T17:00:00", "start": "2024-03-15T16:00:00", "value": 50}, """
""""carbonIntensityOptimal": {"end": "2024-03-16T03:00:00", "start": "2024-03-16T02:00:00", "value": 20}, """
""""countryISO3": "GBR", "emmissionEstimate": [19, 9, 10], "location": "OX1"}""",
""""colour": false, "countryISO3": "GBR", "emmissionEstimate": [19, 9, 10], "location": "OX1"}""",
),
],
)
Expand All @@ -83,13 +81,13 @@ def test_output_json(output, expected):
OUTPUT,
"""{"carbonIntensityNow": {"end": "202403151700", "start": "202403151600", "value": 50}, """
""""carbonIntensityOptimal": {"end": "202403160300", "start": "202403160200", "value": 20}, """
""""countryISO3": "GBR", "emmissionEstimate": null, "location": "OX1"}""",
""""colour": false, "countryISO3": "GBR", "emmissionEstimate": null, "location": "OX1"}""",
),
(
OUTPUT_WITH_EMISSION_ESTIMATE,
"""{"carbonIntensityNow": {"end": "202403151700", "start": "202403151600", "value": 50}, """
""""carbonIntensityOptimal": {"end": "202403160300", "start": "202403160200", "value": 20}, """
""""countryISO3": "GBR", "emmissionEstimate": [19, 9, 10], "location": "OX1"}""",
""""colour": false, "countryISO3": "GBR", "emmissionEstimate": [19, 9, 10], "location": "OX1"}""",
),
],
)
Expand Down

0 comments on commit 99737e8

Please sign in to comment.