├── examples ├── __init__.mojo ├── import.mojo ├── loop.mojo ├── default.mojo ├── json.mojo ├── logfmt.mojo ├── turn_off_styler.mojo ├── message_only.mojo └── custom.mojo ├── external ├── __init__.mojo ├── gojo │ ├── __init__.mojo │ ├── syscall │ │ ├── __init__.mojo │ │ ├── types.mojo │ │ └── file.mojo │ ├── fmt │ │ ├── __init__.mojo │ │ └── fmt.mojo │ ├── unicode │ │ ├── __init__.mojo │ │ └── utf8 │ │ │ ├── __init__.mojo │ │ │ └── runes.mojo │ ├── strings │ │ ├── __init__.mojo │ │ ├── builder.mojo │ │ └── reader.mojo │ ├── bytes │ │ ├── __init__.mojo │ │ └── reader.mojo │ ├── bufio │ │ └── __init__.mojo │ ├── net │ │ ├── __init__.mojo │ │ ├── dial.mojo │ │ ├── fd.mojo │ │ ├── address.mojo │ │ ├── net.mojo │ │ ├── ip.mojo │ │ └── tcp.mojo │ ├── builtins │ │ ├── __init__.mojo │ │ ├── errors.mojo │ │ ├── attributes.mojo │ │ ├── bytes.mojo │ │ └── list.mojo │ └── io │ │ ├── __init__.mojo │ │ └── traits.mojo ├── hue │ ├── __init__.mojo │ ├── math.mojo │ ├── happy_palettegen.mojo │ ├── warm_palettegen.mojo │ ├── color_gens.mojo │ ├── sort.mojo │ ├── soft_palettegen.mojo │ └── hsluv.mojo ├── morrow │ ├── __init__.mojo │ ├── _py.mojo │ ├── constants.mojo │ ├── util.mojo │ ├── timezone.mojo │ ├── _libc.mojo │ ├── timedelta.mojo │ ├── formatter.mojo │ └── morrow.mojo └── mist │ ├── notification.mojo │ ├── hyperlink.mojo │ ├── __init__.mojo │ ├── renderers.mojo │ ├── profile.mojo │ ├── ansi_colors.mojo │ ├── color.mojo │ └── style.mojo ├── tests ├── __init__.mojo └── test_performance.mojo ├── logger.png ├── .pre-commit-config.yaml ├── stump ├── __init__.mojo ├── base.mojo ├── style.mojo ├── processor.mojo └── formatter.mojo ├── run_examples.sh ├── .github └── workflows │ └── test.yml ├── LICENSE ├── .gitignore └── README.md /examples/__init__.mojo: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /external/__init__.mojo: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.mojo: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /external/gojo/__init__.mojo: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /external/gojo/syscall/__init__.mojo: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /external/hue/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .color import Color 2 | -------------------------------------------------------------------------------- /logger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thatstoasty/stump/HEAD/logger.png -------------------------------------------------------------------------------- /external/gojo/fmt/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .fmt import sprintf, printf, sprintf_str 2 | -------------------------------------------------------------------------------- /external/gojo/unicode/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .utf8 import string_iterator, rune_count_in_string 2 | -------------------------------------------------------------------------------- /examples/import.mojo: -------------------------------------------------------------------------------- 1 | from examples.default import logger 2 | 3 | 4 | fn main(): 5 | logger.info("Hello!") 6 | -------------------------------------------------------------------------------- /external/gojo/strings/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .builder import StringBuilder 2 | from .reader import Reader, new_reader 3 | -------------------------------------------------------------------------------- /external/gojo/bytes/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .buffer import Buffer, new_buffer 2 | from .reader import Reader, new_reader 3 | -------------------------------------------------------------------------------- /external/gojo/bufio/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .bufio import Reader, Writer, ReadWriter 2 | from .scan import Scanner, scan_words, scan_bytes, scan_lines 3 | -------------------------------------------------------------------------------- /external/morrow/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .morrow import Morrow 2 | from .timezone import TimeZone 3 | from .timedelta import TimeDelta 4 | 5 | alias __version__ = "0.3.1" 6 | -------------------------------------------------------------------------------- /external/gojo/net/__init__.mojo: -------------------------------------------------------------------------------- 1 | """Adapted from go's net package 2 | 3 | A good chunk of the leg work here came from the lightbug_http project! https://github.com/saviorand/lightbug_http/tree/main 4 | """ 5 | -------------------------------------------------------------------------------- /external/gojo/builtins/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .bytes import Byte, index_byte, has_suffix, has_prefix, to_string 2 | from .list import equals 3 | from .attributes import cap, copy 4 | from .errors import exit, panic 5 | 6 | alias Rune = Int32 7 | -------------------------------------------------------------------------------- /external/gojo/unicode/utf8/__init__.mojo: -------------------------------------------------------------------------------- 1 | """Almost all of the actual implementation in this module was written by @mzaks (https://github.com/mzaks)! 2 | This would not be possible without his help. 3 | """ 4 | from .runes import string_iterator, rune_count_in_string 5 | -------------------------------------------------------------------------------- /examples/loop.mojo: -------------------------------------------------------------------------------- 1 | from stump import get_logger 2 | from time import sleep 3 | 4 | alias logger = get_logger() 5 | 6 | 7 | fn main(): 8 | for i in range(1000): 9 | if i < 500: 10 | logger.warn("", iteration=i) 11 | else: 12 | logger.info("", iteration=i) 13 | -------------------------------------------------------------------------------- /external/morrow/_py.mojo: -------------------------------------------------------------------------------- 1 | from python import Python 2 | 3 | 4 | fn py_dt_datetime() raises -> PythonObject: 5 | var _datetime = Python.import_module("datetime") 6 | return _datetime.datetime 7 | 8 | 9 | fn py_time() raises -> PythonObject: 10 | var _time = Python.import_module("time") 11 | return _time 12 | -------------------------------------------------------------------------------- /external/mist/notification.mojo: -------------------------------------------------------------------------------- 1 | from .style import osc, st 2 | 3 | 4 | fn notify(title: String, body: String): 5 | """Sends a notification to the terminal. 6 | 7 | Args: 8 | title: The title of the notification. 9 | body: The body of the notification. 10 | """ 11 | print(osc + "777;notify;" + title + ";" + body + st, end="") 12 | -------------------------------------------------------------------------------- /external/gojo/builtins/errors.mojo: -------------------------------------------------------------------------------- 1 | from sys import exit 2 | 3 | 4 | fn panic[T: Stringable](message: T, code: Int = 1): 5 | """Panics the program with the given message and exit code. 6 | 7 | Args: 8 | message: The message to panic with. 9 | code: The exit code to panic with. 10 | """ 11 | print("panic:", message) 12 | exit(code) 13 | -------------------------------------------------------------------------------- /examples/default.mojo: -------------------------------------------------------------------------------- 1 | from stump import get_logger 2 | 3 | 4 | alias logger = get_logger() 5 | 6 | 7 | fn main(): 8 | logger.info("Information is good.", "key", "value") 9 | logger.warn("Warnings can be good too.", "no_value") 10 | logger.error("An error!", erroring=True) 11 | logger.fatal("uh oh...", "number", 4, "mojo", "🔥") 12 | logger.debug("Debugging...") 13 | -------------------------------------------------------------------------------- /external/mist/hyperlink.mojo: -------------------------------------------------------------------------------- 1 | from .style import osc, st 2 | 3 | 4 | fn hyperlink(link: String, name: String) -> String: 5 | """Creates a hyperlink using OSC8. 6 | 7 | Args: 8 | link: The URL to link to. 9 | name: The text to display. 10 | 11 | Returns: 12 | The hyperlink text. 13 | """ 14 | return osc + "8;;" + link + st + name + osc + "8;;" + st 15 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v2.3.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | - repo: local 9 | hooks: 10 | - id: mojo-format 11 | name: mojo-format 12 | entry: mojo format -l 120 13 | language: system 14 | files: '\.(mojo|🔥)$' 15 | stages: [commit] 16 | -------------------------------------------------------------------------------- /stump/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .base import DEBUG, INFO, WARN, ERROR, FATAL, LEVEL_MAPPING, Context 2 | from .formatter import ( 3 | JSON_FORMAT, 4 | DEFAULT_FORMAT, 5 | LOGFMT_FORMAT, 6 | ) 7 | from .log import BoundLogger, PrintLogger, Logger, get_logger 8 | from .processor import ( 9 | add_log_level, 10 | add_timestamp, 11 | add_timestamp_with_format, 12 | get_processors, 13 | Processor, 14 | ) 15 | from .style import Styles, Sections 16 | -------------------------------------------------------------------------------- /examples/json.mojo: -------------------------------------------------------------------------------- 1 | from stump import DEBUG, JSON_FORMAT, BoundLogger, PrintLogger 2 | 3 | 4 | # The loggers are compiled at build time, so we can reuse it. 5 | alias LOG_LEVEL = DEBUG 6 | alias logger = BoundLogger(PrintLogger(LOG_LEVEL), formatter=JSON_FORMAT) 7 | 8 | 9 | fn main(): 10 | logger.info("Information is good.", "arbitrary", "pairs", key="value") 11 | logger.warn("Warnings can be good too.") 12 | logger.error("An error!") 13 | logger.debug("Debugging...") 14 | logger.fatal("uh oh...") 15 | -------------------------------------------------------------------------------- /external/hue/math.mojo: -------------------------------------------------------------------------------- 1 | from math import max, min 2 | from math.limit import max_finite 3 | 4 | 5 | fn cube(v: Float64) -> Float64: 6 | return v * v * v 7 | 8 | 9 | fn sq(v: Float64) -> Float64: 10 | return v * v 11 | 12 | 13 | fn clamp01(v: Float64) -> Float64: 14 | """Clamps from 0 to 1.""" 15 | return max(0.0, min(v, 1.0)) 16 | 17 | 18 | alias pi: Float64 = 3.141592653589793238462643383279502884197169399375105820974944592307816406286 19 | alias max_float64: Float64 = max_finite[DType.float64]() 20 | -------------------------------------------------------------------------------- /examples/logfmt.mojo: -------------------------------------------------------------------------------- 1 | from stump import DEBUG, LOGFMT_FORMAT, BoundLogger, PrintLogger 2 | 3 | 4 | # The loggers are compiled at build time, so we can reuse it. 5 | alias LOG_LEVEL = DEBUG 6 | alias logger = BoundLogger(PrintLogger(LOG_LEVEL), formatter=LOGFMT_FORMAT) 7 | 8 | 9 | fn main(): 10 | logger.info("Information is good.", "arbitrary", "pairs", key="value") 11 | logger.warn("Warnings can be good too.") 12 | logger.error("An error!") 13 | logger.debug("Debugging...") 14 | logger.fatal("uh oh...") 15 | -------------------------------------------------------------------------------- /examples/turn_off_styler.mojo: -------------------------------------------------------------------------------- 1 | from stump import DEBUG, LOGFMT_FORMAT, BoundLogger, PrintLogger 2 | 3 | 4 | # The loggers are compiled at build time, so we can reuse it. 5 | alias LOG_LEVEL = DEBUG 6 | alias logger = BoundLogger(PrintLogger(LOG_LEVEL), formatter=LOGFMT_FORMAT, apply_styles=False) 7 | 8 | 9 | fn main(): 10 | logger.info("Information is good.", "arbitrary", "pairs", key="value") 11 | logger.warn("Warnings can be good too.") 12 | logger.error("An error!") 13 | logger.debug("Debugging...") 14 | logger.fatal("uh oh...") 15 | -------------------------------------------------------------------------------- /external/mist/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .color import Color 2 | from .style import TerminalStyle 3 | from .profile import Profile, ASCII, ANSI, ANSI256, TRUE_COLOR, AnyColor, NoColor 4 | from .renderers import ( 5 | as_color, 6 | with_background_color, 7 | red, 8 | green, 9 | blue, 10 | yellow, 11 | cyan, 12 | gray, 13 | magenta, 14 | red_background, 15 | green_background, 16 | blue_background, 17 | yellow_background, 18 | cyan_background, 19 | gray_background, 20 | magenta_background, 21 | bold, 22 | italic, 23 | underline, 24 | faint, 25 | crossout, 26 | overline, 27 | ) 28 | -------------------------------------------------------------------------------- /external/gojo/io/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .traits import ( 2 | Reader, 3 | Writer, 4 | Seeker, 5 | Closer, 6 | ReadWriter, 7 | ReadCloser, 8 | WriteCloser, 9 | ReadWriteCloser, 10 | ReadSeeker, 11 | ReadSeekCloser, 12 | WriteSeeker, 13 | ReadWriteSeeker, 14 | ReaderFrom, 15 | WriterReadFrom, 16 | WriterTo, 17 | ReaderWriteTo, 18 | ReaderAt, 19 | WriterAt, 20 | ByteReader, 21 | ByteScanner, 22 | ByteWriter, 23 | RuneReader, 24 | RuneScanner, 25 | StringWriter, 26 | SEEK_START, 27 | SEEK_CURRENT, 28 | SEEK_END, 29 | ERR_SHORT_WRITE, 30 | ERR_NO_PROGRESS, 31 | ERR_SHORT_BUFFER, 32 | EOF, 33 | ) 34 | from .io import write_string, read_at_least, read_full, read_all, BUFFER_SIZE 35 | -------------------------------------------------------------------------------- /tests/test_performance.mojo: -------------------------------------------------------------------------------- 1 | from stump import get_logger 2 | from time import now 3 | 4 | alias logger = get_logger() 5 | 6 | 7 | fn test_default_logger(): 8 | var print_start = now() 9 | print("Testing print...") 10 | var print_duration = now() - print_start 11 | 12 | var logger_start = now() 13 | logger.info("Testing print...") 14 | var logger_duration = now() - logger_start 15 | 16 | print("print:", "(", print_duration, "ns)") 17 | print("logger:", "(", logger_duration, "ns)") 18 | print( 19 | "Performance difference: ", 20 | str(logger_duration - print_duration) + "ns", 21 | ": Print is ", 22 | str(logger_duration / print_duration) + "x faster", 23 | ) 24 | 25 | 26 | fn main(): 27 | test_default_logger() 28 | -------------------------------------------------------------------------------- /run_examples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mkdir ./temp 3 | mojo package stump -I ./external -o ./temp/stump.mojopkg 4 | 5 | echo -e "Building binaries for all examples...\n" 6 | mojo build examples/custom.mojo -o temp/custom 7 | mojo build examples/default.mojo -o temp/default 8 | mojo build examples/import.mojo -o temp/import 9 | mojo build examples/json.mojo -o temp/json 10 | mojo build examples/logfmt.mojo -o temp/logfmt 11 | mojo build examples/loop.mojo -o temp/loop 12 | mojo build examples/message_only.mojo -o temp/message_only 13 | mojo build examples/turn_off_styler.mojo -o temp/turn_off_styler 14 | 15 | echo -e "Executing examples...\n" 16 | cd temp 17 | ./custom 18 | ./default 19 | ./import 20 | ./json 21 | ./logfmt 22 | ./message_only 23 | ./turn_off_styler 24 | 25 | cd .. 26 | rm -R ./temp 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: ["push"] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | environment: basic 9 | steps: 10 | - name: Check out repository code 11 | uses: actions/checkout@v2 12 | - name: Install dependencies 13 | run: | 14 | curl https://get.modular.com | MODULAR_AUTH=${{ secrets.MODULAR_AUTH }} sh - 15 | modular auth ${{ secrets.MODULAR_AUTH }} 16 | modular install nightly/mojo 17 | pip install pytest 18 | pip install git+https://github.com/guidorice/mojo-pytest.git 19 | - name: Unit Tests 20 | run: | 21 | export MODULAR_HOME="/home/runner/.modular" 22 | export PATH="/home/runner/.modular/pkg/packages.modular.com_nightly_mojo/bin:$PATH" 23 | # pytest 24 | bash run_examples.sh 25 | -------------------------------------------------------------------------------- /stump/base.mojo: -------------------------------------------------------------------------------- 1 | from collections.dict import Dict, KeyElement, DictEntry 2 | 3 | alias FATAL = 0 4 | alias ERROR = 1 5 | alias WARN = 2 6 | alias INFO = 3 7 | alias DEBUG = 4 8 | 9 | alias LEVEL_MAPPING = List[String]( 10 | "FATAL", 11 | "ERROR", 12 | "WARN", 13 | "INFO", 14 | "DEBUG", 15 | ) 16 | 17 | @value 18 | struct StringKey(KeyElement): 19 | var s: String 20 | 21 | fn __init__(inout self, owned s: String): 22 | self.s = s ^ 23 | 24 | fn __init__(inout self, s: StringLiteral): 25 | self.s = String(s) 26 | 27 | fn __hash__(self) -> Int: 28 | return hash(self.s) 29 | 30 | fn __eq__(self, other: Self) -> Bool: 31 | return self.s == other.s 32 | 33 | fn __ne__(self, other: Self) -> Bool: 34 | return self.s != other.s 35 | 36 | fn __str__(self) -> String: 37 | return self.s 38 | 39 | 40 | alias Context = Dict[StringKey, String] 41 | alias ContextPair = DictEntry[StringKey, String] 42 | -------------------------------------------------------------------------------- /external/gojo/builtins/attributes.mojo: -------------------------------------------------------------------------------- 1 | fn copy[T: CollectionElement](inout target: List[T], source: List[T], start: Int = 0) -> Int: 2 | """Copies the contents of source into target at the same index. Returns the number of bytes copied. 3 | Added a start parameter to specify the index to start copying into. 4 | 5 | Args: 6 | target: The buffer to copy into. 7 | source: The buffer to copy from. 8 | start: The index to start copying into. 9 | 10 | Returns: 11 | The number of bytes copied. 12 | """ 13 | var count = 0 14 | 15 | for i in range(len(source)): 16 | if i + start > len(target): 17 | target[i + start] = source[i] 18 | else: 19 | target.append(source[i]) 20 | count += 1 21 | 22 | return count 23 | 24 | 25 | fn cap[T: CollectionElement](iterable: List[T]) -> Int: 26 | """Returns the capacity of the List. 27 | 28 | Args: 29 | iterable: The List to get the capacity of. 30 | """ 31 | return iterable.capacity 32 | -------------------------------------------------------------------------------- /examples/message_only.mojo: -------------------------------------------------------------------------------- 1 | from stump import ( 2 | DEBUG, 3 | DEFAULT_FORMAT, 4 | Processor, 5 | Context, 6 | Styles, 7 | Sections, 8 | BoundLogger, 9 | PrintLogger, 10 | add_log_level, 11 | add_timestamp, 12 | add_timestamp_with_format, 13 | ) 14 | from external.mist import TerminalStyle, Profile, TRUE_COLOR 15 | 16 | 17 | # Define custom processors to add extra information to the log output. 18 | fn my_processors() -> List[Processor]: 19 | return List[Processor]() 20 | 21 | 22 | # The loggers are compiled at build time, so we can reuse it. 23 | alias LOG_LEVEL = DEBUG 24 | 25 | # Build a bound logger with custom processors and styling 26 | alias logger = BoundLogger( 27 | PrintLogger(LOG_LEVEL), 28 | formatter=DEFAULT_FORMAT, 29 | processors=my_processors, 30 | ) 31 | 32 | 33 | fn main(): 34 | logger.info("Information is good.") 35 | logger.warn("Warnings can be good too.") 36 | logger.error("An error!", erroring=True) 37 | logger.debug("Debugging...") 38 | logger.fatal("uh oh...") 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mikhail Tavarez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /external/hue/happy_palettegen.mojo: -------------------------------------------------------------------------------- 1 | from random import randn_float64 2 | from .color import Color, hsv, lab_to_hcl 3 | from .soft_palettegen import soft_palette_ex, SoftPaletteSettings 4 | 5 | 6 | fn fast_happy_palette(colors_count: Int) -> List[Color]: 7 | """Uses the HSV color space to generate colors with similar S,V but distributed 8 | evenly along their Hue. This is fast but not always pretty. 9 | If you've got time to spare, use Lab (the non-fast below).""" 10 | var colors = List[Color](capacity=colors_count) 11 | for i in range(colors_count): 12 | colors.append(Color(0, 0, 0)) 13 | 14 | var i = 0 15 | while i < colors_count: 16 | colors[i] = hsv( 17 | Float64(i) * (360.0 / Float64(colors_count)), 0.8 + randn_float64() * 0.2, 0.65 + randn_float64() * 0.2 18 | ) 19 | i += 1 20 | 21 | return colors 22 | 23 | 24 | fn happy_palette(colors_count: Int) raises -> List[Color]: 25 | fn pimpy(l: Float64, a: Float64, b: Float64) -> Bool: 26 | var h: Float64 27 | var c: Float64 28 | var l_new: Float64 29 | l_new, c, h = lab_to_hcl(l, a, b) 30 | return 0.3 <= c and 0.4 <= l and l <= 0.8 31 | 32 | return soft_palette_ex(colors_count, SoftPaletteSettings(pimpy, 50, True)) 33 | -------------------------------------------------------------------------------- /external/hue/warm_palettegen.mojo: -------------------------------------------------------------------------------- 1 | from random import randn_float64 2 | from .color import hsv, lab_to_hcl 3 | from .soft_palettegen import soft_palette_ex, SoftPaletteSettings 4 | 5 | 6 | fn fast_warm_palette(colors_count: Int) -> List[Color]: 7 | """Uses the hsv color space to generate colors with similar S,V but distributed 8 | evenly along their Hue. This is fast but not always pretty. 9 | If you've got time to spare, use Lab (the non-fast below). 10 | 11 | Args: 12 | colors_count: The number of colors to generate. 13 | 14 | Returns: 15 | A list of colors. 16 | """ 17 | var colors = List[Color](capacity=colors_count) 18 | for i in range(colors_count): 19 | colors.append(Color(0, 0, 0)) 20 | 21 | var i = 0 22 | while i < colors_count: 23 | colors[i] = hsv( 24 | Float64(i) * (360.0 / Float64(colors_count)), 0.55 + randn_float64() * 0.2, 0.35 + randn_float64() * 0.2 25 | ) 26 | i += 1 27 | 28 | return colors 29 | 30 | 31 | fn warm_palette(colors_count: Int) raises -> List[Color]: 32 | fn warmy(l: Float64, a: Float64, b: Float64) -> Bool: 33 | var h: Float64 34 | var c: Float64 35 | var l_new: Float64 36 | h, c, l_new = lab_to_hcl(l, a, b) 37 | return 0.1 <= c and c <= 0.4 and 0.2 <= l and l <= 0.5 38 | 39 | return soft_palette_ex(colors_count, SoftPaletteSettings(warmy, 50, True)) 40 | -------------------------------------------------------------------------------- /external/morrow/constants.mojo: -------------------------------------------------------------------------------- 1 | # todo: hardcode for tmp 2 | alias _MAX_TIMESTAMP: Int = 32503737600 3 | alias MAX_TIMESTAMP = _MAX_TIMESTAMP 4 | alias MAX_TIMESTAMP_MS = MAX_TIMESTAMP * 1000 5 | alias MAX_TIMESTAMP_US = MAX_TIMESTAMP * 1_000_000 6 | 7 | alias _DAYS_IN_MONTH = VariadicList[Int]( 8 | -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 9 | ) 10 | alias _DAYS_BEFORE_MONTH = VariadicList[Int]( 11 | -1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 12 | ) # -1 is a placeholder for indexing purposes. 13 | 14 | 15 | alias MONTH_NAMES = StaticTuple[StringLiteral, 13]( 16 | "", 17 | "January", 18 | "February", 19 | "March", 20 | "April", 21 | "May", 22 | "June", 23 | "July", 24 | "August", 25 | "September", 26 | "October", 27 | "November", 28 | "December", 29 | ) 30 | 31 | alias MONTH_ABBREVIATIONS = StaticTuple[StringLiteral, 13]( 32 | "", 33 | "Jan", 34 | "Feb", 35 | "Mar", 36 | "Apr", 37 | "May", 38 | "Jun", 39 | "Jul", 40 | "Aug", 41 | "Sep", 42 | "Oct", 43 | "Nov", 44 | "Dec", 45 | ) 46 | 47 | alias DAY_NAMES = StaticTuple[StringLiteral, 8]( 48 | "", 49 | "Monday", 50 | "Tuesday", 51 | "Wednesday", 52 | "Thursday", 53 | "Friday", 54 | "Saturday", 55 | "Sunday", 56 | ) 57 | alias DAY_ABBREVIATIONS = StaticTuple[StringLiteral, 8]( 58 | "", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" 59 | ) 60 | -------------------------------------------------------------------------------- /external/hue/color_gens.mojo: -------------------------------------------------------------------------------- 1 | from random import randn_float64 2 | from .color import Color, hsv, hcl 3 | 4 | # Various ways to generate single random colors 5 | 6 | 7 | fn fast_warm_color() -> Color: 8 | """Creates a random dark, "warm" color through a restricted HSV space.""" 9 | return hsv(randn_float64() * 360.0, 0.5 + randn_float64() * 0.3, 0.3 + randn_float64() * 0.3) 10 | 11 | 12 | fn warm_color() -> Color: 13 | """Creates a random dark, "warm" color through restricted HCL space. 14 | This is slower than FastWarmColor but will likely give you colors which have 15 | the same "warmness" if you run it many times.""" 16 | var c = random_warm() 17 | while not c.is_valid(): 18 | c = random_warm() 19 | 20 | return c 21 | 22 | 23 | fn random_warm() -> Color: 24 | return hcl(randn_float64() * 360.0, 0.1 + randn_float64() * 0.3, 0.2 + randn_float64() * 0.3) 25 | 26 | 27 | fn fast_happy_color() -> Color: 28 | """Creates a random bright, "pimpy" color through a restricted HSV space.""" 29 | return hsv(randn_float64() * 360.0, 0.7 + randn_float64() * 0.3, 0.6 + randn_float64() * 0.3) 30 | 31 | 32 | fn happy_color() -> Color: 33 | """Creates a random bright, "pimpy" color through restricted HCL space. 34 | This is slower than FastHappyColor but will likely give you colors which 35 | have the same "brightness" if you run it many times.""" 36 | var c = random_pimp() 37 | while not c.is_valid(): 38 | c = random_pimp() 39 | 40 | return c 41 | 42 | 43 | fn random_pimp() -> Color: 44 | return hcl(randn_float64() * 360.0, 0.5 + randn_float64() * 0.3, 0.5 + randn_float64() * 0.3) 45 | -------------------------------------------------------------------------------- /external/gojo/net/dial.mojo: -------------------------------------------------------------------------------- 1 | from .tcp import TCPAddr, TCPConnection, resolve_internet_addr 2 | from .socket import Socket 3 | from .address import split_host_port 4 | 5 | 6 | @value 7 | struct Dialer: 8 | var local_address: TCPAddr 9 | 10 | fn dial(self, network: String, address: String) raises -> TCPConnection: 11 | var tcp_addr = resolve_internet_addr(network, address) 12 | var socket = Socket(local_address=self.local_address) 13 | socket.connect(tcp_addr.ip, tcp_addr.port) 14 | print(String("Connected to ") + socket.remote_address) 15 | return TCPConnection(socket^) 16 | 17 | 18 | fn dial_tcp(network: String, remote_address: TCPAddr) raises -> TCPConnection: 19 | """Connects to the address on the named network. 20 | 21 | The network must be "tcp", "tcp4", or "tcp6". 22 | Args: 23 | network: The network type. 24 | remote_address: The remote address to connect to. 25 | 26 | Returns: 27 | The TCP connection. 28 | """ 29 | # TODO: Add conversion of domain name to ip address 30 | return Dialer(remote_address).dial(network, remote_address.ip + ":" + str(remote_address.port)) 31 | 32 | 33 | fn dial_tcp(network: String, remote_address: String) raises -> TCPConnection: 34 | """Connects to the address on the named network. 35 | 36 | The network must be "tcp", "tcp4", or "tcp6". 37 | Args: 38 | network: The network type. 39 | remote_address: The remote address to connect to. 40 | 41 | Returns: 42 | The TCP connection. 43 | """ 44 | var address = split_host_port(remote_address) 45 | return Dialer(TCPAddr(address.host, address.port)).dial(network, remote_address) 46 | -------------------------------------------------------------------------------- /external/gojo/syscall/types.mojo: -------------------------------------------------------------------------------- 1 | @value 2 | struct Str: 3 | var vector: List[c_char] 4 | 5 | fn __init__(inout self, string: String): 6 | self.vector = List[c_char](capacity=len(string) + 1) 7 | for i in range(len(string)): 8 | self.vector.append(ord(string[i])) 9 | self.vector.append(0) 10 | 11 | fn __init__(inout self, size: Int): 12 | self.vector = List[c_char]() 13 | self.vector.resize(size + 1, 0) 14 | 15 | fn __len__(self) -> Int: 16 | for i in range(len(self.vector)): 17 | if self.vector[i] == 0: 18 | return i 19 | return -1 20 | 21 | fn to_string(self, size: Int) -> String: 22 | var result: String = "" 23 | for i in range(size): 24 | result += chr(int(self.vector[i])) 25 | return result 26 | 27 | fn __enter__(owned self: Self) -> Self: 28 | return self^ 29 | 30 | 31 | fn strlen(s: Pointer[c_char]) -> c_size_t: 32 | """Libc POSIX `strlen` function 33 | Reference: https://man7.org/linux/man-pages/man3/strlen.3p.html 34 | Fn signature: size_t strlen(const char *s). 35 | 36 | Args: s: A pointer to a C string. 37 | Returns: The length of the string. 38 | """ 39 | return external_call["strlen", c_size_t, Pointer[c_char]](s) 40 | 41 | 42 | # Adapted from https://github.com/crisadamo/mojo-Libc . Huge thanks to Cristian! 43 | # C types 44 | alias c_void = UInt8 45 | alias c_char = UInt8 46 | alias c_schar = Int8 47 | alias c_uchar = UInt8 48 | alias c_short = Int16 49 | alias c_ushort = UInt16 50 | alias c_int = Int32 51 | alias c_uint = UInt32 52 | alias c_long = Int64 53 | alias c_ulong = UInt64 54 | alias c_float = Float32 55 | alias c_double = Float64 56 | 57 | # `Int` is known to be machine's width 58 | alias c_size_t = Int 59 | alias c_ssize_t = Int 60 | 61 | alias ptrdiff_t = Int64 62 | alias intptr_t = Int64 63 | alias uintptr_t = UInt64 64 | -------------------------------------------------------------------------------- /external/gojo/builtins/bytes.mojo: -------------------------------------------------------------------------------- 1 | from .list import equals 2 | 3 | 4 | alias Byte = Int8 5 | 6 | 7 | fn has_prefix(bytes: List[Byte], prefix: List[Byte]) -> Bool: 8 | """Reports whether the List[Byte] struct begins with prefix. 9 | 10 | Args: 11 | bytes: The List[Byte] struct to search. 12 | prefix: The prefix to search for. 13 | 14 | Returns: 15 | True if the List[Byte] struct begins with prefix; otherwise, False. 16 | """ 17 | var len_comparison = len(bytes) >= len(prefix) 18 | var prefix_comparison = equals(bytes[0 : len(prefix)], prefix) 19 | return len_comparison and prefix_comparison 20 | 21 | 22 | fn has_suffix(bytes: List[Byte], suffix: List[Byte]) -> Bool: 23 | """Reports whether the List[Byte] struct ends with suffix. 24 | 25 | Args: 26 | bytes: The List[Byte] struct to search. 27 | suffix: The prefix to search for. 28 | 29 | Returns: 30 | True if the List[Byte] struct ends with suffix; otherwise, False. 31 | """ 32 | var len_comparison = len(bytes) >= len(suffix) 33 | var suffix_comparison = equals(bytes[len(bytes) - len(suffix) : len(bytes)], suffix) 34 | return len_comparison and suffix_comparison 35 | 36 | 37 | fn index_byte(bytes: List[Byte], delim: Byte) -> Int: 38 | """Return the index of the first occurrence of the byte delim. 39 | 40 | Args: 41 | bytes: The List[Byte] struct to search. 42 | delim: The byte to search for. 43 | 44 | Returns: 45 | The index of the first occurrence of the byte delim. 46 | """ 47 | var i = 0 48 | for i in range(len(bytes)): 49 | if bytes[i] == delim: 50 | return i 51 | 52 | return -1 53 | 54 | 55 | fn to_string(bytes: List[Byte]) -> String: 56 | """Makes a deepcopy of the List[Byte] supplied and converts it to a string. If it's not null terminated, it will append a null byte. 57 | 58 | Args: 59 | bytes: The List[Byte] struct to convert. 60 | 61 | Returns: 62 | The string representation of the List[Byte] struct. 63 | """ 64 | var copy = List[Byte](bytes) 65 | if copy[-1] != 0: 66 | copy.append(0) 67 | return String(copy) 68 | -------------------------------------------------------------------------------- /examples/custom.mojo: -------------------------------------------------------------------------------- 1 | from stump import ( 2 | DEBUG, 3 | DEFAULT_FORMAT, 4 | Processor, 5 | Context, 6 | Styles, 7 | Sections, 8 | BoundLogger, 9 | PrintLogger, 10 | add_log_level, 11 | add_timestamp, 12 | add_timestamp_with_format, 13 | ) 14 | from external.mist import TerminalStyle, Profile, TRUE_COLOR 15 | 16 | 17 | # Define a custom processor to add a name to the log output. 18 | fn add_my_name(context: Context, level: String) -> Context: 19 | var new_context = Context(context) 20 | new_context["name"] = "Mikhail" 21 | return new_context 22 | 23 | 24 | # Define custom processors to add extra information to the log output. 25 | fn my_processors() -> List[Processor]: 26 | return List[Processor]( 27 | add_log_level, add_timestamp_with_format["YYYY"](), add_my_name 28 | ) 29 | 30 | 31 | # Define custom styles to format and colorize the log output. 32 | fn my_styles() -> Styles: 33 | # Log level styles, by default just set colors 34 | var levels = Sections() 35 | levels["FATAL"] = TerminalStyle.new().background("#d4317d") 36 | levels["ERROR"] = TerminalStyle.new().background("#d48244") 37 | levels["INFO"] = TerminalStyle.new().background("#13ed84") 38 | levels["WARN"] = TerminalStyle.new().background("#decf2f") 39 | levels["DEBUG"] = TerminalStyle.new().background("#bd37db") 40 | 41 | var keys = Sections() 42 | keys["name"] = TerminalStyle.new().foreground("#c9a0dc").underline() 43 | 44 | var values = Sections() 45 | values["name"] = TerminalStyle.new().foreground("#d48244").bold() 46 | 47 | return Styles( 48 | levels=levels, 49 | key=TerminalStyle.new().faint(), 50 | separator=TerminalStyle.new().faint(), 51 | keys=keys, 52 | values=values, 53 | ) 54 | 55 | 56 | # The loggers are compiled at build time, so we can reuse it. 57 | alias LOG_LEVEL = DEBUG 58 | 59 | # Build a bound logger with custom processors and styling 60 | alias logger = BoundLogger( 61 | PrintLogger(LOG_LEVEL), 62 | formatter=DEFAULT_FORMAT, 63 | processors=my_processors, 64 | styles=my_styles, 65 | ) 66 | 67 | 68 | fn main(): 69 | logger.info("Information is good.") 70 | logger.warn("Warnings can be good too.") 71 | logger.error("An error!", erroring=True) 72 | logger.debug("Debugging...") 73 | logger.fatal("uh oh...") 74 | -------------------------------------------------------------------------------- /external/morrow/util.mojo: -------------------------------------------------------------------------------- 1 | from collections.vector import DynamicVector 2 | 3 | from .constants import MAX_TIMESTAMP, MAX_TIMESTAMP_MS, MAX_TIMESTAMP_US 4 | from .constants import _DAYS_IN_MONTH, _DAYS_BEFORE_MONTH 5 | 6 | 7 | fn _is_leap(year: Int) -> Bool: 8 | "year -> 1 if leap year, else 0." 9 | return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) 10 | 11 | 12 | def _days_before_year(year: Int) -> Int: 13 | "year -> number of days before January 1st of year." 14 | var y = year - 1 15 | return y * 365 + y // 4 - y // 100 + y // 400 16 | 17 | 18 | def _days_in_month(year: Int, month: Int) -> Int: 19 | "year, month -> number of days in that month in that year." 20 | if month == 2 and _is_leap(year): 21 | return 29 22 | return _DAYS_IN_MONTH[month] 23 | 24 | 25 | def _days_before_month(year: Int, month: Int) -> Int: 26 | "year, month -> number of days in year preceding first day of month." 27 | if month > 2 and _is_leap(year): 28 | return _DAYS_BEFORE_MONTH[month] + 1 29 | return _DAYS_BEFORE_MONTH[month] 30 | 31 | 32 | @always_inline 33 | def _ymd2ord(year: Int, month: Int, day: Int) -> Int: 34 | "year, month, day -> ordinal, considering 01-Jan-0001 as day 1." 35 | dim = _days_in_month(year, month) 36 | return _days_before_year(year) + _days_before_month(year, month) + day 37 | 38 | 39 | def normalize_timestamp(timestamp: Float64) -> Float64: 40 | """Normalize millisecond and microsecond timestamps into normal timestamps.""" 41 | if timestamp > MAX_TIMESTAMP: 42 | if timestamp < MAX_TIMESTAMP_MS: 43 | timestamp /= 1000 44 | elif timestamp < MAX_TIMESTAMP_US: 45 | timestamp /= 1_000_000 46 | else: 47 | raise Error( 48 | "The specified timestamp " + String(timestamp) + "is too large." 49 | ) 50 | return timestamp 51 | 52 | 53 | fn _repeat_string(string: String, n: Int) -> String: 54 | var result: String = "" 55 | for _ in range(n): 56 | result += string 57 | return result 58 | 59 | 60 | fn rjust(string: String, width: Int, fillchar: String = " ") -> String: 61 | var extra = width - len(string) 62 | return _repeat_string(fillchar, extra) + string 63 | 64 | 65 | fn rjust(string: Int, width: Int, fillchar: String = " ") -> String: 66 | return rjust(String(string), width, fillchar) 67 | -------------------------------------------------------------------------------- /external/morrow/timezone.mojo: -------------------------------------------------------------------------------- 1 | from .util import rjust 2 | from ._libc import c_localtime 3 | 4 | alias UTC_TZ = TimeZone(0, "UTC") 5 | 6 | 7 | @value 8 | struct TimeZone(Stringable): 9 | var offset: Int 10 | var name: String 11 | 12 | fn __init__(inout self, offset: Int, name: String = ""): 13 | self.offset = offset 14 | self.name = name 15 | 16 | fn __str__(self) -> String: 17 | return self.name 18 | 19 | fn is_none(self) -> Bool: 20 | return self.name == "None" 21 | 22 | @staticmethod 23 | fn none() -> TimeZone: 24 | return TimeZone(0, "None") 25 | 26 | @staticmethod 27 | fn local() -> TimeZone: 28 | var local_t = c_localtime(0) 29 | return TimeZone(local_t.tm_gmtoff.to_int(), "local") 30 | 31 | @staticmethod 32 | fn from_utc(utc_str: String) raises -> TimeZone: 33 | if len(utc_str) == 0: 34 | raise Error("utc_str is empty") 35 | if utc_str == "utc" or utc_str == "UTC" or utc_str == "Z": 36 | return TimeZone(0, "utc") 37 | var p = 3 if len(utc_str) > 3 and utc_str[0:3] == "UTC" else 0 38 | 39 | var sign = -1 if utc_str[p] == "-" else 1 40 | if utc_str[p] == "+" or utc_str[p] == "-": 41 | p += 1 42 | 43 | if ( 44 | len(utc_str) < p + 2 45 | or not isdigit(ord(utc_str[p])) 46 | or not isdigit(ord(utc_str[p + 1])) 47 | ): 48 | raise Error("utc_str format is invalid") 49 | var hours: Int = atol(utc_str[p : p + 2]) 50 | p += 2 51 | 52 | var minutes: Int 53 | if len(utc_str) <= p: 54 | minutes = 0 55 | elif len(utc_str) == p + 3 and utc_str[p] == ":": 56 | minutes = atol(utc_str[p + 1 : p + 3]) 57 | elif len(utc_str) == p + 2 and isdigit(ord(utc_str[p])): 58 | minutes = atol(utc_str[p : p + 2]) 59 | else: 60 | minutes = 0 61 | raise Error("utc_str format is invalid") 62 | var offset: Int = sign * (hours * 3600 + minutes * 60) 63 | return TimeZone(offset) 64 | 65 | fn format(self, sep: String = ":") -> String: 66 | var sign: String 67 | var offset_abs: Int 68 | if self.offset < 0: 69 | sign = "-" 70 | offset_abs = -self.offset 71 | else: 72 | sign = "+" 73 | offset_abs = self.offset 74 | var hh = offset_abs // 3600 75 | var mm = offset_abs % 3600 76 | return sign + rjust(hh, 2, "0") + sep + rjust(mm, 2, "0") 77 | -------------------------------------------------------------------------------- /stump/style.mojo: -------------------------------------------------------------------------------- 1 | from collections.dict import Dict, KeyElement, DictEntry 2 | from external.mist import TerminalStyle, Profile, TRUE_COLOR 3 | from .base import StringKey, FATAL, INFO, DEBUG, WARN, ERROR 4 | 5 | 6 | alias Sections = Dict[StringKey, TerminalStyle] 7 | 8 | 9 | # TODO: For now setting profile each time, it doesn't seem like os.getenv works at comp time? 10 | @value 11 | struct Styles: 12 | var timestamp: TerminalStyle 13 | var message: TerminalStyle 14 | var key: TerminalStyle 15 | var value: TerminalStyle 16 | var separator: TerminalStyle 17 | var levels: Dict[StringKey, TerminalStyle] 18 | var keys: Dict[StringKey, TerminalStyle] 19 | var values: Dict[StringKey, TerminalStyle] 20 | 21 | fn __init__( 22 | inout self, 23 | *, 24 | timestamp: TerminalStyle = TerminalStyle.new(Profile(TRUE_COLOR)), 25 | message: TerminalStyle = TerminalStyle.new(Profile(TRUE_COLOR)), 26 | key: TerminalStyle = TerminalStyle.new(Profile(TRUE_COLOR)), 27 | value: TerminalStyle = TerminalStyle.new(Profile(TRUE_COLOR)), 28 | separator: TerminalStyle = TerminalStyle.new(Profile(TRUE_COLOR)), 29 | levels: Dict[StringKey, TerminalStyle] = Dict[StringKey, TerminalStyle](), 30 | keys: Dict[StringKey, TerminalStyle] = Dict[StringKey, TerminalStyle](), 31 | values: Dict[StringKey, TerminalStyle] = Dict[StringKey, TerminalStyle](), 32 | ): 33 | self.timestamp = timestamp 34 | self.message = message 35 | self.key = key 36 | self.value = value 37 | self.separator = separator 38 | self.levels = levels 39 | self.keys = keys 40 | self.values = values 41 | 42 | 43 | fn get_default_styles() -> Styles: 44 | # Log level styles, by default just set colors 45 | var levels = Sections() 46 | levels["FATAL"] = TerminalStyle.new().foreground("#d4317d") 47 | levels["ERROR"] = TerminalStyle.new().foreground("#d48244") 48 | levels["INFO"] = TerminalStyle.new().foreground("#13ed84") 49 | levels["WARN"] = TerminalStyle.new().foreground("#decf2f") 50 | levels["DEBUG"] = TerminalStyle.new().foreground("#bd37db") 51 | 52 | return Styles( 53 | timestamp=TerminalStyle.new(), 54 | message=TerminalStyle.new(), 55 | key=TerminalStyle.new().faint(), 56 | value=TerminalStyle.new(), 57 | separator=TerminalStyle.new().faint(), 58 | levels=levels, 59 | keys=Dict[StringKey, TerminalStyle](), 60 | values=Dict[StringKey, TerminalStyle](), 61 | ) 62 | 63 | 64 | alias DEFAULT_STYLES = get_default_styles() 65 | -------------------------------------------------------------------------------- /external/gojo/net/fd.mojo: -------------------------------------------------------------------------------- 1 | from collections.optional import Optional 2 | import ..io 3 | from ..builtins import Byte 4 | from ..syscall.file import close 5 | from ..syscall.types import c_char 6 | from ..syscall.net import ( 7 | recv, 8 | send, 9 | strlen, 10 | ) 11 | 12 | alias O_RDWR = 0o2 13 | 14 | 15 | trait FileDescriptorBase(io.Reader, io.Writer, io.Closer): 16 | ... 17 | 18 | 19 | struct FileDescriptor(FileDescriptorBase): 20 | var fd: Int 21 | var is_closed: Bool 22 | 23 | # This takes ownership of a POSIX file descriptor. 24 | fn __moveinit__(inout self, owned existing: Self): 25 | self.fd = existing.fd 26 | self.is_closed = existing.is_closed 27 | 28 | fn __init__(inout self, fd: Int): 29 | self.fd = fd 30 | self.is_closed = False 31 | 32 | fn __del__(owned self): 33 | if not self.is_closed: 34 | var err = self.close() 35 | if err: 36 | print(str(err)) 37 | 38 | fn close(inout self) -> Error: 39 | """Mark the file descriptor as closed.""" 40 | var close_status = close(self.fd) 41 | if close_status == -1: 42 | return Error("FileDescriptor.close: Failed to close socket") 43 | 44 | self.is_closed = True 45 | return Error() 46 | 47 | fn dup(self) -> Self: 48 | """Duplicate the file descriptor.""" 49 | var new_fd = external_call["dup", Int, Int](self.fd) 50 | return Self(new_fd) 51 | 52 | # TODO: Need faster approach to copying data from the file descriptor to the buffer. 53 | fn read(inout self, inout dest: List[Byte]) -> (Int, Error): 54 | """Receive data from the file descriptor and write it to the buffer provided.""" 55 | var ptr = Pointer[UInt8]().alloc(dest.capacity) 56 | var bytes_received = recv(self.fd, ptr, dest.capacity, 0) 57 | if bytes_received == -1: 58 | return 0, Error("Failed to receive message from socket.") 59 | 60 | var int8_ptr = ptr.bitcast[Int8]() 61 | for i in range(bytes_received): 62 | dest.append(int8_ptr[i]) 63 | 64 | if bytes_received < dest.capacity: 65 | return bytes_received, Error(io.EOF) 66 | 67 | return bytes_received, Error() 68 | 69 | fn write(inout self, src: List[Byte]) -> (Int, Error): 70 | """Write data from the buffer to the file descriptor.""" 71 | var header_pointer = Pointer[Int8](src.data.address).bitcast[UInt8]() 72 | 73 | var bytes_sent = send(self.fd, header_pointer, strlen(header_pointer), 0) 74 | if bytes_sent == -1: 75 | return 0, Error("Failed to send message") 76 | 77 | return bytes_sent, Error() 78 | -------------------------------------------------------------------------------- /external/morrow/_libc.mojo: -------------------------------------------------------------------------------- 1 | from memory.unsafe import Pointer 2 | 3 | 4 | alias c_void = UInt8 5 | alias c_char = UInt8 6 | alias c_schar = Int8 7 | alias c_uchar = UInt8 8 | alias c_short = Int16 9 | alias c_ushort = UInt16 10 | alias c_int = Int32 11 | alias c_uint = UInt32 12 | alias c_long = Int64 13 | alias c_ulong = UInt64 14 | alias c_float = Float32 15 | alias c_double = Float64 16 | 17 | 18 | @value 19 | @register_passable("trivial") 20 | struct CTimeval: 21 | var tv_sec: Int # Seconds 22 | var tv_usec: Int # Microseconds 23 | 24 | fn __init__(tv_sec: Int = 0, tv_usec: Int = 0) -> Self: 25 | return Self {tv_sec: tv_sec, tv_usec: tv_usec} 26 | 27 | 28 | @value 29 | @register_passable("trivial") 30 | struct CTm: 31 | var tm_sec: Int32 # Seconds 32 | var tm_min: Int32 # Minutes 33 | var tm_hour: Int32 # Hour 34 | var tm_mday: Int32 # Day of the month 35 | var tm_mon: Int32 # Month 36 | var tm_year: Int32 # Year minus 1900 37 | var tm_wday: Int32 # Day of the week 38 | var tm_yday: Int32 # Day of the year 39 | var tm_isdst: Int32 # Daylight savings flag 40 | var tm_gmtoff: Int64 # localtime zone offset seconds 41 | 42 | fn __init__() -> Self: 43 | return Self { 44 | tm_sec: 0, 45 | tm_min: 0, 46 | tm_hour: 0, 47 | tm_mday: 0, 48 | tm_mon: 0, 49 | tm_year: 0, 50 | tm_wday: 0, 51 | tm_yday: 0, 52 | tm_isdst: 0, 53 | tm_gmtoff: 0, 54 | } 55 | 56 | 57 | @always_inline 58 | fn c_gettimeofday() -> CTimeval: 59 | var tv = CTimeval() 60 | var p_tv = Pointer[CTimeval].address_of(tv) 61 | external_call["gettimeofday", NoneType, Pointer[CTimeval], Int32](p_tv, 0) 62 | return tv 63 | 64 | 65 | @always_inline 66 | fn c_localtime(owned tv_sec: Int) -> CTm: 67 | var p_tv_sec = Pointer[Int].address_of(tv_sec) 68 | var tm = external_call["localtime", Pointer[CTm], Pointer[Int]](p_tv_sec).load() 69 | return tm 70 | 71 | 72 | @always_inline 73 | fn c_strptime(time_str: String, time_format: String) -> CTm: 74 | var tm = CTm() 75 | var p_tm = Pointer[CTm].address_of(tm) 76 | external_call["strptime", NoneType, Pointer[c_char], Pointer[c_char], Pointer[CTm]]( 77 | to_char_ptr(time_str), to_char_ptr(time_format), p_tm 78 | ) 79 | return tm 80 | 81 | 82 | @always_inline 83 | fn c_gmtime(owned tv_sec: Int) -> CTm: 84 | var p_tv_sec = Pointer[Int].address_of(tv_sec) 85 | var tm = external_call["gmtime", Pointer[CTm], Pointer[Int]](p_tv_sec).load() 86 | return tm 87 | 88 | 89 | fn to_char_ptr(s: String) -> Pointer[c_char]: 90 | """Only ASCII-based strings.""" 91 | var ptr = Pointer[c_char]().alloc(len(s)) 92 | for i in range(len(s)): 93 | ptr.store(i, ord(s[i])) 94 | return ptr 95 | -------------------------------------------------------------------------------- /stump/processor.mojo: -------------------------------------------------------------------------------- 1 | from external.morrow import Morrow 2 | from .base import Context 3 | from .style import get_default_styles 4 | 5 | # TODO: Included `escaping` in the Processor alias for now. It enables the use of functions that generate processors (ie passing args to the processor function) 6 | # Need to understanding closures a bit more, but this works with existing processors. 7 | alias Processor = fn (context: Context, level: String) escaping -> Context 8 | 9 | 10 | # Built in processor functions to modify the context before logging a message. 11 | fn add_timestamp(context: Context, level: String) -> Context: 12 | """Adds a timestamp to the log message with the specified format. 13 | The default format for timestamps is `YYYY-MM-DD HH:mm:ss`. 14 | 15 | Args: 16 | context: The current context. 17 | level: The log level of the message. 18 | """ 19 | var new_context = Context(context) 20 | # var timestamp: String = "" 21 | try: 22 | # timestamp = Morrow.now().format("YYYY-MM-DD HH:mm:ss") 23 | new_context["timestamp"] = Morrow.now().format("YYYY-MM-DD HH:mm:ss") 24 | return new_context 25 | except: 26 | print("add_timestamp: failed to get timestamp") 27 | 28 | # new_context["timestamp"] = timestamp 29 | return new_context 30 | 31 | 32 | fn add_log_level(context: Context, level: String) -> Context: 33 | """Adds the log level to the log message. 34 | 35 | Args: 36 | context: The current context. 37 | level: The log level of the message. 38 | """ 39 | var new_context = Context(context) 40 | new_context["level"] = level 41 | 42 | return new_context 43 | 44 | 45 | # If you need to modify something within the processor function, create a function that returns a Processor 46 | fn add_timestamp_with_format[format: String]() -> Processor: 47 | """Adds a timestamp to the log message with the specified format. 48 | The format should be a valid format string for Morrow.now().format() or "iso". 49 | 50 | The default format for timestamps is `YYYY-MM-DD HH:mm:ss`. 51 | 52 | Params: 53 | format: The format string for the timestamp. 54 | """ 55 | fn processor(context: Context, level: String) -> Context: 56 | var new_context = Context(context) 57 | try: 58 | var now = Morrow.now() 59 | var timestamp: String = "" 60 | if format == "iso": 61 | timestamp = now.isoformat() 62 | else: 63 | timestamp = Morrow.now().format(format) 64 | new_context["timestamp"] = timestamp 65 | return new_context 66 | except: 67 | print("add_timestamp_with_format: failed to get timestamp") 68 | 69 | return new_context 70 | 71 | return processor 72 | 73 | 74 | # TODO: Temporary solution to get a list of processors at runtime. Storing the processors as a field in the boundlogger struct does not work as of 24.2 75 | fn get_processors() -> List[Processor]: 76 | return List[Processor](add_timestamp, add_log_level) 77 | -------------------------------------------------------------------------------- /external/mist/renderers.mojo: -------------------------------------------------------------------------------- 1 | from .style import TerminalStyle 2 | from .profile import Profile 3 | 4 | 5 | alias RED = "#E88388" 6 | alias GREEN = "#A8CC8C" 7 | alias YELLOW = "#DBAB79" 8 | alias BLUE = "#71BEF2" 9 | alias MAGENTA = "#D290E4" 10 | alias CYAN = "#66C2CD" 11 | alias GRAY = "#B9BFCA" 12 | 13 | 14 | # Convenience functions for quick style application 15 | fn as_color(text: String, color: String) -> String: 16 | var profile = Profile() 17 | return TerminalStyle.new().foreground(profile.color(color)).render(text) 18 | 19 | 20 | fn red(text: String) -> String: 21 | """Apply red color to the text.""" 22 | return as_color(text, RED) 23 | 24 | 25 | fn green(text: String) -> String: 26 | """Apply green color to the text.""" 27 | return as_color(text, GREEN) 28 | 29 | 30 | fn yellow(text: String) -> String: 31 | """Apply yellow color to the text.""" 32 | return as_color(text, YELLOW) 33 | 34 | 35 | fn blue(text: String) -> String: 36 | """Apply blue color to the text.""" 37 | return as_color(text, BLUE) 38 | 39 | 40 | fn magenta(text: String) -> String: 41 | """Apply magenta color to the text.""" 42 | return as_color(text, MAGENTA) 43 | 44 | 45 | fn cyan(text: String) -> String: 46 | """Apply cyan color to the text.""" 47 | return as_color(text, CYAN) 48 | 49 | 50 | fn gray(text: String) -> String: 51 | """Apply gray color to the text.""" 52 | return as_color(text, GRAY) 53 | 54 | 55 | fn with_background_color(text: String, color: String) -> String: 56 | var profile = Profile() 57 | return TerminalStyle.new().background(profile.color(color)).render(text) 58 | 59 | 60 | fn red_background(text: String) -> String: 61 | """Apply red background color to the text.""" 62 | return with_background_color(text, RED) 63 | 64 | 65 | fn green_background(text: String) -> String: 66 | """Apply green background color to the text.""" 67 | return with_background_color(text, GREEN) 68 | 69 | 70 | fn yellow_background(text: String) -> String: 71 | """Apply yellow background color to the text.""" 72 | return with_background_color(text, YELLOW) 73 | 74 | 75 | fn blue_background(text: String) -> String: 76 | """Apply blue background color to the text.""" 77 | return with_background_color(text, BLUE) 78 | 79 | 80 | fn magenta_background(text: String) -> String: 81 | """Apply magenta background color to the text.""" 82 | return with_background_color(text, MAGENTA) 83 | 84 | 85 | fn cyan_background(text: String) -> String: 86 | """Apply cyan background color to the text.""" 87 | return with_background_color(text, CYAN) 88 | 89 | 90 | fn gray_background(text: String) -> String: 91 | """Apply gray background color to the text.""" 92 | return with_background_color(text, GRAY) 93 | 94 | 95 | fn bold(text: String) -> String: 96 | return TerminalStyle.new().bold().render(text) 97 | 98 | 99 | fn faint(text: String) -> String: 100 | return TerminalStyle.new().faint().render(text) 101 | 102 | 103 | fn italic(text: String) -> String: 104 | return TerminalStyle.new().italic().render(text) 105 | 106 | 107 | fn underline(text: String) -> String: 108 | return TerminalStyle.new().underline().render(text) 109 | 110 | 111 | fn overline(text: String) -> String: 112 | return TerminalStyle.new().overline().render(text) 113 | 114 | 115 | fn crossout(text: String) -> String: 116 | return TerminalStyle.new().crossout().render(text) 117 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /external/gojo/builtins/list.mojo: -------------------------------------------------------------------------------- 1 | fn equals(left: List[Int8], right: List[Int8]) -> Bool: 2 | if len(left) != len(right): 3 | return False 4 | for i in range(len(left)): 5 | if left[i] != right[i]: 6 | return False 7 | return True 8 | 9 | 10 | fn equals(left: List[UInt8], right: List[UInt8]) -> Bool: 11 | if len(left) != len(right): 12 | return False 13 | for i in range(len(left)): 14 | if left[i] != right[i]: 15 | return False 16 | return True 17 | 18 | 19 | fn equals(left: List[Int16], right: List[Int16]) -> Bool: 20 | if len(left) != len(right): 21 | return False 22 | for i in range(len(left)): 23 | if left[i] != right[i]: 24 | return False 25 | return True 26 | 27 | 28 | fn equals(left: List[UInt16], right: List[UInt16]) -> Bool: 29 | if len(left) != len(right): 30 | return False 31 | for i in range(len(left)): 32 | if left[i] != right[i]: 33 | return False 34 | return True 35 | 36 | 37 | fn equals(left: List[Int32], right: List[Int32]) -> Bool: 38 | if len(left) != len(right): 39 | return False 40 | for i in range(len(left)): 41 | if left[i] != right[i]: 42 | return False 43 | return True 44 | 45 | 46 | fn equals(left: List[UInt32], right: List[UInt32]) -> Bool: 47 | if len(left) != len(right): 48 | return False 49 | for i in range(len(left)): 50 | if left[i] != right[i]: 51 | return False 52 | return True 53 | 54 | 55 | fn equals(left: List[Int64], right: List[Int64]) -> Bool: 56 | if len(left) != len(right): 57 | return False 58 | for i in range(len(left)): 59 | if left[i] != right[i]: 60 | return False 61 | return True 62 | 63 | 64 | fn equals(left: List[UInt64], right: List[UInt64]) -> Bool: 65 | if len(left) != len(right): 66 | return False 67 | for i in range(len(left)): 68 | if left[i] != right[i]: 69 | return False 70 | return True 71 | 72 | 73 | fn equals(left: List[Int], right: List[Int]) -> Bool: 74 | if len(left) != len(right): 75 | return False 76 | for i in range(len(left)): 77 | if left[i] != right[i]: 78 | return False 79 | return True 80 | 81 | 82 | fn equals(left: List[Float16], right: List[Float16]) -> Bool: 83 | if len(left) != len(right): 84 | return False 85 | for i in range(len(left)): 86 | if left[i] != right[i]: 87 | return False 88 | return True 89 | 90 | 91 | fn equals(left: List[Float32], right: List[Float32]) -> Bool: 92 | if len(left) != len(right): 93 | return False 94 | for i in range(len(left)): 95 | if left[i] != right[i]: 96 | return False 97 | return True 98 | 99 | 100 | fn equals(left: List[Float64], right: List[Float64]) -> Bool: 101 | if len(left) != len(right): 102 | return False 103 | for i in range(len(left)): 104 | if left[i] != right[i]: 105 | return False 106 | return True 107 | 108 | 109 | fn equals(left: List[String], right: List[String]) -> Bool: 110 | if len(left) != len(right): 111 | return False 112 | for i in range(len(left)): 113 | if left[i] != right[i]: 114 | return False 115 | return True 116 | 117 | 118 | fn equals(left: List[StringLiteral], right: List[StringLiteral]) -> Bool: 119 | if len(left) != len(right): 120 | return False 121 | for i in range(len(left)): 122 | if left[i] != right[i]: 123 | return False 124 | return True 125 | 126 | 127 | fn equals(left: List[Bool], right: List[Bool]) -> Bool: 128 | if len(left) != len(right): 129 | return False 130 | for i in range(len(left)): 131 | if left[i] != right[i]: 132 | return False 133 | return True 134 | -------------------------------------------------------------------------------- /stump/formatter.mojo: -------------------------------------------------------------------------------- 1 | from external.gojo.strings import StringBuilder 2 | from external.gojo.fmt.fmt import sprintf_str, sprintf 3 | from .base import Context, ContextPair, LEVEL_MAPPING 4 | from .style import Styles 5 | 6 | 7 | # Formatter options 8 | alias Formatter = UInt8 9 | alias DEFAULT_FORMAT: Formatter = 0 10 | alias JSON_FORMAT: Formatter = 1 11 | alias LOGFMT_FORMAT: Formatter = 2 12 | 13 | 14 | fn join(separator: String, iterable: List[String]) raises -> String: 15 | var result: String = "" 16 | for i in range(iterable.__len__()): 17 | result += iterable[i] 18 | if i != iterable.__len__() - 1: 19 | result += separator 20 | return result 21 | 22 | 23 | fn default_formatter(context: Context) raises -> String: 24 | """Default formatter for log messages. 25 | 26 | Args: 27 | context: The context to format. 28 | 29 | Returns: 30 | The formatted log message. 31 | """ 32 | # TODO: Probably need a better algorithm for this formatting process. 33 | var new_context = Context(context) 34 | var format = List[String]() 35 | var args = List[String]() 36 | 37 | # timestamp then level, then message, then other context keys 38 | if "timestamp" in new_context: 39 | args.append(new_context.pop("timestamp")) 40 | format.append("%s") 41 | 42 | if "level" in new_context: 43 | args.append(new_context.pop("level")) 44 | format.append("%s") 45 | 46 | args.append(new_context.pop("message")) 47 | format.append("%s") 48 | 49 | # Add the rest of the context delimited by a space. 50 | var delimiter: String = " " 51 | var builder = StringBuilder() 52 | _ = builder.write_string(delimiter) 53 | var pair_count = new_context.size 54 | var current_index = 0 55 | for pair in new_context.items(): 56 | _ = builder.write_string(stringify_kv_pair(pair[])) 57 | 58 | if current_index < pair_count - 1: 59 | _ = builder.write_string(delimiter) 60 | current_index += 1 61 | 62 | return sprintf_str(join(" ", format), args=args) + str(builder) 63 | 64 | 65 | fn json_formatter(context: Context) raises -> String: 66 | return stringify_context(context) 67 | 68 | 69 | fn stringify_kv_pair(pair: ContextPair) raises -> String: 70 | return sprintf("%s=%s", pair.key.s, pair.value) 71 | 72 | 73 | fn stringify_context(data: Context) -> String: 74 | var key_count = data.size 75 | var builder = StringBuilder() 76 | _ = builder.write_string("{") 77 | 78 | var key_index = 0 79 | for pair in data.items(): 80 | _ = builder.write_string('"') 81 | _ = builder.write_string(pair[].key.s) 82 | _ = builder.write_string('"') 83 | _ = builder.write_string(':"') 84 | 85 | if pair[].key.s == "level": 86 | var level_text: String = "" 87 | try: 88 | level_text = LEVEL_MAPPING[atol(pair[].value)] 89 | _ = builder.write_string(level_text) 90 | except: 91 | _ = builder.write_string(pair[].value) 92 | else: 93 | _ = builder.write_string(pair[].value) 94 | 95 | _ = builder.write_string('"') 96 | 97 | # Add comma for all elements except last 98 | if key_index != key_count - 1: 99 | _ = builder.write_string(", ") 100 | key_index += 1 101 | 102 | _ = builder.write_string("}") 103 | return str(builder) 104 | 105 | 106 | fn logfmt_formatter(context: Context) raises -> String: 107 | var new_context = Context(context) 108 | 109 | # Add all the keys in the context in KV format. 110 | var delimiter = " " 111 | var builder = StringBuilder() 112 | var pair_count = new_context.size 113 | var current_index = 0 114 | for pair in new_context.items(): 115 | _ = builder.write_string(stringify_kv_pair(pair[])) 116 | 117 | if current_index < pair_count - 1: 118 | _ = builder.write_string(delimiter) 119 | current_index += 1 120 | 121 | # timestamp then level, then message, then other context keys 122 | return str(builder) 123 | 124 | 125 | fn format(formatter: Formatter, context: Context) raises -> String: 126 | if formatter == JSON_FORMAT: 127 | return json_formatter(context) 128 | elif formatter == LOGFMT_FORMAT: 129 | return logfmt_formatter(context) 130 | 131 | return default_formatter(context) 132 | -------------------------------------------------------------------------------- /external/gojo/strings/builder.mojo: -------------------------------------------------------------------------------- 1 | # Adapted from https://github.com/maniartech/mojo-strings/blob/master/strings/builder.mojo 2 | # Modified to use List[Int8] instead of List[String] 3 | 4 | import ..io 5 | from ..builtins import Byte 6 | 7 | 8 | @value 9 | struct StringBuilder(Stringable, Sized, io.Writer, io.ByteWriter, io.StringWriter): 10 | """ 11 | A string builder class that allows for efficient string management and concatenation. 12 | This class is useful when you need to build a string by appending multiple strings 13 | together. It is around 20x faster than using the `+` operator to concatenate 14 | strings because it avoids the overhead of creating and destroying many 15 | intermediate strings and performs memcopy operations. 16 | 17 | The result is a more efficient when building larger string concatenations. It 18 | is generally not recommended to use this class for small concatenations such as 19 | a few strings like `a + b + c + d` because the overhead of creating the string 20 | builder and appending the strings is not worth the performance gain. 21 | 22 | Example: 23 | ``` 24 | from strings.builder import StringBuilder 25 | 26 | var sb = StringBuilder() 27 | sb.write_string("mojo") 28 | sb.write_string("jojo") 29 | print(sb) # mojojojo 30 | ``` 31 | """ 32 | 33 | var _vector: List[Byte] 34 | 35 | fn __init__(inout self, *, size: Int = 4096): 36 | self._vector = List[Byte](capacity=size) 37 | 38 | fn __str__(self) -> String: 39 | """ 40 | Converts the string builder to a string. 41 | 42 | Returns: 43 | The string representation of the string builder. Returns an empty 44 | string if the string builder is empty. 45 | """ 46 | var copy = List[Byte](self._vector) 47 | if copy[-1] != 0: 48 | copy.append(0) 49 | return String(copy) 50 | 51 | fn get_bytes(self) -> List[Int8]: 52 | """ 53 | Returns a deepcopy of the byte array of the string builder. 54 | 55 | Returns: 56 | The byte array of the string builder. 57 | """ 58 | return List[Byte](self._vector) 59 | 60 | fn get_null_terminated_bytes(self) -> List[Int8]: 61 | """ 62 | Returns a deepcopy of the byte array of the string builder with a null terminator. 63 | 64 | Returns: 65 | The byte array of the string builder with a null terminator. 66 | """ 67 | var copy = List[Byte](self._vector) 68 | if copy[-1] != 0: 69 | copy.append(0) 70 | 71 | return copy 72 | 73 | fn write(inout self, src: List[Byte]) -> (Int, Error): 74 | """ 75 | Appends a byte array to the builder buffer. 76 | 77 | Args: 78 | src: The byte array to append. 79 | """ 80 | self._vector.extend(src) 81 | return len(src), Error() 82 | 83 | fn write_byte(inout self, byte: Int8) -> (Int, Error): 84 | """ 85 | Appends a byte array to the builder buffer. 86 | 87 | Args: 88 | byte: The byte array to append. 89 | """ 90 | self._vector.append(byte) 91 | return 1, Error() 92 | 93 | fn write_string(inout self, src: String) -> (Int, Error): 94 | """ 95 | Appends a string to the builder buffer. 96 | 97 | Args: 98 | src: The string to append. 99 | """ 100 | var string_buffer = src.as_bytes() 101 | self._vector.extend(string_buffer) 102 | return len(string_buffer), Error() 103 | 104 | fn __len__(self) -> Int: 105 | """ 106 | Returns the length of the string builder. 107 | 108 | Returns: 109 | The length of the string builder. 110 | """ 111 | return len(self._vector) 112 | 113 | fn __getitem__(self, index: Int) -> String: 114 | """ 115 | Returns the string at the given index. 116 | 117 | Args: 118 | index: The index of the string to return. 119 | 120 | Returns: 121 | The string at the given index. 122 | """ 123 | return self._vector[index] 124 | 125 | fn __setitem__(inout self, index: Int, value: Int8): 126 | """ 127 | Sets the string at the given index. 128 | 129 | Args: 130 | index: The index of the string to set. 131 | value: The value to set. 132 | """ 133 | self._vector[index] = value 134 | -------------------------------------------------------------------------------- /external/gojo/syscall/file.mojo: -------------------------------------------------------------------------------- 1 | from .types import c_int, c_char, c_void, c_size_t, c_ssize_t 2 | 3 | 4 | # --- ( File Related Syscalls & Structs )--------------------------------------- 5 | alias O_NONBLOCK = 16384 6 | alias O_ACCMODE = 3 7 | alias O_CLOEXEC = 524288 8 | 9 | 10 | fn close(fildes: c_int) -> c_int: 11 | """Libc POSIX `close` function 12 | Reference: https://man7.org/linux/man-pages/man3/close.3p.html 13 | Fn signature: int close(int fildes). 14 | 15 | Args: 16 | fildes: A File Descriptor to close. 17 | 18 | Returns: 19 | Upon successful completion, 0 shall be returned; otherwise, -1 20 | shall be returned and errno set to indicate the error. 21 | """ 22 | return external_call["close", c_int, c_int](fildes) 23 | 24 | 25 | fn open[*T: AnyType](path: Pointer[c_char], oflag: c_int, *args: *T) -> c_int: 26 | """Libc POSIX `open` function 27 | Reference: https://man7.org/linux/man-pages/man3/open.3p.html 28 | Fn signature: int open(const char *path, int oflag, ...). 29 | 30 | Args: 31 | path: A pointer to a C string containing the path to open. 32 | oflag: The flags to open the file with. 33 | args: The optional arguments. 34 | Returns: 35 | A File Descriptor or -1 in case of failure 36 | """ 37 | return external_call["open", c_int, Pointer[c_char], c_int](path, oflag, args) # FnName, RetType # Args 38 | 39 | 40 | fn openat[*T: AnyType](fd: c_int, path: Pointer[c_char], oflag: c_int, *args: *T) -> c_int: 41 | """Libc POSIX `open` function 42 | Reference: https://man7.org/linux/man-pages/man3/open.3p.html 43 | Fn signature: int openat(int fd, const char *path, int oflag, ...). 44 | 45 | Args: 46 | fd: A File Descriptor. 47 | path: A pointer to a C string containing the path to open. 48 | oflag: The flags to open the file with. 49 | args: The optional arguments. 50 | Returns: 51 | A File Descriptor or -1 in case of failure 52 | """ 53 | return external_call["openat", c_int, c_int, Pointer[c_char], c_int]( # FnName, RetType # Args 54 | fd, path, oflag, args 55 | ) 56 | 57 | 58 | fn printf[*T: AnyType](format: Pointer[c_char], *args: *T) -> c_int: 59 | """Libc POSIX `printf` function 60 | Reference: https://man7.org/linux/man-pages/man3/fprintf.3p.html 61 | Fn signature: int printf(const char *restrict format, ...). 62 | 63 | Args: format: A pointer to a C string containing the format. 64 | args: The optional arguments. 65 | Returns: The number of bytes written or -1 in case of failure. 66 | """ 67 | return external_call[ 68 | "printf", 69 | c_int, # FnName, RetType 70 | Pointer[c_char], # Args 71 | ](format, args) 72 | 73 | 74 | fn sprintf[*T: AnyType](s: Pointer[c_char], format: Pointer[c_char], *args: *T) -> c_int: 75 | """Libc POSIX `sprintf` function 76 | Reference: https://man7.org/linux/man-pages/man3/fprintf.3p.html 77 | Fn signature: int sprintf(char *restrict s, const char *restrict format, ...). 78 | 79 | Args: s: A pointer to a buffer to store the result. 80 | format: A pointer to a C string containing the format. 81 | args: The optional arguments. 82 | Returns: The number of bytes written or -1 in case of failure. 83 | """ 84 | return external_call["sprintf", c_int, Pointer[c_char], Pointer[c_char]](s, format, args) # FnName, RetType # Args 85 | 86 | 87 | fn read(fildes: c_int, buf: Pointer[c_void], nbyte: c_size_t) -> c_int: 88 | """Libc POSIX `read` function 89 | Reference: https://man7.org/linux/man-pages/man3/read.3p.html 90 | Fn signature: sssize_t read(int fildes, void *buf, size_t nbyte). 91 | 92 | Args: fildes: A File Descriptor. 93 | buf: A pointer to a buffer to store the read data. 94 | nbyte: The number of bytes to read. 95 | Returns: The number of bytes read or -1 in case of failure. 96 | """ 97 | return external_call["read", c_ssize_t, c_int, Pointer[c_void], c_size_t](fildes, buf, nbyte) 98 | 99 | 100 | fn write(fildes: c_int, buf: Pointer[c_void], nbyte: c_size_t) -> c_int: 101 | """Libc POSIX `write` function 102 | Reference: https://man7.org/linux/man-pages/man3/write.3p.html 103 | Fn signature: ssize_t write(int fildes, const void *buf, size_t nbyte). 104 | 105 | Args: fildes: A File Descriptor. 106 | buf: A pointer to a buffer to write. 107 | nbyte: The number of bytes to write. 108 | Returns: The number of bytes written or -1 in case of failure. 109 | """ 110 | return external_call["write", c_ssize_t, c_int, Pointer[c_void], c_size_t](fildes, buf, nbyte) 111 | -------------------------------------------------------------------------------- /external/gojo/net/address.mojo: -------------------------------------------------------------------------------- 1 | @value 2 | struct NetworkType: 3 | var value: String 4 | 5 | alias empty = NetworkType("") 6 | alias tcp = NetworkType("tcp") 7 | alias tcp4 = NetworkType("tcp4") 8 | alias tcp6 = NetworkType("tcp6") 9 | alias udp = NetworkType("udp") 10 | alias udp4 = NetworkType("udp4") 11 | alias udp6 = NetworkType("udp6") 12 | alias ip = NetworkType("ip") 13 | alias ip4 = NetworkType("ip4") 14 | alias ip6 = NetworkType("ip6") 15 | alias unix = NetworkType("unix") 16 | 17 | 18 | trait Addr(CollectionElement, Stringable): 19 | fn network(self) -> String: 20 | """Name of the network (for example, "tcp", "udp").""" 21 | ... 22 | 23 | 24 | @value 25 | struct TCPAddr(Addr): 26 | """Addr struct representing a TCP address. 27 | 28 | Args: 29 | ip: IP address. 30 | port: Port number. 31 | zone: IPv6 addressing zone. 32 | """ 33 | 34 | var ip: String 35 | var port: Int 36 | var zone: String # IPv6 addressing zone 37 | 38 | fn __init__(inout self): 39 | self.ip = String("127.0.0.1") 40 | self.port = 8000 41 | self.zone = "" 42 | 43 | fn __init__(inout self, ip: String, port: Int): 44 | self.ip = ip 45 | self.port = port 46 | self.zone = "" 47 | 48 | fn __str__(self) -> String: 49 | if self.zone != "": 50 | return join_host_port(String(self.ip) + "%" + self.zone, self.port) 51 | return join_host_port(self.ip, self.port) 52 | 53 | fn network(self) -> String: 54 | return NetworkType.tcp.value 55 | 56 | 57 | fn resolve_internet_addr(network: String, address: String) raises -> TCPAddr: 58 | var host: String = "" 59 | var port: String = "" 60 | var portnum: Int = 0 61 | if ( 62 | network == NetworkType.tcp.value 63 | or network == NetworkType.tcp4.value 64 | or network == NetworkType.tcp6.value 65 | or network == NetworkType.udp.value 66 | or network == NetworkType.udp4.value 67 | or network == NetworkType.udp6.value 68 | ): 69 | if address != "": 70 | var host_port = split_host_port(address) 71 | host = host_port.host 72 | port = host_port.port 73 | portnum = atol(port.__str__()) 74 | elif network == NetworkType.ip.value or network == NetworkType.ip4.value or network == NetworkType.ip6.value: 75 | if address != "": 76 | host = address 77 | elif network == NetworkType.unix.value: 78 | raise Error("Unix addresses not supported yet") 79 | else: 80 | raise Error("unsupported network type: " + network) 81 | return TCPAddr(host, portnum) 82 | 83 | 84 | alias missingPortError = Error("missing port in address") 85 | alias tooManyColonsError = Error("too many colons in address") 86 | 87 | 88 | struct HostPort(Stringable): 89 | var host: String 90 | var port: Int 91 | 92 | fn __init__(inout self, host: String, port: Int): 93 | self.host = host 94 | self.port = port 95 | 96 | fn __str__(self) -> String: 97 | return join_host_port(self.host, str(self.port)) 98 | 99 | 100 | fn join_host_port(host: String, port: String) -> String: 101 | if host.find(":") != -1: # must be IPv6 literal 102 | return "[" + host + "]:" + port 103 | return host + ":" + port 104 | 105 | 106 | fn split_host_port(hostport: String) raises -> HostPort: 107 | var host: String = "" 108 | var port: String = "" 109 | var colon_index = hostport.rfind(":") 110 | var j: Int = 0 111 | var k: Int = 0 112 | 113 | if colon_index == -1: 114 | raise missingPortError 115 | if hostport[0] == "[": 116 | var end_bracket_index = hostport.find("]") 117 | if end_bracket_index == -1: 118 | raise Error("missing ']' in address") 119 | if end_bracket_index + 1 == len(hostport): 120 | raise missingPortError 121 | elif end_bracket_index + 1 == colon_index: 122 | host = hostport[1:end_bracket_index] 123 | j = 1 124 | k = end_bracket_index + 1 125 | else: 126 | if hostport[end_bracket_index + 1] == ":": 127 | raise tooManyColonsError 128 | else: 129 | raise missingPortError 130 | else: 131 | host = hostport[:colon_index] 132 | if host.find(":") != -1: 133 | raise tooManyColonsError 134 | if hostport[j:].find("[") != -1: 135 | raise Error("unexpected '[' in address") 136 | if hostport[k:].find("]") != -1: 137 | raise Error("unexpected ']' in address") 138 | port = hostport[colon_index + 1 :] 139 | 140 | if port == "": 141 | raise missingPortError 142 | if host == "": 143 | raise Error("missing host") 144 | 145 | return HostPort(host, atol(port)) 146 | -------------------------------------------------------------------------------- /external/gojo/net/net.mojo: -------------------------------------------------------------------------------- 1 | from memory._arc import Arc 2 | import ..io 3 | from ..builtins import Byte 4 | from .socket import Socket 5 | from .address import Addr, TCPAddr 6 | 7 | alias DEFAULT_BUFFER_SIZE = 4096 8 | 9 | 10 | trait Conn(io.Writer, io.Reader, io.Closer): 11 | fn __init__(inout self, owned socket: Socket): 12 | ... 13 | 14 | """Conn is a generic stream-oriented network connection.""" 15 | 16 | fn local_address(self) -> TCPAddr: 17 | """Returns the local network address, if known.""" 18 | ... 19 | 20 | fn remote_address(self) -> TCPAddr: 21 | """Returns the local network address, if known.""" 22 | ... 23 | 24 | # fn set_deadline(self, t: time.Time) -> Error: 25 | # """Sets the read and write deadlines associated 26 | # with the connection. It is equivalent to calling both 27 | # SetReadDeadline and SetWriteDeadline. 28 | 29 | # A deadline is an absolute time after which I/O operations 30 | # fail instead of blocking. The deadline applies to all future 31 | # and pending I/O, not just the immediately following call to 32 | # read or write. After a deadline has been exceeded, the 33 | # connection can be refreshed by setting a deadline in the future. 34 | 35 | # If the deadline is exceeded a call to read or write or to other 36 | # I/O methods will return an error that wraps os.ErrDeadlineExceeded. 37 | # This can be tested using errors.Is(err, os.ErrDeadlineExceeded). 38 | # The error's Timeout method will return true, but note that there 39 | # are other possible errors for which the Timeout method will 40 | # return true even if the deadline has not been exceeded. 41 | 42 | # An idle timeout can be implemented by repeatedly extending 43 | # the deadline after successful read or write calls. 44 | 45 | # A zero value for t means I/O operations will not time out.""" 46 | # ... 47 | 48 | # fn set_read_deadline(self, t: time.Time) -> Error: 49 | # """Sets the deadline for future read calls 50 | # and any currently-blocked read call. 51 | # A zero value for t means read will not time out.""" 52 | # ... 53 | 54 | # fn set_write_deadline(self, t: time.Time) -> Error: 55 | # """Sets the deadline for future write calls 56 | # and any currently-blocked write call. 57 | # Even if write times out, it may return n > 0, indicating that 58 | # some of the data was successfully written. 59 | # A zero value for t means write will not time out.""" 60 | # ... 61 | 62 | 63 | @value 64 | struct Connection(Conn): 65 | """Connection is a concrete generic stream-oriented network connection. 66 | It is used as the internal connection for structs like TCPConnection. 67 | 68 | Args: 69 | fd: The file descriptor of the connection. 70 | """ 71 | 72 | var fd: Arc[Socket] 73 | 74 | fn __init__(inout self, owned socket: Socket): 75 | self.fd = Arc(socket^) 76 | 77 | fn read(inout self, inout dest: List[Byte]) -> (Int, Error): 78 | """Reads data from the underlying file descriptor. 79 | 80 | Args: 81 | dest: The buffer to read data into. 82 | 83 | Returns: 84 | The number of bytes read, or an error if one occurred. 85 | """ 86 | var bytes_written: Int = 0 87 | var err = Error() 88 | bytes_written, err = self.fd[].read(dest) 89 | if err: 90 | if str(err) != io.EOF: 91 | return 0, err 92 | 93 | return bytes_written, err 94 | 95 | fn write(inout self, src: List[Byte]) -> (Int, Error): 96 | """Writes data to the underlying file descriptor. 97 | 98 | Args: 99 | src: The buffer to read data into. 100 | 101 | Returns: 102 | The number of bytes written, or an error if one occurred. 103 | """ 104 | var bytes_read: Int = 0 105 | var err = Error() 106 | bytes_read, err = self.fd[].write(src) 107 | if err: 108 | return 0, err 109 | 110 | return bytes_read, err 111 | 112 | fn close(inout self) -> Error: 113 | """Closes the underlying file descriptor. 114 | 115 | Returns: 116 | An error if one occurred, or None if the file descriptor was closed successfully. 117 | """ 118 | return self.fd[].close() 119 | 120 | fn local_address(self) -> TCPAddr: 121 | """Returns the local network address. 122 | The Addr returned is shared by all invocations of local_address, so do not modify it. 123 | """ 124 | return self.fd[].local_address 125 | 126 | fn remote_address(self) -> TCPAddr: 127 | """Returns the remote network address. 128 | The Addr returned is shared by all invocations of remote_address, so do not modify it. 129 | """ 130 | return self.fd[].remote_address 131 | -------------------------------------------------------------------------------- /external/mist/profile.mojo: -------------------------------------------------------------------------------- 1 | import os 2 | from .color import ( 3 | NoColor, 4 | ANSIColor, 5 | ANSI256Color, 6 | RGBColor, 7 | AnyColor, 8 | hex_to_ansi256, 9 | ansi256_to_ansi, 10 | hex_to_rgb, 11 | ) 12 | 13 | 14 | fn contains(vector: List[Int], value: Int) -> Bool: 15 | for i in range(vector.size): 16 | if vector[i] == value: 17 | return True 18 | return False 19 | 20 | 21 | alias TRUE_COLOR: Int = 0 22 | alias ANSI256: Int = 1 23 | alias ANSI: Int = 2 24 | alias ASCII: Int = 3 25 | 26 | 27 | # TODO: UNIX systems only for now. Need to add Windows, POSIX, and SOLARIS support. 28 | fn get_color_profile() -> Profile: 29 | """Queries the terminal to determine the color profile it supports. 30 | ASCII, ANSI, ANSI256, or TRUE_COLOR. 31 | """ 32 | # if not o.isTTY(): 33 | # return Ascii 34 | if os.getenv("GOOGLE_CLOUD_SHELL", "false") == "true": 35 | return Profile(TRUE_COLOR) 36 | 37 | var term = os.getenv("TERM").lower() 38 | var color_term = os.getenv("COLORTERM").lower() 39 | 40 | # COLORTERM is used by some terminals to indicate TRUE_COLOR support. 41 | if color_term == "24bit": 42 | pass 43 | elif color_term == "truecolor": 44 | if term.startswith("screen"): 45 | # tmux supports TRUE_COLOR, screen only ANSI256 46 | if os.getenv("TERM_PROGRAM") != "tmux": 47 | return Profile(ANSI256) 48 | return Profile(TRUE_COLOR) 49 | elif color_term == "yes": 50 | pass 51 | elif color_term == "true": 52 | return Profile(ANSI256) 53 | 54 | # TERM is used by most terminals to indicate color support. 55 | if term == "xterm-kitty" or term == "wezterm" or term == "xterm-ghostty": 56 | return Profile(TRUE_COLOR) 57 | elif term == "linux": 58 | return Profile(ANSI) 59 | 60 | if "256color" in term: 61 | return Profile(ANSI256) 62 | 63 | if "color" in term: 64 | return Profile(ANSI) 65 | 66 | if "ansi" in term: 67 | return Profile(ANSI) 68 | 69 | return Profile(ASCII) 70 | 71 | 72 | @value 73 | struct Profile: 74 | var value: Int 75 | 76 | fn __init__(inout self, value: Int) -> None: 77 | """ 78 | Initialize a new profile with the given profile type. 79 | 80 | Args: 81 | value: The setting to use for this profile. Valid values: [TRUE_COLOR, ANSI256, ANSI, ASCII]. 82 | """ 83 | var valid = List[Int](TRUE_COLOR, ANSI256, ANSI, ASCII) 84 | if not contains(valid, value): 85 | self.value = TRUE_COLOR 86 | return 87 | 88 | self.value = value 89 | 90 | fn __init__(inout self) -> None: 91 | """ 92 | Initialize a new profile with the given profile type. 93 | """ 94 | self = get_color_profile() 95 | 96 | fn convert(self, color: AnyColor) -> AnyColor: 97 | """Degrades a color based on the terminal profile. 98 | 99 | Args: 100 | color: The color to convert to the current profile. 101 | """ 102 | if self.value == ASCII: 103 | return NoColor() 104 | 105 | if color.isa[NoColor](): 106 | return color.get[NoColor]()[] 107 | elif color.isa[ANSIColor](): 108 | return color.get[ANSIColor]()[] 109 | elif color.isa[ANSI256Color](): 110 | if self.value == ANSI: 111 | return ansi256_to_ansi(color.get[ANSIColor]()[].value) 112 | 113 | return color.get[ANSI256Color]()[] 114 | elif color.isa[RGBColor](): 115 | var h = hex_to_rgb(color.get[RGBColor]()[].value) 116 | 117 | if self.value != TRUE_COLOR: 118 | var ansi256 = hex_to_ansi256(h) 119 | if self.value == ANSI: 120 | return ansi256_to_ansi(ansi256.value) 121 | 122 | return ansi256 123 | 124 | return color.get[RGBColor]()[] 125 | 126 | # If it somehow gets here, just return No Color until I can figure out how to just return whatever color was passed in. 127 | return color.get[NoColor]()[] 128 | 129 | fn color(self, value: String) -> AnyColor: 130 | """Color creates a Color from a string. Valid inputs are hex colors, as well as 131 | ANSI color codes (0-15, 16-255). If an invalid input is passed in, NoColor() is returned which will not apply any coloring. 132 | 133 | Args: 134 | value: The string to convert to a color. 135 | """ 136 | if len(value) == 0: 137 | return NoColor() 138 | 139 | if self.value == ASCII: 140 | return NoColor() 141 | 142 | if value[0] == "#": 143 | var c = RGBColor(value) 144 | return self.convert(c) 145 | else: 146 | var i = 0 147 | try: 148 | i = atol(value) 149 | except e: 150 | return NoColor() 151 | 152 | if i < 16: 153 | var c = ANSIColor(i) 154 | return self.convert(c) 155 | elif i < 256: 156 | var c = ANSI256Color(i) 157 | return self.convert(c) 158 | 159 | return NoColor() 160 | -------------------------------------------------------------------------------- /external/mist/ansi_colors.mojo: -------------------------------------------------------------------------------- 1 | # RGB values of ANSI colors (0-255). 2 | alias ANSI_HEX_CODES = List[String]( 3 | "#000000", 4 | "#000000", 5 | "#800000", 6 | "#008000", 7 | "#808000", 8 | "#000080", 9 | "#800080", 10 | "#008080", 11 | "#c0c0c0", 12 | "#808080", 13 | "#ff0000", 14 | "#00ff00", 15 | "#ffff00", 16 | "#0000ff", 17 | "#ff00ff", 18 | "#00ffff", 19 | "#ffffff", 20 | "#000000", 21 | "#00005f", 22 | "#000087", 23 | "#0000af", 24 | "#0000d7", 25 | "#0000ff", 26 | "#005f00", 27 | "#005f5f", 28 | "#005f87", 29 | "#005faf", 30 | "#005fd7", 31 | "#005fff", 32 | "#008700", 33 | "#00875f", 34 | "#008787", 35 | "#0087af", 36 | "#0087d7", 37 | "#0087ff", 38 | "#00af00", 39 | "#00af5f", 40 | "#00af87", 41 | "#00afaf", 42 | "#00afd7", 43 | "#00afff", 44 | "#00d700", 45 | "#00d75f", 46 | "#00d787", 47 | "#00d7af", 48 | "#00d7d7", 49 | "#00d7ff", 50 | "#00ff00", 51 | "#00ff5f", 52 | "#00ff87", 53 | "#00ffaf", 54 | "#00ffd7", 55 | "#00ffff", 56 | "#5f0000", 57 | "#5f005f", 58 | "#5f0087", 59 | "#5f00af", 60 | "#5f00d7", 61 | "#5f00ff", 62 | "#5f5f00", 63 | "#5f5f5f", 64 | "#5f5f87", 65 | "#5f5faf", 66 | "#5f5fd7", 67 | "#5f5fff", 68 | "#5f8700", 69 | "#5f875f", 70 | "#5f8787", 71 | "#5f87af", 72 | "#5f87d7", 73 | "#5f87ff", 74 | "#5faf00", 75 | "#5faf5f", 76 | "#5faf87", 77 | "#5fafaf", 78 | "#5fafd7", 79 | "#5fafff", 80 | "#5fd700", 81 | "#5fd75f", 82 | "#5fd787", 83 | "#5fd7af", 84 | "#5fd7d7", 85 | "#5fd7ff", 86 | "#5fff00", 87 | "#5fff5f", 88 | "#5fff87", 89 | "#5fffaf", 90 | "#5fffd7", 91 | "#5fffff", 92 | "#870000", 93 | "#87005f", 94 | "#870087", 95 | "#8700af", 96 | "#8700d7", 97 | "#8700ff", 98 | "#875f00", 99 | "#875f5f", 100 | "#875f87", 101 | "#875faf", 102 | "#875fd7", 103 | "#875fff", 104 | "#878700", 105 | "#87875f", 106 | "#878787", 107 | "#8787af", 108 | "#8787d7", 109 | "#8787ff", 110 | "#87af00", 111 | "#87af5f", 112 | "#87af87", 113 | "#87afaf", 114 | "#87afd7", 115 | "#87afff", 116 | "#87d700", 117 | "#87d75f", 118 | "#87d787", 119 | "#87d7af", 120 | "#87d7d7", 121 | "#87d7ff", 122 | "#87ff00", 123 | "#87ff5f", 124 | "#87ff87", 125 | "#87ffaf", 126 | "#87ffd7", 127 | "#87ffff", 128 | "#af0000", 129 | "#af005f", 130 | "#af0087", 131 | "#af00af", 132 | "#af00d7", 133 | "#af00ff", 134 | "#af5f00", 135 | "#af5f5f", 136 | "#af5f87", 137 | "#af5faf", 138 | "#af5fd7", 139 | "#af5fff", 140 | "#af8700", 141 | "#af875f", 142 | "#af8787", 143 | "#af87af", 144 | "#af87d7", 145 | "#af87ff", 146 | "#afaf00", 147 | "#afaf5f", 148 | "#afaf87", 149 | "#afafaf", 150 | "#afafd7", 151 | "#afafff", 152 | "#afd700", 153 | "#afd75f", 154 | "#afd787", 155 | "#afd7af", 156 | "#afd7d7", 157 | "#afd7ff", 158 | "#afff00", 159 | "#afff5f", 160 | "#afff87", 161 | "#afffaf", 162 | "#afffd7", 163 | "#afffff", 164 | "#d70000", 165 | "#d7005f", 166 | "#d70087", 167 | "#d700af", 168 | "#d700d7", 169 | "#d700ff", 170 | "#d75f00", 171 | "#d75f5f", 172 | "#d75f87", 173 | "#d75faf", 174 | "#d75fd7", 175 | "#d75fff", 176 | "#d78700", 177 | "#d7875f", 178 | "#d78787", 179 | "#d787af", 180 | "#d787d7", 181 | "#d787ff", 182 | "#d7af00", 183 | "#d7af5f", 184 | "#d7af87", 185 | "#d7afaf", 186 | "#d7afd7", 187 | "#d7afff", 188 | "#d7d700", 189 | "#d7d75f", 190 | "#d7d787", 191 | "#d7d7af", 192 | "#d7d7d7", 193 | "#d7d7ff", 194 | "#d7ff00", 195 | "#d7ff5f", 196 | "#d7ff87", 197 | "#d7ffaf", 198 | "#d7ffd7", 199 | "#d7ffff", 200 | "#ff0000", 201 | "#ff005f", 202 | "#ff0087", 203 | "#ff00af", 204 | "#ff00d7", 205 | "#ff00ff", 206 | "#ff5f00", 207 | "#ff5f5f", 208 | "#ff5f87", 209 | "#ff5faf", 210 | "#ff5fd7", 211 | "#ff5fff", 212 | "#ff8700", 213 | "#ff875f", 214 | "#ff8787", 215 | "#ff87af", 216 | "#ff87d7", 217 | "#ff87ff", 218 | "#ffaf00", 219 | "#ffaf5f", 220 | "#ffaf87", 221 | "#ffafaf", 222 | "#ffafd7", 223 | "#ffafff", 224 | "#ffd700", 225 | "#ffd75f", 226 | "#ffd787", 227 | "#ffd7af", 228 | "#ffd7d7", 229 | "#ffd7ff", 230 | "#ffff00", 231 | "#ffff5f", 232 | "#ffff87", 233 | "#ffffaf", 234 | "#ffffd7", 235 | "#ffffff", 236 | "#080808", 237 | "#121212", 238 | "#1c1c1c", 239 | "#262626", 240 | "#303030", 241 | "#3a3a3a", 242 | "#444444", 243 | "#4e4e4e", 244 | "#585858", 245 | "#626262", 246 | "#6c6c6c", 247 | "#767676", 248 | "#808080", 249 | "#8a8a8a", 250 | "#949494", 251 | "#9e9e9e", 252 | "#a8a8a8", 253 | "#b2b2b2", 254 | "#bcbcbc", 255 | "#c6c6c6", 256 | "#d0d0d0", 257 | "#dadada", 258 | "#e4e4e4", 259 | "#eeeeee", 260 | ) 261 | -------------------------------------------------------------------------------- /external/hue/sort.mojo: -------------------------------------------------------------------------------- 1 | # # An element represents a single element of a set. It is used to 2 | # # implement a disjoint-set forest. 3 | # type element struct: 4 | # parent *element # Parent element 5 | # rank int # Rank (approximate depth) of the subtree with this element as root 6 | 7 | 8 | # # newElement creates a singleton set and returns its sole element. 9 | # fn newElement() *element: 10 | # s = &element{ 11 | # s.parent = s 12 | # return s 13 | 14 | 15 | # # find returns an arbitrary element of a set when invoked on any element of 16 | # # the set, The important feature is that it returns the same value when 17 | # # invoked on any element of the set. Consequently, it can be used to test if 18 | # # two elements belong to the same set. 19 | # fn (e *element) find() *element: 20 | # for e.parent != e: 21 | # e.parent = e.parent.parent 22 | # e = e.parent 23 | 24 | # return e 25 | 26 | 27 | # # union establishes the union of two sets when given an element from each set. 28 | # # Afterwards, the original sets no longer exist as separate entities. 29 | # fn union(e1, e2 *element): 30 | # # Ensure the two elements aren't already part of the same union. 31 | # e1Root = e1.find() 32 | # e2Root = e2.find() 33 | # if e1Root == e2Root: 34 | # return 35 | 36 | 37 | # # Create a union by making the shorter tree point to the root of the 38 | # # larger tree. 39 | # switch: 40 | # case e1Root.rank < e2Root.rank: 41 | # e1Root.parent = e2Root 42 | # case e1Root.rank > e2Root.rank: 43 | # e2Root.parent = e1Root 44 | # default: 45 | # e2Root.parent = e1Root 46 | # e1Root.rank++ 47 | 48 | 49 | # # An edgeIdxs describes an edge in a graph or tree. The vertices in the edge 50 | # # are indexes into a list of Color values. 51 | # type edgeIdxs [2]int 52 | 53 | # # An edgeDistance is a map from an edge (pair of indices) to a distance 54 | # # between the two vertices. 55 | # type edgeDistance map[edgeIdxs]float64 56 | 57 | # # allToAllDistancesCIEDE2000 computes the CIEDE2000 distance between each pair of 58 | # # colors. It returns a map from a pair of indices (u, v) with u < v to a 59 | # # distance. 60 | # fn allToAllDistancesCIEDE2000(cs []Color) edgeDistance: 61 | # nc = len(cs) 62 | # m = make(edgeDistance, nc*nc) 63 | # for u = 0; u < nc-1; u++: 64 | # for v = u + 1; v < nc; v++: 65 | # m[edgeIdxs{u, v] = cs[u].DistanceCIEDE2000(cs[v]) 66 | 67 | 68 | # return m 69 | 70 | 71 | # # sortEdges sorts all edges in a distance map by increasing vertex distance. 72 | # fn sortEdges(m edgeDistance) []edgeIdxs: 73 | # es = make([]edgeIdxs, 0, len(m)) 74 | # for uv = range m: 75 | # es = append(es, uv) 76 | 77 | # sort.Slice(es, fn(i, j int) bool: 78 | # return m[es[i]] < m[es[j]] 79 | # ) 80 | # return es 81 | 82 | 83 | # # minSpanTree computes a minimum spanning tree from a vertex count and a 84 | # # distance-sorted edge list. It returns the subset of edges that belong to 85 | # # the tree, including both (u, v) and (v, u) for each edge. 86 | # fn minSpanTree(nc int, es []edgeIdxs) map[edgeIdxs]struct{: 87 | # # Start with each vertex in its own set. 88 | # elts = make([]*element, nc) 89 | # for i = range elts: 90 | # elts[i] = newElement() 91 | 92 | 93 | # # Run Kruskal's algorithm to construct a minimal spanning tree. 94 | # mst = make(map[edgeIdxs]struct{, nc) 95 | # for _, uv = range es: 96 | # u, v = uv[0], uv[1] 97 | # if elts[u].find() == elts[v].find(): 98 | # continue # Same set: edge would introduce a cycle. 99 | 100 | # mst[uv] = struct{{ 101 | # mst[edgeIdxs{v, u] = struct{{ 102 | # union(elts[u], elts[v]) 103 | 104 | # return mst 105 | 106 | 107 | # # traverseMST walks a minimum spanning tree in prefix order. 108 | # fn traverseMST(mst map[edgeIdxs]struct{, root int) []int: 109 | # # Compute a list of neighbors for each vertex. 110 | # neighs = make(map[int][]int, len(mst)) 111 | # for uv = range mst: 112 | # u, v = uv[0], uv[1] 113 | # neighs[u] = append(neighs[u], v) 114 | 115 | # for u, vs = range neighs: 116 | # sort.Ints(vs) 117 | # copy(neighs[u], vs) 118 | 119 | 120 | # # Walk the tree from a given vertex. 121 | # order = make([]int, 0, len(neighs)) 122 | # visited = make(map[int]bool, len(neighs)) 123 | # var walkFrom fn(int) 124 | # walkFrom = fn(r int): 125 | # # Visit the starting vertex. 126 | # order = append(order, r) 127 | # visited[r] = true 128 | 129 | # # Recursively visit each child in turn. 130 | # for _, c = range neighs[r]: 131 | # if !visited[c]: 132 | # walkFrom(c) 133 | 134 | 135 | # walkFrom(root) 136 | # return order 137 | 138 | 139 | # # Sorted sorts a list of Color values. Sorting is not a well-defined operation 140 | # # for colors so the intention here primarily is to order colors so that the 141 | # # transition from one to the next is fairly smooth. 142 | # fn Sorted(cs []Color) []Color: 143 | # # Do nothing in trivial cases. 144 | # newCs = make([]Color, len(cs)) 145 | # if len(cs) < 2: 146 | # copy(newCs, cs) 147 | # return newCs 148 | 149 | 150 | # # Compute the distance from each color to every other color. 151 | # dists = allToAllDistancesCIEDE2000(cs) 152 | 153 | # # Produce a list of edges in increasing order of the distance between 154 | # # their vertices. 155 | # edges = sortEdges(dists) 156 | 157 | # # Construct a minimum spanning tree from the list of edges. 158 | # mst = minSpanTree(len(cs), edges) 159 | 160 | # # Find the darkest color in the list. 161 | # var black Color 162 | # var dIdx int # Index of darkest color 163 | # light = math.MaxFloat64 # Lightness of darkest color (distance from black) 164 | # for i, c = range cs: 165 | # d = black.DistanceCIEDE2000(c) 166 | # if d < light: 167 | # dIdx = i 168 | # light = d 169 | 170 | 171 | # # Traverse the tree starting from the darkest color. 172 | # idxs = traverseMST(mst, dIdx) 173 | 174 | # # Convert the index list to a list of colors, overwriting the input. 175 | # for i, idx = range idxs: 176 | # newCs[i] = cs[idx] 177 | 178 | # return newCs 179 | -------------------------------------------------------------------------------- /external/morrow/timedelta.mojo: -------------------------------------------------------------------------------- 1 | from math import abs 2 | from .util import rjust 3 | 4 | alias SECONDS_OF_DAY = 24 * 3600 5 | 6 | 7 | struct TimeDelta(Stringable): 8 | var days: Int 9 | var seconds: Int 10 | var microseconds: Int 11 | 12 | fn __init__( 13 | inout self, 14 | days: Int = 0, 15 | seconds: Int = 0, 16 | microseconds: Int = 0, 17 | milliseconds: Int = 0, 18 | minutes: Int = 0, 19 | hours: Int = 0, 20 | weeks: Int = 0, 21 | ): 22 | self.days = 0 23 | self.seconds = 0 24 | self.microseconds = 0 25 | 26 | var days_ = days 27 | var seconds_ = seconds 28 | var microseconds_ = microseconds 29 | 30 | # Normalize everything to days, seconds, microseconds. 31 | days_ += weeks * 7 32 | seconds_ += minutes * 60 + hours * 3600 33 | microseconds_ += milliseconds * 1000 34 | 35 | self.days = days_ 36 | days_ = seconds_ // SECONDS_OF_DAY 37 | seconds_ = seconds_ % SECONDS_OF_DAY 38 | self.days += days_ 39 | self.seconds += seconds_ 40 | 41 | seconds_ = microseconds_ // 1000000 42 | microseconds_ = microseconds_ % 1000000 43 | days_ = seconds_ // SECONDS_OF_DAY 44 | seconds_ = seconds_ % SECONDS_OF_DAY 45 | self.days += days_ 46 | self.seconds += seconds_ 47 | 48 | seconds_ = microseconds_ // 1000000 49 | self.microseconds = microseconds_ % 1000000 50 | self.seconds += seconds_ 51 | days_ = self.seconds // SECONDS_OF_DAY 52 | self.seconds = self.seconds % SECONDS_OF_DAY 53 | self.days += days_ 54 | 55 | fn __copyinit__(inout self, other: Self): 56 | self.days = other.days 57 | self.seconds = other.seconds 58 | self.microseconds = other.microseconds 59 | 60 | fn __str__(self) -> String: 61 | var mm = self.seconds // 60 62 | var ss = self.seconds % 60 63 | var hh = mm // 60 64 | mm = mm % 60 65 | var s = String(hh) + ":" + rjust(mm, 2, "0") + ":" + rjust(ss, 2, "0") 66 | if self.days: 67 | if abs(self.days) != 1: 68 | s = String(self.days) + " days, " + s 69 | else: 70 | s = String(self.days) + " day, " + s 71 | if self.microseconds: 72 | s = s + rjust(self.microseconds, 6, "0") 73 | return s 74 | 75 | fn total_seconds(self) -> Float64: 76 | """Total seconds in the duration.""" 77 | return ( 78 | (self.days * 86400 + self.seconds) * 10**6 + self.microseconds 79 | ) / 10**6 80 | 81 | @always_inline 82 | fn __add__(self, other: Self) -> Self: 83 | return Self( 84 | self.days + other.days, 85 | self.seconds + other.seconds, 86 | self.microseconds + other.microseconds, 87 | ) 88 | 89 | fn __radd__(self, other: Self) -> Self: 90 | return self.__add__(other) 91 | 92 | fn __sub__(self, other: Self) -> Self: 93 | return Self( 94 | self.days - other.days, 95 | self.seconds - other.seconds, 96 | self.microseconds - other.microseconds, 97 | ) 98 | 99 | fn __rsub__(self, other: Self) -> Self: 100 | return Self( 101 | other.days - self.days, 102 | other.seconds - self.seconds, 103 | other.microseconds - self.microseconds, 104 | ) 105 | 106 | fn __neg__(self) -> Self: 107 | return Self(-self.days, -self.seconds, -self.microseconds) 108 | 109 | fn __pos__(self) -> Self: 110 | return self 111 | 112 | def __abs__(self) -> Self: 113 | if self.days < 0: 114 | return -self 115 | else: 116 | return self 117 | 118 | @always_inline 119 | fn __mul__(self, other: Int) -> Self: 120 | return Self( 121 | self.days * other, 122 | self.seconds * other, 123 | self.microseconds * other, 124 | ) 125 | 126 | fn __rmul__(self, other: Int) -> Self: 127 | return self.__mul__(other) 128 | 129 | fn _to_microseconds(self) -> Int: 130 | return (self.days * SECONDS_OF_DAY + self.seconds) * 1000000 + self.microseconds 131 | 132 | fn __mod__(self, other: Self) -> Self: 133 | var r = self._to_microseconds() % other._to_microseconds() 134 | return Self(0, 0, r) 135 | 136 | fn __eq__(self, other: Self) -> Bool: 137 | return ( 138 | self.days == other.days 139 | and self.seconds == other.seconds 140 | and self.microseconds == other.microseconds 141 | ) 142 | 143 | @always_inline 144 | fn __le__(self, other: Self) -> Bool: 145 | if self.days < other.days: 146 | return True 147 | elif self.days == other.days: 148 | if self.seconds < other.seconds: 149 | return True 150 | elif ( 151 | self.seconds == other.seconds 152 | and self.microseconds <= other.microseconds 153 | ): 154 | return True 155 | return False 156 | 157 | @always_inline 158 | fn __lt__(self, other: Self) -> Bool: 159 | if self.days < other.days: 160 | return True 161 | elif self.days == other.days: 162 | if self.seconds < other.seconds: 163 | return True 164 | elif ( 165 | self.seconds == other.seconds and self.microseconds < other.microseconds 166 | ): 167 | return True 168 | return False 169 | 170 | fn __ge__(self, other: Self) -> Bool: 171 | return not self.__lt__(other) 172 | 173 | fn __gt__(self, other: Self) -> Bool: 174 | return not self.__le__(other) 175 | 176 | fn __bool__(self) -> Bool: 177 | return self.days != 0 or self.seconds != 0 or self.microseconds != 0 178 | 179 | 180 | alias Min = TimeDelta(-99999999) 181 | alias Max = TimeDelta(days=99999999) 182 | alias Resolution = TimeDelta(microseconds=1) 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stump 2 | 3 | ![Mojo 24.3](https://img.shields.io/badge/Mojo%F0%9F%94%A5-24.3-purple) 4 | 5 | WIP Logger! Inspired by charmbracelet's log package and the Python structlog package. 6 | 7 | **THIS LIBRARY IS BROKEN FOR MOJO 24.4+ UNTIL Dict.popitem() is fixed: https://github.com/modularml/mojo/issues/2756** 8 | 9 | There are some things I'm ironing out around terminal color profile querying at compilation time. At the moment, the default styles assume a `TRUE_COLOR` enabled color profile. So, if your terminal only supports `ANSI` or `ANSI256`, try setting custom styles like in the `custom.mojo` example, or update the default profile in `stump/style.mojo` from `TRUE_COLOR` to `ANSI` or `ANSI256`. 10 | 11 | See the examples directory for examples on setting up custom processors, styling, message only/json/logfmt logging, and logging with the styling turned off! 12 | 13 | ![Example logs](https://github.com/thatstoasty/stump/blob/main/logger.png) 14 | 15 | Minimal default logger example: 16 | 17 | ```py 18 | from stump import get_logger 19 | 20 | 21 | alias logger = get_logger() 22 | 23 | 24 | fn main(): 25 | logger.info("Information is good.") 26 | logger.warn("Warnings can be good too.") 27 | logger.error("An error!") 28 | logger.debug("Debugging...") 29 | logger.fatal("uh oh...") 30 | ``` 31 | 32 | There's support for arbitrary arg pairs and kwargs to be merged into the log statement! 33 | 34 | ```mojo 35 | from stump import get_logger 36 | 37 | 38 | alias logger = get_logger() 39 | 40 | 41 | fn main(): 42 | logger.info("Information is good.", "key", "value") 43 | logger.warn("Warnings can be good too.", "no_value") 44 | logger.error("An error!", erroring=True) 45 | logger.fatal("uh oh...", "number", 4, "mojo", "🔥") 46 | logger.debug("Debugging...") 47 | ``` 48 | 49 | Output (no color included) 50 | 51 | ```txt 52 | 2024-04-03 14:53:56 INFO Information is good. key=value 53 | 2024-04-03 14:53:56 WARN Warnings can be good too. no_value= 54 | 2024-04-03 14:53:56 ERROR An error! erroring=True 55 | 2024-04-03 14:53:56 FATAL uh oh... number=4 mojo=🔥 56 | ``` 57 | 58 | Minimal JSON logger example: 59 | 60 | ```mojo 61 | from stump import ( 62 | DEBUG, 63 | JSON_FORMAT, 64 | BoundLogger, 65 | PrintLogger 66 | ) 67 | 68 | 69 | # The loggers are compiled at build time, so we can reuse it. 70 | alias LOG_LEVEL = DEBUG 71 | alias logger = BoundLogger(PrintLogger(LOG_LEVEL), formatter=JSON_FORMAT) 72 | 73 | 74 | fn main(): 75 | logger.info("Information is good.") 76 | logger.warn("Warnings can be good too.") 77 | logger.error("An error!") 78 | logger.debug("Debugging...") 79 | logger.fatal("uh oh...") 80 | 81 | ``` 82 | 83 | Customized style and processor logger example: 84 | 85 | ```mojo 86 | from stump import ( 87 | DEBUG, 88 | DEFAULT_FORMAT, 89 | Processor, 90 | Context, 91 | Styles, 92 | Sections, 93 | BoundLogger, 94 | PrintLogger, 95 | add_log_level, 96 | add_timestamp, 97 | add_timestamp_with_format, 98 | ) 99 | from external.mist import TerminalStyle, Profile, TRUE_COLOR 100 | 101 | 102 | # Define a custom processor to add a name to the log output. 103 | fn add_my_name(context: Context) -> Context: 104 | var new_context = Context(context) 105 | new_context["name"] = "Mikhail" 106 | return new_context 107 | 108 | 109 | # Define custom processors to add extra information to the log output. 110 | fn my_processors() -> List[Processor]: 111 | return List[Processor]( 112 | add_log_level, add_timestamp_with_format["YYYY"](), add_my_name 113 | ) 114 | 115 | 116 | # Define custom styles to format and colorize the log output. 117 | fn my_styles() -> Styles: 118 | # Log level styles, by default just set colors 119 | var levels = Sections() 120 | levels["FATAL"] = TerminalStyle.new().background("#d4317d") 121 | levels["ERROR"] = TerminalStyle.new().background("#d48244") 122 | levels["INFO"] = TerminalStyle.new().background("#13ed84") 123 | levels["WARN"] = TerminalStyle.new().background("#decf2f") 124 | levels["DEBUG"] = TerminalStyle.new().background("#bd37db") 125 | 126 | var keys = Sections() 127 | keys["name"] = ( 128 | TerminalStyle.new().foreground("#c9a0dc").underline() 129 | ) 130 | 131 | var values = Sections() 132 | values["name"] = TerminalStyle.new().foreground("#d48244").bold() 133 | 134 | return Styles( 135 | levels=levels, 136 | key=TerminalStyle.new().faint(), 137 | separator=TerminalStyle.new().faint(), 138 | keys=keys, 139 | values=values, 140 | ) 141 | 142 | 143 | # The loggers are compiled at build time, so we can reuse it. 144 | alias LOG_LEVEL = DEBUG 145 | 146 | # Build a bound logger with custom processors and styling 147 | alias logger = BoundLogger( 148 | PrintLogger(LOG_LEVEL), formatter=DEFAULT_FORMAT, processors=my_processors, styles=my_styles 149 | ) 150 | 151 | fn main(): 152 | logger.info("Information is good.") 153 | logger.warn("Warnings can be good too.") 154 | logger.error("An error!") 155 | logger.debug("Debugging...") 156 | logger.fatal("uh oh...") 157 | ``` 158 | 159 | Importing the logger into other files works! 160 | 161 | ```mojo 162 | from examples.default import logger 163 | 164 | 165 | fn main(): 166 | logger.info("Hello!") 167 | ``` 168 | 169 | ## TODO 170 | 171 | ### Features 172 | 173 | - Add more processor functions. 174 | - Add support for logging to files via `Logger` struct that uses a writer that implement `io.Writer`. 175 | - Add global logger support once we have file scope support. 176 | - Make formatter flexible and composable. Right now it's only a few predefined formats. 177 | - Exiting on fatal log calls. 178 | - logf functions to specify a specific format for that log message. 179 | - Speed improvements once https://github.com/modularml/mojo/issues/2779 is resolved and enables `mist` to compile text styling at comp time instead of on each and every log call. Providing a STDOUT writer logger instead of print logger will speed it up measurably as well. 180 | - Simple naive JSON formatter to be improved to handle escaped chars, brackets, etc correctly. 181 | 182 | ### Bugs 183 | -------------------------------------------------------------------------------- /external/gojo/net/ip.mojo: -------------------------------------------------------------------------------- 1 | from utils.variant import Variant 2 | from sys.info import os_is_linux, os_is_macos 3 | from ..syscall.types import ( 4 | c_int, 5 | c_char, 6 | c_void, 7 | c_uint, 8 | ) 9 | from ..syscall.net import ( 10 | addrinfo, 11 | addrinfo_unix, 12 | AF_INET, 13 | SOCK_STREAM, 14 | AI_PASSIVE, 15 | sockaddr, 16 | sockaddr_in, 17 | htons, 18 | ntohs, 19 | inet_pton, 20 | inet_ntop, 21 | getaddrinfo, 22 | getaddrinfo_unix, 23 | gai_strerror, 24 | to_char_ptr, 25 | c_charptr_to_string, 26 | ) 27 | 28 | alias AddrInfo = Variant[addrinfo, addrinfo_unix] 29 | 30 | 31 | fn get_addr_info(host: String) raises -> AddrInfo: 32 | var status: Int32 = 0 33 | if os_is_macos(): 34 | var servinfo = Pointer[addrinfo]().alloc(1) 35 | servinfo.store(addrinfo()) 36 | var hints = addrinfo() 37 | hints.ai_family = AF_INET 38 | hints.ai_socktype = SOCK_STREAM 39 | hints.ai_flags = AI_PASSIVE 40 | 41 | var host_ptr = to_char_ptr(host) 42 | 43 | var status = getaddrinfo( 44 | host_ptr, 45 | Pointer[UInt8](), 46 | Pointer.address_of(hints), 47 | Pointer.address_of(servinfo), 48 | ) 49 | if status != 0: 50 | print("getaddrinfo failed to execute with status:", status) 51 | var msg_ptr = gai_strerror(c_int(status)) 52 | _ = external_call["printf", c_int, Pointer[c_char], Pointer[c_char]]( 53 | to_char_ptr("gai_strerror: %s"), msg_ptr 54 | ) 55 | var msg = c_charptr_to_string(msg_ptr) 56 | print("getaddrinfo error message: ", msg) 57 | 58 | if not servinfo: 59 | print("servinfo is null") 60 | raise Error("Failed to get address info. Pointer to addrinfo is null.") 61 | 62 | return servinfo.load() 63 | elif os_is_linux(): 64 | var servinfo = Pointer[addrinfo_unix]().alloc(1) 65 | servinfo.store(addrinfo_unix()) 66 | var hints = addrinfo_unix() 67 | hints.ai_family = AF_INET 68 | hints.ai_socktype = SOCK_STREAM 69 | hints.ai_flags = AI_PASSIVE 70 | 71 | var host_ptr = to_char_ptr(host) 72 | 73 | var status = getaddrinfo_unix( 74 | host_ptr, 75 | Pointer[UInt8](), 76 | Pointer.address_of(hints), 77 | Pointer.address_of(servinfo), 78 | ) 79 | if status != 0: 80 | print("getaddrinfo failed to execute with status:", status) 81 | var msg_ptr = gai_strerror(c_int(status)) 82 | _ = external_call["printf", c_int, Pointer[c_char], Pointer[c_char]]( 83 | to_char_ptr("gai_strerror: %s"), msg_ptr 84 | ) 85 | var msg = c_charptr_to_string(msg_ptr) 86 | print("getaddrinfo error message: ", msg) 87 | 88 | if not servinfo: 89 | print("servinfo is null") 90 | raise Error("Failed to get address info. Pointer to addrinfo is null.") 91 | 92 | return servinfo.load() 93 | else: 94 | raise Error("Windows is not supported yet! Sorry!") 95 | 96 | 97 | fn get_ip_address(host: String) raises -> String: 98 | """Get the IP address of a host.""" 99 | # Call getaddrinfo to get the IP address of the host. 100 | var result = get_addr_info(host) 101 | var ai_addr: Pointer[sockaddr] 102 | var address_family: Int32 = 0 103 | var address_length: UInt32 = 0 104 | if result.isa[addrinfo](): 105 | var addrinfo = result.get[addrinfo]() 106 | ai_addr = addrinfo[].ai_addr 107 | address_family = addrinfo[].ai_family 108 | address_length = addrinfo[].ai_addrlen 109 | else: 110 | var addrinfo = result.get[addrinfo_unix]() 111 | ai_addr = addrinfo[].ai_addr 112 | address_family = addrinfo[].ai_family 113 | address_length = addrinfo[].ai_addrlen 114 | 115 | if not ai_addr: 116 | print("ai_addr is null") 117 | raise Error("Failed to get IP address. getaddrinfo was called successfully, but ai_addr is null.") 118 | 119 | # Cast sockaddr struct to sockaddr_in struct and convert the binary IP to a string using inet_ntop. 120 | var addr_in = ai_addr.bitcast[sockaddr_in]().load() 121 | 122 | return convert_binary_ip_to_string(addr_in.sin_addr.s_addr, address_family, address_length).strip() 123 | 124 | 125 | fn convert_port_to_binary(port: Int) -> UInt16: 126 | return htons(UInt16(port)) 127 | 128 | 129 | fn convert_binary_port_to_int(port: UInt16) -> Int: 130 | return int(ntohs(port)) 131 | 132 | 133 | fn convert_ip_to_binary(ip_address: String, address_family: Int) -> UInt32: 134 | var ip_buffer = Pointer[c_void].alloc(4) 135 | var status = inet_pton(address_family, to_char_ptr(ip_address), ip_buffer) 136 | if status == -1: 137 | print("Failed to convert IP address to binary") 138 | 139 | return ip_buffer.bitcast[c_uint]().load() 140 | 141 | 142 | fn convert_binary_ip_to_string(owned ip_address: UInt32, address_family: Int32, address_length: UInt32) -> String: 143 | """Convert a binary IP address to a string by calling inet_ntop. 144 | 145 | Args: 146 | ip_address: The binary IP address. 147 | address_family: The address family of the IP address. 148 | address_length: The length of the address. 149 | 150 | Returns: 151 | The IP address as a string. 152 | """ 153 | # It seems like the len of the buffer depends on the length of the string IP. 154 | # Allocating 10 works for localhost (127.0.0.1) which I suspect is 9 bytes + 1 null terminator byte. So max should be 16 (15 + 1). 155 | var ip_buffer = Pointer[c_void].alloc(16) 156 | var ip_address_ptr = Pointer.address_of(ip_address).bitcast[c_void]() 157 | _ = inet_ntop(address_family, ip_address_ptr, ip_buffer, 16) 158 | 159 | var string_buf = ip_buffer.bitcast[Int8]() 160 | var index = 0 161 | while True: 162 | if string_buf[index] == 0: 163 | break 164 | index += 1 165 | 166 | return StringRef(string_buf, index) 167 | 168 | 169 | fn build_sockaddr_pointer(ip_address: String, port: Int, address_family: Int) -> Pointer[sockaddr]: 170 | """Build a sockaddr pointer from an IP address and port number. 171 | https://learn.microsoft.com/en-us/windows/win32/winsock/sockaddr-2 172 | https://learn.microsoft.com/en-us/windows/win32/api/ws2def/ns-ws2def-sockaddr_in. 173 | """ 174 | var bin_port = convert_port_to_binary(port) 175 | var bin_ip = convert_ip_to_binary(ip_address, address_family) 176 | 177 | var ai = sockaddr_in(address_family, bin_port, bin_ip, StaticTuple[c_char, 8]()) 178 | return Pointer[sockaddr_in].address_of(ai).bitcast[sockaddr]() 179 | -------------------------------------------------------------------------------- /external/morrow/formatter.mojo: -------------------------------------------------------------------------------- 1 | from collections.vector import InlinedFixedVector 2 | from utils.static_tuple import StaticTuple 3 | from .util import rjust 4 | from .constants import MONTH_NAMES, MONTH_ABBREVIATIONS, DAY_NAMES, DAY_ABBREVIATIONS 5 | from .timezone import UTC_TZ 6 | 7 | alias formatter = _Formatter() 8 | 9 | 10 | struct _Formatter: 11 | var _sub_chrs: InlinedFixedVector[Int, 128] 12 | 13 | fn __init__(inout self): 14 | self._sub_chrs = InlinedFixedVector[Int, 128](0) 15 | for i in range(128): 16 | self._sub_chrs[i] = 0 17 | self._sub_chrs[_Y] = 4 18 | self._sub_chrs[_M] = 4 19 | self._sub_chrs[_D] = 2 20 | self._sub_chrs[_d] = 4 21 | self._sub_chrs[_H] = 2 22 | self._sub_chrs[_h] = 2 23 | self._sub_chrs[_m] = 2 24 | self._sub_chrs[_s] = 2 25 | self._sub_chrs[_S] = 6 26 | self._sub_chrs[_Z] = 3 27 | self._sub_chrs[_A] = 1 28 | self._sub_chrs[_a] = 1 29 | 30 | fn format(self, m: Morrow, fmt: String) raises -> String: 31 | """ 32 | "YYYY[abc]MM" -> repalce("YYYY") + "abc" + replace("MM") 33 | """ 34 | if len(fmt) == 0: 35 | return "" 36 | var ret: String = "" 37 | var in_bracket = False 38 | var start_idx = 0 39 | for i in range(len(fmt)): 40 | if fmt[i] == "[": 41 | if in_bracket: 42 | ret += "[" 43 | else: 44 | in_bracket = True 45 | ret += self.replace(m, fmt[start_idx:i]) 46 | start_idx = i + 1 47 | elif fmt[i] == "]": 48 | if in_bracket: 49 | ret += fmt[start_idx:i] 50 | in_bracket = False 51 | else: 52 | ret += self.replace(m, fmt[start_idx:i]) 53 | ret += "]" 54 | start_idx = i + 1 55 | if in_bracket: 56 | ret += "[" 57 | if start_idx < len(fmt): 58 | ret += self.replace(m, fmt[start_idx:]) 59 | return ret 60 | 61 | fn replace(self, m: Morrow, s: String) raises -> String: 62 | """ 63 | split token and replace 64 | """ 65 | if len(s) == 0: 66 | return "" 67 | var ret: String = "" 68 | var match_chr_ord = 0 69 | var match_count = 0 70 | for i in range(len(s)): 71 | var c = ord(s[i]) 72 | if 0 < c < 128 and self._sub_chrs[c] > 0: 73 | if c == match_chr_ord: 74 | match_count += 1 75 | else: 76 | ret += self.replace_token(m, match_chr_ord, match_count) 77 | match_chr_ord = c 78 | match_count = 1 79 | if match_count == self._sub_chrs[c]: 80 | ret += self.replace_token(m, match_chr_ord, match_count) 81 | match_chr_ord = 0 82 | else: 83 | if match_chr_ord > 0: 84 | ret += self.replace_token(m, match_chr_ord, match_count) 85 | match_chr_ord = 0 86 | ret += s[i] 87 | if match_chr_ord > 0: 88 | ret += self.replace_token(m, match_chr_ord, match_count) 89 | return ret 90 | 91 | fn replace_token(self, m: Morrow, token: String, token_count: Int) raises -> String: 92 | if token == _Y: 93 | if token_count == 1: 94 | return "Y" 95 | if token_count == 2: 96 | return rjust(m.year, 4, "0")[2:4] 97 | if token_count == 4: 98 | return rjust(m.year, 4, "0") 99 | elif token == _M: 100 | if token_count == 1: 101 | return String(m.month) 102 | if token_count == 2: 103 | return rjust(m.month, 2, "0") 104 | if token_count == 3: 105 | return String(MONTH_ABBREVIATIONS[m.month]) 106 | if token_count == 4: 107 | return String(MONTH_NAMES[m.month]) 108 | elif token == _D: 109 | if token_count == 1: 110 | return String(m.day) 111 | if token_count == 2: 112 | return rjust(m.day, 2, "0") 113 | elif token == _H: 114 | if token_count == 1: 115 | return String(m.hour) 116 | if token_count == 2: 117 | return rjust(m.hour, 2, "0") 118 | elif token == _h: 119 | var h_12 = m.hour 120 | if m.hour > 12: 121 | h_12 -= 12 122 | if token_count == 1: 123 | return String(h_12) 124 | if token_count == 2: 125 | return rjust(h_12, 2, "0") 126 | elif token == _m: 127 | if token_count == 1: 128 | return String(m.minute) 129 | if token_count == 2: 130 | return rjust(m.minute, 2, "0") 131 | elif token == _s: 132 | if token_count == 1: 133 | return String(m.second) 134 | if token_count == 2: 135 | return rjust(m.second, 2, "0") 136 | elif token == _S: 137 | if token_count == 1: 138 | return String(m.microsecond // 100000) 139 | if token_count == 2: 140 | return rjust(m.microsecond // 10000, 2, "0") 141 | if token_count == 3: 142 | return rjust(m.microsecond // 1000, 3, "0") 143 | if token_count == 4: 144 | return rjust(m.microsecond // 100, 4, "0") 145 | if token_count == 5: 146 | return rjust(m.microsecond // 10, 5, "0") 147 | if token_count == 6: 148 | return rjust(m.microsecond, 6, "0") 149 | elif token == _d: 150 | if token_count == 1: 151 | return String(m.isoweekday()) 152 | if token_count == 3: 153 | return String(DAY_ABBREVIATIONS[m.isoweekday()]) 154 | if token_count == 4: 155 | return String(DAY_NAMES[m.isoweekday()]) 156 | elif token == _Z: 157 | if token_count == 3: 158 | return UTC_TZ.name if m.tz.is_none() else m.tz.name 159 | var separator = "" if token_count == 1 else ":" 160 | if m.tz.is_none(): 161 | return UTC_TZ.format(separator) 162 | else: 163 | return m.tz.format(separator) 164 | 165 | elif token == _a: 166 | return "am" if m.hour < 12 else "pm" 167 | elif token == _A: 168 | return "AM" if m.hour < 12 else "PM" 169 | return "" 170 | 171 | 172 | alias _Y = ord("Y") 173 | alias _M = ord("M") 174 | alias _D = ord("D") 175 | alias _d = ord("d") 176 | alias _H = ord("H") 177 | alias _h = ord("h") 178 | alias _m = ord("m") 179 | alias _s = ord("s") 180 | alias _S = ord("S") 181 | alias _X = ord("X") 182 | alias _x = ord("x") 183 | alias _Z = ord("Z") 184 | alias _A = ord("A") 185 | alias _a = ord("a") 186 | -------------------------------------------------------------------------------- /external/gojo/unicode/utf8/runes.mojo: -------------------------------------------------------------------------------- 1 | """Almost all of the actual implementation in this module was written by @mzaks (https://github.com/mzaks)! 2 | This would not be possible without his help. 3 | """ 4 | 5 | from ...builtins import Rune 6 | from algorithm.functional import vectorize 7 | from memory.unsafe import DTypePointer 8 | from sys.info import simdwidthof 9 | from math.bit import ctlz 10 | 11 | 12 | # The default lowest and highest continuation byte. 13 | alias locb = 0b10000000 14 | alias hicb = 0b10111111 15 | alias RUNE_SELF = 0x80 # Characters below RuneSelf are represented as themselves in a single byte 16 | 17 | 18 | # acceptRange gives the range of valid values for the second byte in a UTF-8 19 | # sequence. 20 | @value 21 | struct AcceptRange(CollectionElement): 22 | var lo: UInt8 # lowest value for second byte. 23 | var hi: UInt8 # highest value for second byte. 24 | 25 | 26 | # ACCEPT_RANGES has size 16 to avoid bounds checks in the code that uses it. 27 | alias ACCEPT_RANGES = List[AcceptRange]( 28 | AcceptRange(locb, hicb), 29 | AcceptRange(0xA0, hicb), 30 | AcceptRange(locb, 0x9F), 31 | AcceptRange(0x90, hicb), 32 | AcceptRange(locb, 0x8F), 33 | ) 34 | 35 | # These names of these constants are chosen to give nice alignment in the 36 | # table below. The first nibble is an index into acceptRanges or F for 37 | # special one-byte cases. The second nibble is the Rune length or the 38 | # Status for the special one-byte case. 39 | alias xx = 0xF1 # invalid: size 1 40 | alias as1 = 0xF0 # ASCII: size 1 41 | alias s1 = 0x02 # accept 0, size 2 42 | alias s2 = 0x13 # accept 1, size 3 43 | alias s3 = 0x03 # accept 0, size 3 44 | alias s4 = 0x23 # accept 2, size 3 45 | alias s5 = 0x34 # accept 3, size 4 46 | alias s6 = 0x04 # accept 0, size 4 47 | alias s7 = 0x44 # accept 4, size 4 48 | 49 | 50 | # first is information about the first byte in a UTF-8 sequence. 51 | var first = List[UInt8]( 52 | # 1 2 3 4 5 6 7 8 9 A B C D E F 53 | as1, 54 | as1, 55 | as1, 56 | as1, 57 | as1, 58 | as1, 59 | as1, 60 | as1, 61 | as1, 62 | as1, 63 | as1, 64 | as1, 65 | as1, 66 | as1, 67 | as1, 68 | as1, # 0x00-0x0F 69 | as1, 70 | as1, 71 | as1, 72 | as1, 73 | as1, 74 | as1, 75 | as1, 76 | as1, 77 | as1, 78 | as1, 79 | as1, 80 | as1, 81 | as1, 82 | as1, 83 | as1, 84 | as1, # 0x10-0x1F 85 | as1, 86 | as1, 87 | as1, 88 | as1, 89 | as1, 90 | as1, 91 | as1, 92 | as1, 93 | as1, 94 | as1, 95 | as1, 96 | as1, 97 | as1, 98 | as1, 99 | as1, 100 | as1, # 0x20-0x2F 101 | as1, 102 | as1, 103 | as1, 104 | as1, 105 | as1, 106 | as1, 107 | as1, 108 | as1, 109 | as1, 110 | as1, 111 | as1, 112 | as1, 113 | as1, 114 | as1, 115 | as1, 116 | as1, # 0x30-0x3F 117 | as1, 118 | as1, 119 | as1, 120 | as1, 121 | as1, 122 | as1, 123 | as1, 124 | as1, 125 | as1, 126 | as1, 127 | as1, 128 | as1, 129 | as1, 130 | as1, 131 | as1, 132 | as1, # 0x40-0x4F 133 | as1, 134 | as1, 135 | as1, 136 | as1, 137 | as1, 138 | as1, 139 | as1, 140 | as1, 141 | as1, 142 | as1, 143 | as1, 144 | as1, 145 | as1, 146 | as1, 147 | as1, 148 | as1, # 0x50-0x5F 149 | as1, 150 | as1, 151 | as1, 152 | as1, 153 | as1, 154 | as1, 155 | as1, 156 | as1, 157 | as1, 158 | as1, 159 | as1, 160 | as1, 161 | as1, 162 | as1, 163 | as1, 164 | as1, # 0x60-0x6F 165 | as1, 166 | as1, 167 | as1, 168 | as1, 169 | as1, 170 | as1, 171 | as1, 172 | as1, 173 | as1, 174 | as1, 175 | as1, 176 | as1, 177 | as1, 178 | as1, 179 | as1, 180 | as1, # 0x70-0x7F 181 | # 1 2 3 4 5 6 7 8 9 A B C D E F 182 | xx, 183 | xx, 184 | xx, 185 | xx, 186 | xx, 187 | xx, 188 | xx, 189 | xx, 190 | xx, 191 | xx, 192 | xx, 193 | xx, 194 | xx, 195 | xx, 196 | xx, 197 | xx, # 0x80-0x8F 198 | xx, 199 | xx, 200 | xx, 201 | xx, 202 | xx, 203 | xx, 204 | xx, 205 | xx, 206 | xx, 207 | xx, 208 | xx, 209 | xx, 210 | xx, 211 | xx, 212 | xx, 213 | xx, # 0x90-0x9F 214 | xx, 215 | xx, 216 | xx, 217 | xx, 218 | xx, 219 | xx, 220 | xx, 221 | xx, 222 | xx, 223 | xx, 224 | xx, 225 | xx, 226 | xx, 227 | xx, 228 | xx, 229 | xx, # 0xA0-0xAF 230 | xx, 231 | xx, 232 | xx, 233 | xx, 234 | xx, 235 | xx, 236 | xx, 237 | xx, 238 | xx, 239 | xx, 240 | xx, 241 | xx, 242 | xx, 243 | xx, 244 | xx, 245 | xx, # 0xB0-0xBF 246 | xx, 247 | xx, 248 | s1, 249 | s1, 250 | s1, 251 | s1, 252 | s1, 253 | s1, 254 | s1, 255 | s1, 256 | s1, 257 | s1, 258 | s1, 259 | s1, 260 | s1, 261 | s1, # 0xC0-0xCF 262 | s1, 263 | s1, 264 | s1, 265 | s1, 266 | s1, 267 | s1, 268 | s1, 269 | s1, 270 | s1, 271 | s1, 272 | s1, 273 | s1, 274 | s1, 275 | s1, 276 | s1, 277 | s1, # 0xD0-0xDF 278 | s2, 279 | s3, 280 | s3, 281 | s3, 282 | s3, 283 | s3, 284 | s3, 285 | s3, 286 | s3, 287 | s3, 288 | s3, 289 | s3, 290 | s3, 291 | s4, 292 | s3, 293 | s3, # 0xE0-0xEF 294 | s5, 295 | s6, 296 | s6, 297 | s6, 298 | s7, 299 | xx, 300 | xx, 301 | xx, 302 | xx, 303 | xx, 304 | xx, 305 | xx, 306 | xx, 307 | xx, 308 | xx, 309 | xx, # 0xF0-0xFF 310 | ) 311 | 312 | 313 | alias simd_width_u8 = simdwidthof[DType.uint8]() 314 | 315 | 316 | fn rune_count_in_string(s: String) -> Int: 317 | """Count the number of runes in a string. 318 | 319 | Args: 320 | s: The string to count runes in. 321 | 322 | Returns: 323 | The number of runes in the string. 324 | """ 325 | var p = s._as_ptr().bitcast[DType.uint8]() 326 | var string_byte_length = len(s) 327 | var result = 0 328 | 329 | @parameter 330 | fn count[simd_width: Int](offset: Int): 331 | result += int(((p.load[width=simd_width](offset) >> 6) != 0b10).cast[DType.uint8]().reduce_add()) 332 | 333 | vectorize[count, simd_width_u8](string_byte_length) 334 | return result 335 | 336 | 337 | fn string_iterator(s: String, func: fn (String) -> None): 338 | """Iterate over the runes in a string and call the given function with each rune. 339 | 340 | Args: 341 | s: The string to iterate over. 342 | func: The function to call with each rune. 343 | """ 344 | var bytes = len(s) 345 | var p = s._as_ptr().bitcast[DType.uint8]() 346 | while bytes > 0: 347 | var char_length = int((p.load() >> 7 == 0).cast[DType.uint8]() * 1 + ctlz(~p.load())) 348 | var sp = DTypePointer[DType.int8].alloc(char_length + 1) 349 | memcpy(sp, p.bitcast[DType.int8](), char_length) 350 | sp[char_length] = 0 351 | func(String(sp, char_length + 1)) 352 | bytes -= char_length 353 | p += char_length 354 | -------------------------------------------------------------------------------- /external/gojo/fmt/fmt.mojo: -------------------------------------------------------------------------------- 1 | """Formatting options 2 | General 3 | %v the value in a default format 4 | when printing structs, the plus flag (%+v) adds field names 5 | 6 | Boolean 7 | %t the word true or false 8 | 9 | Integer 10 | %d base 10 11 | %q a single-quoted character literal. 12 | %x base 16, with lower-case letters for a-f 13 | %X base 16, with upper-case letters for A-F 14 | 15 | Floating-point and complex constituents: 16 | %f decimal point but no exponent, e.g. 123.456 17 | 18 | String and slice of bytes (treated equivalently with these verbs): 19 | %s the uninterpreted bytes of the string or slice 20 | %q a double-quoted string 21 | 22 | TODO: 23 | - Add support for more formatting options 24 | - Switch to buffered writing to avoid multiple string concatenations 25 | - Add support for width and precision formatting options 26 | - Handle escaping for String's %q 27 | """ 28 | 29 | from utils.variant import Variant 30 | from math import floor 31 | from ..builtins import Byte 32 | 33 | alias Args = Variant[String, Int, Float64, Bool, List[Byte]] 34 | 35 | 36 | fn replace_first(s: String, old: String, new: String) -> String: 37 | """Replace the first occurrence of a substring in a string. 38 | 39 | Args: 40 | s: The original string 41 | old: The substring to be replaced 42 | new: The new substring 43 | 44 | Returns: 45 | The string with the first occurrence of the old substring replaced by the new one. 46 | """ 47 | # Find the first occurrence of the old substring 48 | var index = s.find(old) 49 | 50 | # If the old substring is found, replace it 51 | if index != -1: 52 | return s[:index] + new + s[index + len(old) :] 53 | 54 | # If the old substring is not found, return the original string 55 | return s 56 | 57 | 58 | fn find_first_verb(s: String, verbs: List[String]) -> String: 59 | """Find the first occurrence of a verb in a string. 60 | 61 | Args: 62 | s: The original string 63 | verbs: The list of verbs to search for. 64 | 65 | Returns: 66 | The verb to replace. 67 | """ 68 | var index = -1 69 | var verb: String = "" 70 | 71 | for v in verbs: 72 | var i = s.find(v[]) 73 | if i != -1 and (index == -1 or i < index): 74 | index = i 75 | verb = v[] 76 | 77 | return verb 78 | 79 | 80 | alias BASE10_TO_BASE16 = List[String]("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f") 81 | 82 | 83 | fn convert_base10_to_base16(value: Int) -> String: 84 | """Converts a base 10 number to base 16. 85 | 86 | Args: 87 | value: Base 10 number. 88 | 89 | Returns: 90 | Base 16 number as a String. 91 | """ 92 | 93 | var val: Float64 = 0.0 94 | var result: Float64 = value 95 | var base16: String = "" 96 | while result > 1: 97 | var temp = result / 16 98 | var floor_result = floor(temp) 99 | var remainder = temp - floor_result 100 | result = floor_result 101 | val = 16 * remainder 102 | 103 | base16 = BASE10_TO_BASE16[int(val)] + base16 104 | 105 | return base16 106 | 107 | 108 | fn format_string(format: String, arg: String) -> String: 109 | var verb = find_first_verb(format, List[String]("%s", "%q")) 110 | var arg_to_place = arg 111 | if verb == "%q": 112 | arg_to_place = '"' + arg + '"' 113 | 114 | return replace_first(format, String("%s"), arg) 115 | 116 | 117 | fn format_bytes(format: String, arg: List[Byte]) -> String: 118 | var argument = arg 119 | if argument[-1] != 0: 120 | argument.append(0) 121 | 122 | return format_string(format, argument) 123 | 124 | 125 | fn format_integer(format: String, arg: Int) -> String: 126 | var verb = find_first_verb(format, List[String]("%x", "%X", "%d", "%q")) 127 | var arg_to_place = String(arg) 128 | if verb == "%x": 129 | arg_to_place = String(convert_base10_to_base16(arg)).lower() 130 | elif verb == "%X": 131 | arg_to_place = String(convert_base10_to_base16(arg)).upper() 132 | elif verb == "%q": 133 | arg_to_place = "'" + String(arg) + "'" 134 | 135 | return replace_first(format, verb, arg_to_place) 136 | 137 | 138 | fn format_float(format: String, arg: Float64) -> String: 139 | return replace_first(format, String("%f"), arg) 140 | 141 | 142 | fn format_boolean(format: String, arg: Bool) -> String: 143 | var value: String = "False" 144 | if arg: 145 | value = "True" 146 | 147 | return replace_first(format, String("%t"), value) 148 | 149 | 150 | # If the number of arguments does not match the number of format specifiers 151 | alias BadArgCount = "(BAD ARG COUNT)" 152 | 153 | 154 | fn sprintf(formatting: String, *args: Args) -> String: 155 | var text = formatting 156 | var raw_percent_count = formatting.count("%%") * 2 157 | var formatter_count = formatting.count("%") - raw_percent_count 158 | 159 | if formatter_count != len(args): 160 | return BadArgCount 161 | 162 | for i in range(len(args)): 163 | var argument = args[i] 164 | if argument.isa[String](): 165 | text = format_string(text, argument.get[String]()[]) 166 | elif argument.isa[List[Byte]](): 167 | text = format_bytes(text, argument.get[List[Byte]]()[]) 168 | elif argument.isa[Int](): 169 | text = format_integer(text, argument.get[Int]()[]) 170 | elif argument.isa[Float64](): 171 | text = format_float(text, argument.get[Float64]()[]) 172 | elif argument.isa[Bool](): 173 | text = format_boolean(text, argument.get[Bool]()[]) 174 | 175 | return text 176 | 177 | 178 | # TODO: temporary until we have arg packing. 179 | fn sprintf_str(formatting: String, args: List[String]) raises -> String: 180 | var text = formatting 181 | var formatter_count = formatting.count("%") 182 | 183 | if formatter_count > len(args): 184 | raise Error("Not enough arguments for format string") 185 | elif formatter_count < len(args): 186 | raise Error("Too many arguments for format string") 187 | 188 | for i in range(len(args)): 189 | text = format_string(text, args[i]) 190 | 191 | return text 192 | 193 | 194 | fn printf(formatting: String, *args: Args) raises: 195 | var text = formatting 196 | var raw_percent_count = formatting.count("%%") * 2 197 | var formatter_count = formatting.count("%") - raw_percent_count 198 | 199 | if formatter_count > len(args): 200 | raise Error("Not enough arguments for format string") 201 | elif formatter_count < len(args): 202 | raise Error("Too many arguments for format string") 203 | 204 | for i in range(len(args)): 205 | var argument = args[i] 206 | if argument.isa[String](): 207 | text = format_string(text, argument.get[String]()[]) 208 | elif argument.isa[List[Byte]](): 209 | text = format_bytes(text, argument.get[List[Byte]]()[]) 210 | elif argument.isa[Int](): 211 | text = format_integer(text, argument.get[Int]()[]) 212 | elif argument.isa[Float64](): 213 | text = format_float(text, argument.get[Float64]()[]) 214 | elif argument.isa[Bool](): 215 | text = format_boolean(text, argument.get[Bool]()[]) 216 | else: 217 | raise Error("Unknown for argument #" + String(i)) 218 | 219 | print(text) 220 | -------------------------------------------------------------------------------- /external/gojo/net/tcp.mojo: -------------------------------------------------------------------------------- 1 | from ..builtins import Byte 2 | from ..syscall.net import SO_REUSEADDR 3 | from .net import Connection, Conn 4 | from .address import TCPAddr, NetworkType, split_host_port 5 | from .socket import Socket 6 | 7 | 8 | # Time in nanoseconds 9 | alias Duration = Int 10 | alias DEFAULT_BUFFER_SIZE = 4096 11 | alias DEFAULT_TCP_KEEP_ALIVE = Duration(15 * 1000 * 1000 * 1000) # 15 seconds 12 | 13 | 14 | fn resolve_internet_addr(network: String, address: String) raises -> TCPAddr: 15 | var host: String = "" 16 | var port: String = "" 17 | var portnum: Int = 0 18 | if ( 19 | network == NetworkType.tcp.value 20 | or network == NetworkType.tcp4.value 21 | or network == NetworkType.tcp6.value 22 | or network == NetworkType.udp.value 23 | or network == NetworkType.udp4.value 24 | or network == NetworkType.udp6.value 25 | ): 26 | if address != "": 27 | var host_port = split_host_port(address) 28 | host = host_port.host 29 | port = host_port.port 30 | portnum = atol(port.__str__()) 31 | elif network == NetworkType.ip.value or network == NetworkType.ip4.value or network == NetworkType.ip6.value: 32 | if address != "": 33 | host = address 34 | elif network == NetworkType.unix.value: 35 | raise Error("Unix addresses not supported yet") 36 | else: 37 | raise Error("unsupported network type: " + network) 38 | return TCPAddr(host, portnum) 39 | 40 | 41 | # TODO: For now listener is paired with TCP until we need to support 42 | # more than one type of Connection or Listener 43 | @value 44 | struct ListenConfig(CollectionElement): 45 | var keep_alive: Duration 46 | 47 | fn listen(self, network: String, address: String) raises -> TCPListener: 48 | var tcp_addr = resolve_internet_addr(network, address) 49 | var socket = Socket(local_address=tcp_addr) 50 | socket.bind(tcp_addr.ip, tcp_addr.port) 51 | socket.set_socket_option(SO_REUSEADDR, 1) 52 | socket.listen() 53 | print(String("Listening on ") + socket.local_address) 54 | return TCPListener(socket^, self, network, address) 55 | 56 | 57 | trait Listener(Movable): 58 | # Raising here because a Result[Optional[Connection], Error] is funky. 59 | fn accept(self) raises -> Connection: 60 | ... 61 | 62 | fn close(inout self) -> Error: 63 | ... 64 | 65 | fn addr(self) raises -> TCPAddr: 66 | ... 67 | 68 | 69 | @value 70 | struct TCPConnection(Conn): 71 | """TCPConn is an implementation of the Conn interface for TCP network connections. 72 | 73 | Args: 74 | connection: The underlying Connection. 75 | """ 76 | 77 | var _connection: Connection 78 | 79 | fn __init__(inout self, connection: Connection): 80 | self._connection = connection 81 | 82 | fn __init__(inout self, owned socket: Socket): 83 | self._connection = Connection(socket^) 84 | 85 | fn __moveinit__(inout self, owned existing: Self): 86 | self._connection = existing._connection^ 87 | 88 | fn read(inout self, inout dest: List[Byte]) -> (Int, Error): 89 | """Reads data from the underlying file descriptor. 90 | 91 | Args: 92 | dest: The buffer to read data into. 93 | 94 | Returns: 95 | The number of bytes read, or an error if one occurred. 96 | """ 97 | var bytes_written: Int 98 | var err: Error 99 | bytes_written, err = self._connection.read(dest) 100 | if err: 101 | if str(err) != io.EOF: 102 | return 0, err 103 | 104 | return bytes_written, Error() 105 | 106 | fn write(inout self, src: List[Byte]) -> (Int, Error): 107 | """Writes data to the underlying file descriptor. 108 | 109 | Args: 110 | src: The buffer to read data into. 111 | 112 | Returns: 113 | The number of bytes written, or an error if one occurred. 114 | """ 115 | var bytes_written: Int 116 | var err: Error 117 | bytes_written, err = self._connection.write(src) 118 | if err: 119 | return 0, err 120 | 121 | return bytes_written, Error() 122 | 123 | fn close(inout self) -> Error: 124 | """Closes the underlying file descriptor. 125 | 126 | Returns: 127 | An error if one occurred, or None if the file descriptor was closed successfully. 128 | """ 129 | return self._connection.close() 130 | 131 | fn local_address(self) -> TCPAddr: 132 | """Returns the local network address. 133 | The Addr returned is shared by all invocations of local_address, so do not modify it. 134 | 135 | Returns: 136 | The local network address. 137 | """ 138 | return self._connection.local_address() 139 | 140 | fn remote_address(self) -> TCPAddr: 141 | """Returns the remote network address. 142 | The Addr returned is shared by all invocations of remote_address, so do not modify it. 143 | 144 | Returns: 145 | The remote network address. 146 | """ 147 | return self._connection.remote_address() 148 | 149 | 150 | fn listen_tcp(network: String, local_address: TCPAddr) raises -> TCPListener: 151 | """Creates a new TCP listener. 152 | 153 | Args: 154 | network: The network type. 155 | local_address: The local address to listen on. 156 | """ 157 | return ListenConfig(DEFAULT_TCP_KEEP_ALIVE).listen(network, local_address.ip + ":" + str(local_address.port)) 158 | 159 | 160 | fn listen_tcp(network: String, local_address: String) raises -> TCPListener: 161 | """Creates a new TCP listener. 162 | 163 | Args: 164 | network: The network type. 165 | local_address: The address to listen on. The format is "host:port". 166 | """ 167 | return ListenConfig(DEFAULT_TCP_KEEP_ALIVE).listen(network, local_address) 168 | 169 | 170 | struct TCPListener(Listener): 171 | var _file_descriptor: Socket 172 | var listen_config: ListenConfig 173 | var network_type: String 174 | var address: String 175 | 176 | fn __init__( 177 | inout self, 178 | owned file_descriptor: Socket, 179 | listen_config: ListenConfig, 180 | network_type: String, 181 | address: String, 182 | ): 183 | self._file_descriptor = file_descriptor^ 184 | self.listen_config = listen_config 185 | self.network_type = network_type 186 | self.address = address 187 | 188 | fn __moveinit__(inout self, owned existing: Self): 189 | self._file_descriptor = existing._file_descriptor^ 190 | self.listen_config = existing.listen_config^ 191 | self.network_type = existing.network_type 192 | self.address = existing.address 193 | 194 | fn listen(self) raises -> Self: 195 | return self.listen_config.listen(self.network_type, self.address) 196 | 197 | fn accept(self) raises -> Connection: 198 | return Connection(self._file_descriptor.accept()) 199 | 200 | fn accept_tcp(self) raises -> TCPConnection: 201 | return TCPConnection(self._file_descriptor.accept()) 202 | 203 | fn close(inout self) -> Error: 204 | return self._file_descriptor.close() 205 | 206 | fn addr(self) raises -> TCPAddr: 207 | return resolve_internet_addr(self.network_type, self.address) 208 | -------------------------------------------------------------------------------- /external/gojo/bytes/reader.mojo: -------------------------------------------------------------------------------- 1 | from collections.optional import Optional 2 | from ..builtins import cap, copy, Byte, panic 3 | import ..io 4 | 5 | 6 | @value 7 | struct Reader( 8 | Copyable, 9 | Sized, 10 | io.Reader, 11 | io.ReaderAt, 12 | io.WriterTo, 13 | io.Seeker, 14 | io.ByteReader, 15 | io.ByteScanner, 16 | ): 17 | """A Reader implements the io.Reader, io.ReaderAt, io.WriterTo, io.Seeker, 18 | io.ByteScanner, and io.RuneScanner Interfaces by reading from 19 | a byte slice. 20 | Unlike a [Buffer], a Reader is read-only and supports seeking. 21 | The zero value for Reader operates like a Reader of an empty slice. 22 | """ 23 | 24 | var buffer: List[Byte] 25 | var index: Int64 # current reading index 26 | var prev_rune: Int # index of previous rune; or < 0 27 | 28 | fn __len__(self) -> Int: 29 | """len returns the number of bytes of the unread portion of the 30 | slice.""" 31 | if self.index >= len(self.buffer): 32 | return 0 33 | 34 | return int(len(self.buffer) - self.index) 35 | 36 | fn size(self) -> Int: 37 | """Returns the original length of the underlying byte slice. 38 | Size is the number of bytes available for reading via [Reader.ReadAt]. 39 | The result is unaffected by any method calls except [Reader.Reset].""" 40 | return len(self.buffer) 41 | 42 | fn read(inout self, inout dest: List[Byte]) -> (Int, Error): 43 | """Reads from the internal buffer into the dest List[Byte] struct. 44 | Implements the [io.Reader] Interface. 45 | 46 | Args: 47 | dest: The destination List[Byte] struct to read into. 48 | 49 | Returns: 50 | Int: The number of bytes read into dest.""" 51 | if self.index >= len(self.buffer): 52 | return 0, Error(io.EOF) 53 | 54 | self.prev_rune = -1 55 | var unread_bytes = self.buffer[int(self.index) : len(self.buffer)] 56 | var bytes_read = copy(dest, unread_bytes) 57 | 58 | self.index += bytes_read 59 | return bytes_read, Error() 60 | 61 | fn read_at(self, inout dest: List[Byte], off: Int64) -> (Int, Error): 62 | """Reads len(dest) bytes into dest beginning at byte offset off. 63 | Implements the [io.ReaderAt] Interface. 64 | 65 | Args: 66 | dest: The destination List[Byte] struct to read into. 67 | off: The offset to start reading from. 68 | 69 | Returns: 70 | Int: The number of bytes read into dest. 71 | """ 72 | # cannot modify state - see io.ReaderAt 73 | if off < 0: 74 | return 0, Error("bytes.Reader.read_at: negative offset") 75 | 76 | if off >= Int64(len(self.buffer)): 77 | return 0, Error(io.EOF) 78 | 79 | var unread_bytes = self.buffer[int(off) : len(self.buffer)] 80 | var bytes_written = copy(dest, unread_bytes) 81 | if bytes_written < len(dest): 82 | return 0, Error(io.EOF) 83 | 84 | return bytes_written, Error() 85 | 86 | fn read_byte(inout self) -> (Byte, Error): 87 | """Reads and returns a single byte from the internal buffer. Implements the [io.ByteReader] Interface.""" 88 | self.prev_rune = -1 89 | if self.index >= len(self.buffer): 90 | return Int8(0), Error(io.EOF) 91 | 92 | var byte = self.buffer[int(self.index)] 93 | self.index += 1 94 | return byte, Error() 95 | 96 | fn unread_byte(inout self) -> Error: 97 | """Unreads the last byte read by moving the read position back by one. 98 | Complements [Reader.read_byte] in implementing the [io.ByteScanner] Interface. 99 | """ 100 | if self.index <= 0: 101 | return Error("bytes.Reader.unread_byte: at beginning of slice") 102 | 103 | self.prev_rune = -1 104 | self.index -= 1 105 | 106 | return Error() 107 | 108 | # # read_rune implements the [io.RuneReader] Interface. 109 | # fn read_rune(self) (ch rune, size Int, err error): 110 | # if self.index >= Int64(len(self.buffer)): 111 | # self.prev_rune = -1 112 | # return 0, 0, io.EOF 113 | 114 | # self.prev_rune = Int(self.index) 115 | # if c := self.buffer[self.index]; c < utf8.RuneSelf: 116 | # self.index+= 1 117 | # return rune(c), 1, nil 118 | 119 | # ch, size = utf8.DecodeRune(self.buffer[self.index:]) 120 | # self.index += Int64(size) 121 | # return 122 | 123 | # # unread_rune complements [Reader.read_rune] in implementing the [io.RuneScanner] Interface. 124 | # fn unread_rune(self) error: 125 | # if self.index <= 0: 126 | # return errors.New("bytes.Reader.unread_rune: at beginning of slice") 127 | 128 | # if self.prev_rune < 0: 129 | # return errors.New("bytes.Reader.unread_rune: previous operation was not read_rune") 130 | 131 | # self.index = Int64(self.prev_rune) 132 | # self.prev_rune = -1 133 | # return nil 134 | 135 | fn seek(inout self, offset: Int64, whence: Int) -> (Int64, Error): 136 | """Moves the read position to the specified offset from the specified whence. 137 | Implements the [io.Seeker] Interface. 138 | 139 | Args: 140 | offset: The offset to move to. 141 | whence: The reference point for offset. 142 | 143 | Returns: 144 | The new position in which the next read will start from. 145 | """ 146 | self.prev_rune = -1 147 | var position: Int64 = 0 148 | 149 | if whence == io.SEEK_START: 150 | position = offset 151 | elif whence == io.SEEK_CURRENT: 152 | position = self.index + offset 153 | elif whence == io.SEEK_END: 154 | position = len(self.buffer) + offset 155 | else: 156 | return Int64(0), Error("bytes.Reader.seek: invalid whence") 157 | 158 | if position < 0: 159 | return Int64(0), Error("bytes.Reader.seek: negative position") 160 | 161 | self.index = position 162 | return position, Error() 163 | 164 | fn write_to[W: io.Writer](inout self, inout writer: W) -> (Int64, Error): 165 | """Writes data to w until the buffer is drained or an error occurs. 166 | implements the [io.WriterTo] Interface. 167 | 168 | Args: 169 | writer: The writer to write to. 170 | """ 171 | self.prev_rune = -1 172 | if self.index >= len(self.buffer): 173 | return Int64(0), Error() 174 | 175 | var bytes = self.buffer[int(self.index) : len(self.buffer)] 176 | var write_count: Int 177 | var err: Error 178 | write_count, err = writer.write(bytes) 179 | if write_count > len(bytes): 180 | panic("bytes.Reader.write_to: invalid Write count") 181 | 182 | self.index += write_count 183 | if write_count != len(bytes): 184 | return Int64(write_count), Error(io.ERR_SHORT_WRITE) 185 | 186 | return Int64(write_count), Error() 187 | 188 | fn reset(inout self, buffer: List[Byte]): 189 | """Resets the [Reader.Reader] to be reading from b. 190 | 191 | Args: 192 | buffer: The new buffer to read from. 193 | """ 194 | self.buffer = buffer 195 | self.index = 0 196 | self.prev_rune = -1 197 | 198 | 199 | fn new_reader(buffer: List[Byte]) -> Reader: 200 | """Returns a new [Reader.Reader] reading from b. 201 | 202 | Args: 203 | buffer: The new buffer to read from. 204 | 205 | """ 206 | return Reader(buffer, 0, -1) 207 | 208 | 209 | fn new_reader(buffer: String) -> Reader: 210 | """Returns a new [Reader.Reader] reading from b. 211 | 212 | Args: 213 | buffer: The new buffer to read from. 214 | 215 | """ 216 | return Reader(buffer.as_bytes(), 0, -1) 217 | -------------------------------------------------------------------------------- /external/hue/soft_palettegen.mojo: -------------------------------------------------------------------------------- 1 | from collections.optional import Optional 2 | from random import random_si64 3 | from math.limit import inf 4 | import math 5 | from .math import sq 6 | from .color import lab 7 | 8 | 9 | # The algorithm works in L*a*b* color space and converts to RGB in the end. 10 | # L* in [0..1], a* and b* in [-1..1] 11 | @register_passable("trivial") 12 | struct lab_t(EqualityComparable): 13 | var L: Float64 14 | var A: Float64 15 | var B: Float64 16 | 17 | fn __init__(inout self, L: Float64, A: Float64, B: Float64): 18 | self.L = L 19 | self.A = A 20 | self.B = B 21 | 22 | fn __eq__(self, other: lab_t) -> Bool: 23 | return self.L == other.L and self.A == other.A and self.B == other.B 24 | 25 | fn __ne__(self, other: lab_t) -> Bool: 26 | return self.L != other.L or self.A != other.A or self.B != other.B 27 | 28 | 29 | fn in_stack(haystack: List[lab_t], upto: Int, needle: lab_t) -> Bool: 30 | var i = 0 31 | while i < upto and i < len(haystack): 32 | if haystack[i] == needle: 33 | return True 34 | i += 1 35 | 36 | return False 37 | 38 | 39 | fn labs_2_cols(labs: List[lab_t]) -> List[Color]: 40 | var lab_count = len(labs) 41 | var cols = List[Color](capacity=lab_count) 42 | for i in range(lab_count): 43 | cols.append(Color(0.0, 0.0, 0.0)) 44 | 45 | for i in range(lab_count): 46 | cols[i] = lab(labs[i].L, labs[i].A, labs[i].B) 47 | 48 | return cols 49 | 50 | 51 | alias CheckColorFn = fn (l: Float64, a: Float64, b: Float64) -> Bool 52 | 53 | 54 | @value 55 | struct SoftPaletteSettings: 56 | # A fntion which can be used to restrict the allowed color-space. 57 | var check_color: Optional[CheckColorFn] 58 | 59 | # The higher, the better quality but the slower. Usually two figures. 60 | var iterations: Int 61 | 62 | # Use up to 160000 or 8000 samples of the L*a*b* space (and thus calls to CheckColor). 63 | # Set this to true only if your CheckColor shapes the Lab space weirdly. 64 | var many_samples: Bool 65 | 66 | 67 | # That's faster than using colorful's DistanceLab since we would have to 68 | # convert back and forth for that. Here is no conversion. 69 | fn lab_dist(lab1: lab_t, lab2: lab_t) -> Float64: 70 | return math.sqrt(sq(lab1.L - lab2.L) + sq(lab1.A - lab2.A) + sq(lab1.B - lab2.B)) 71 | 72 | 73 | # A wrapper which uses common parameters. 74 | fn soft_palette(colors_count: Int) raises -> List[Color]: 75 | return soft_palette_ex(colors_count, SoftPaletteSettings(None, 50, False)) 76 | 77 | 78 | alias LAB_DELTA = 1e-6 79 | 80 | 81 | fn lab_eq(lab1: lab_t, lab2: lab_t) -> Bool: 82 | return ( 83 | math.abs(lab1.L - lab2.L) < LAB_DELTA 84 | and math.abs(lab1.A - lab2.A) < LAB_DELTA 85 | and math.abs(lab1.B - lab2.B) < LAB_DELTA 86 | ) 87 | 88 | 89 | fn soft_palette_ex(colors_count: Int, settings: SoftPaletteSettings) raises -> List[Color]: 90 | """Yeah, windows-stype Foo, FooEx, screw you golang... 91 | Uses K-means to cluster the color-space and return the means of the clusters 92 | as a new palette of distinctive colors. Falls back to K-medoid if the mean 93 | happens to fall outside of the color-space, which can only happen if you 94 | specify a CheckColor fntion.""" 95 | 96 | # Checks whether it's a valid RGB and also fulfills the potentially provided constraint. 97 | @always_inline 98 | fn check(col: lab_t) -> Bool: 99 | var c = lab(col.L, col.A, col.B) 100 | return c.is_valid() and settings.check_color.take()(col.L, col.A, col.B) 101 | 102 | # Sample the color space. These will be the points k-means is run on. 103 | var dl = 0.05 104 | var dab = 0.1 105 | if settings.many_samples: 106 | dl = 0.01 107 | dab = 0.05 108 | 109 | var samples = List[lab_t](capacity=int(1.0 / dl * 2.0 / dab * 2.0 / dab)) 110 | var l = 0.0 111 | while l <= 1.0: 112 | var a = -1.0 113 | while a <= 1.0: 114 | var b = -1.0 115 | while b <= 1.0: 116 | var labt = lab_t(l, a, b) 117 | if check(labt): 118 | samples.append(labt) 119 | b += dab 120 | a += dab 121 | l += dl 122 | 123 | # That would cause some infinite loops down there... 124 | if len(samples) < colors_count: 125 | raise Error( 126 | String("palettegen: more colors requested ") 127 | + colors_count 128 | + " than samples available " 129 | + len(samples) 130 | + " Your requested color count may be wrong, you might want to use many samples or your constraint fntion" 131 | " makes the valid color space too small" 132 | ) 133 | elif len(samples) == colors_count: 134 | return labs_2_cols(samples) # Oops? 135 | 136 | # We take the initial means out of the samples, so they are in fact medoids. 137 | # This helps us avoid infinite loops or arbitrary cutoffs with too restrictive constraints. 138 | var means = List[lab_t](capacity=colors_count) 139 | for i in range(colors_count): 140 | means.append(lab_t(0.0, 0.0, 0.0)) 141 | 142 | var i = 0 143 | while i < colors_count: 144 | i += 1 145 | means[i] = samples[int(random_si64(0, len(samples)))] 146 | while in_stack(means, i, means[i]): 147 | means[i] = samples[int(random_si64(0, len(samples)))] 148 | 149 | var clusters = List[Int](capacity=len(samples)) 150 | for _ in range(len(samples)): 151 | clusters.append(0) 152 | var samples_used = List[Bool](capacity=len(samples)) 153 | for _ in range(len(samples)): 154 | samples_used.append(False) 155 | 156 | # The actual k-means/medoid iterations 157 | i = 0 158 | while i < settings.iterations: 159 | # Reassing the samples to clusters, i.e. to their closest mean. 160 | # By the way, also check if any sample is used as a medoid and if so, mark that. 161 | for j in range(len(samples)): 162 | samples_used[j] = False 163 | var mindist = inf[DType.float64]() 164 | for k in range(len(means)): 165 | var dist = lab_dist(samples[j], means[k]) 166 | if dist < mindist: 167 | mindist = dist 168 | clusters[j] = k 169 | 170 | # Mark samples which are used as a medoid. 171 | if lab_eq(samples[j], means[k]): 172 | samples_used[i] = True 173 | 174 | # Compute new means according to the samples. 175 | for k in range(len(means)): 176 | # The new mean is the average of all samples belonging to it.. 177 | var nsamples = 0 178 | var newmean = lab_t(0.0, 0.0, 0.0) 179 | 180 | for j in range(len(samples)): 181 | if clusters[j] == k: 182 | nsamples += 1 183 | newmean.L += samples[j].L 184 | newmean.A += samples[j].A 185 | newmean.B += samples[j].B 186 | 187 | if nsamples > 0: 188 | newmean.L /= Float64(nsamples) 189 | newmean.A /= Float64(nsamples) 190 | newmean.B /= Float64(nsamples) 191 | else: 192 | # That mean doesn't have any samples? Get a new mean from the sample list! 193 | var inewmean = int(random_si64(0, len(samples_used))) 194 | while samples_used[inewmean]: 195 | inewmean = int(random_si64(0, len(samples_used))) 196 | 197 | newmean = samples[inewmean] 198 | samples_used[inewmean] = True 199 | 200 | # But now we still need to check whether the new mean is an allowed color. 201 | if nsamples > 0 and check(newmean): 202 | # It does, life's good (TM) 203 | means[k] = newmean 204 | else: 205 | # New mean isn't an allowed color or doesn't have any samples! 206 | # Switch to medoid mode and pick the closest (unused) sample. 207 | # This should always find something thanks to len(samples) >= colors_count 208 | var mindist = inf[DType.float64]() 209 | for l in range(len(samples)): 210 | if not samples_used[l]: 211 | var dist = lab_dist(samples[l], newmean) 212 | if dist < mindist: 213 | mindist = dist 214 | newmean = samples[l] 215 | i += 1 216 | 217 | return labs_2_cols(means) 218 | -------------------------------------------------------------------------------- /external/gojo/strings/reader.mojo: -------------------------------------------------------------------------------- 1 | import ..io 2 | from ..builtins import Byte, copy, panic 3 | 4 | 5 | @value 6 | struct Reader(Sized, io.Reader, io.ReaderAt, io.ByteReader, io.ByteScanner, io.Seeker, io.WriterTo): 7 | """A Reader that implements the [io.Reader], [io.ReaderAt], [io.ByteReader], [io.ByteScanner], [io.Seeker], and [io.WriterTo] traits 8 | by reading from a string. The zero value for Reader operates like a Reader of an empty string. 9 | """ 10 | 11 | var string: String 12 | var read_pos: Int64 # current reading index 13 | var prev_rune: Int # index of previous rune; or < 0 14 | 15 | fn __init__(inout self, string: String = ""): 16 | self.string = string 17 | self.read_pos = 0 18 | self.prev_rune = -1 19 | 20 | fn __len__(self) -> Int: 21 | """Returns the number of bytes of the unread portion of the string. 22 | 23 | Returns: 24 | int: the number of bytes of the unread portion of the string. 25 | """ 26 | if self.read_pos >= Int64(len(self.string)): 27 | return 0 28 | 29 | return int(Int64(len(self.string)) - self.read_pos) 30 | 31 | fn size(self) -> Int64: 32 | """Returns the original length of the underlying string. 33 | size is the number of bytes available for reading via [Reader.read_at]. 34 | The returned value is always the same and is not affected by calls 35 | to any other method. 36 | 37 | Returns: 38 | The original length of the underlying string. 39 | """ 40 | return Int64(len(self.string)) 41 | 42 | fn read(inout self, inout dest: List[Byte]) -> (Int, Error): 43 | """Reads from the underlying string into the provided List[Byte] object. 44 | Implements the [io.Reader] trait. 45 | 46 | Args: 47 | dest: The destination List[Byte] object to read into. 48 | 49 | Returns: 50 | The number of bytes read into dest. 51 | """ 52 | if self.read_pos >= Int64(len(self.string)): 53 | return 0, Error(io.EOF) 54 | 55 | self.prev_rune = -1 56 | var bytes_written = copy(dest, self.string[int(self.read_pos) :].as_bytes()) 57 | self.read_pos += Int64(bytes_written) 58 | return bytes_written, Error() 59 | 60 | fn read_at(self, inout dest: List[Byte], off: Int64) -> (Int, Error): 61 | """Reads from the Reader into the dest List[Byte] starting at the offset off. 62 | It returns the number of bytes read into dest and an error if any. 63 | Implements the [io.ReaderAt] trait. 64 | 65 | Args: 66 | dest: The destination List[Byte] object to read into. 67 | off: The byte offset to start reading from. 68 | 69 | Returns: 70 | The number of bytes read into dest. 71 | """ 72 | # cannot modify state - see io.ReaderAt 73 | if off < 0: 74 | return 0, Error("strings.Reader.read_at: negative offset") 75 | 76 | if off >= Int64(len(self.string)): 77 | return 0, Error(io.EOF) 78 | 79 | var error = Error() 80 | var copied_elements_count = copy(dest, self.string[int(off) :].as_bytes()) 81 | if copied_elements_count < len(dest): 82 | error = Error(io.EOF) 83 | 84 | return copied_elements_count, Error() 85 | 86 | fn read_byte(inout self) -> (Byte, Error): 87 | """Reads the next byte from the underlying string. 88 | Implements the [io.ByteReader] trait. 89 | 90 | Returns: 91 | The next byte from the underlying string. 92 | """ 93 | self.prev_rune = -1 94 | if self.read_pos >= Int64(len(self.string)): 95 | return Byte(0), Error(io.EOF) 96 | 97 | var b = self.string[int(self.read_pos)] 98 | self.read_pos += 1 99 | return Byte(ord(b)), Error() 100 | 101 | fn unread_byte(inout self) -> Error: 102 | """Unreads the last byte read. Only the most recent byte read can be unread. 103 | Implements the [io.ByteScanner] trait. 104 | """ 105 | if self.read_pos <= 0: 106 | return Error("strings.Reader.unread_byte: at beginning of string") 107 | 108 | self.prev_rune = -1 109 | self.read_pos -= 1 110 | 111 | return Error() 112 | 113 | # # read_rune implements the [io.RuneReader] trait. 114 | # fn read_rune() (ch rune, size int, err error): 115 | # if self.read_pos >= Int64(len(self.string)): 116 | # self.prev_rune = -1 117 | # return 0, 0, io.EOF 118 | 119 | # self.prev_rune = int(self.read_pos) 120 | # if c = self.string[self.read_pos]; c < utf8.RuneSelf: 121 | # self.read_pos += 1 122 | # return rune(c), 1, nil 123 | 124 | # ch, size = utf8.DecodeRuneInString(self.string[self.read_pos:]) 125 | # self.read_pos += Int64(size) 126 | # return 127 | 128 | # # unread_rune implements the [io.RuneScanner] trait. 129 | # fn unread_rune() error: 130 | # if self.read_pos <= 0: 131 | # return errors.New("strings.Reader.unread_rune: at beginning of string") 132 | 133 | # if self.prev_rune < 0: 134 | # return errors.New("strings.Reader.unread_rune: previous operation was not read_rune") 135 | 136 | # self.read_pos = Int64(self.prev_rune) 137 | # self.prev_rune = -1 138 | # return nil 139 | 140 | fn seek(inout self, offset: Int64, whence: Int) -> (Int64, Error): 141 | """Seeks to a new position in the underlying string. The next read will start from that position. 142 | Implements the [io.Seeker] trait. 143 | 144 | Args: 145 | offset: The offset to seek to. 146 | whence: The seek mode. It can be one of [io.SEEK_START], [io.SEEK_CURRENT], or [io.SEEK_END]. 147 | 148 | Returns: 149 | The new position in the string. 150 | """ 151 | self.prev_rune = -1 152 | var position: Int64 = 0 153 | 154 | if whence == io.SEEK_START: 155 | position = offset 156 | elif whence == io.SEEK_CURRENT: 157 | position = self.read_pos + offset 158 | elif whence == io.SEEK_END: 159 | position = Int64(len(self.string)) + offset 160 | else: 161 | return Int64(0), Error("strings.Reader.seek: invalid whence") 162 | 163 | if position < 0: 164 | return Int64(0), Error("strings.Reader.seek: negative position") 165 | 166 | self.read_pos = position 167 | return position, Error() 168 | 169 | fn write_to[W: io.Writer](inout self, inout writer: W) -> (Int64, Error): 170 | """Writes the remaining portion of the underlying string to the provided writer. 171 | Implements the [io.WriterTo] trait. 172 | 173 | Args: 174 | writer: The writer to write the remaining portion of the string to. 175 | 176 | Returns: 177 | The number of bytes written to the writer. 178 | """ 179 | self.prev_rune = -1 180 | if self.read_pos >= Int64(len(self.string)): 181 | return Int64(0), Error() 182 | 183 | var chunk_to_write = self.string[int(self.read_pos) :] 184 | var bytes_written: Int 185 | var err: Error 186 | bytes_written, err = io.write_string(writer, chunk_to_write) 187 | if bytes_written > len(chunk_to_write): 188 | panic("strings.Reader.write_to: invalid write_string count") 189 | 190 | self.read_pos += Int64(bytes_written) 191 | if bytes_written != len(chunk_to_write) and not err: 192 | err = Error(io.ERR_SHORT_WRITE) 193 | 194 | return Int64(bytes_written), err 195 | 196 | # TODO: How can I differentiate between the two write_to methods when the writer implements both traits? 197 | # fn write_to[W: io.StringWriter](inout self, inout writer: W) raises -> Int64: 198 | # """Writes the remaining portion of the underlying string to the provided writer. 199 | # Implements the [io.WriterTo] trait. 200 | 201 | # Args: 202 | # writer: The writer to write the remaining portion of the string to. 203 | 204 | # Returns: 205 | # The number of bytes written to the writer. 206 | # """ 207 | # self.prev_rune = -1 208 | # if self.read_pos >= Int64(len(self.string)): 209 | # return 0 210 | 211 | # var chunk_to_write = self.string[self.read_pos:] 212 | # var bytes_written = io.write_string(writer, chunk_to_write) 213 | # if bytes_written > len(chunk_to_write): 214 | # raise Error("strings.Reader.write_to: invalid write_string count") 215 | 216 | # self.read_pos += Int64(bytes_written) 217 | # if bytes_written != len(chunk_to_write): 218 | # raise Error(io.ERR_SHORT_WRITE) 219 | 220 | # return Int64(bytes_written) 221 | 222 | fn reset(inout self, string: String): 223 | """Resets the [Reader] to be reading from the beginning of the provided string. 224 | 225 | Args: 226 | string: The string to read from. 227 | """ 228 | self.string = string 229 | self.read_pos = 0 230 | self.prev_rune = -1 231 | 232 | 233 | fn new_reader(string: String = "") -> Reader: 234 | """Returns a new [Reader] reading from the provided string. 235 | It is similar to [bytes.new_buffer] but more efficient and non-writable. 236 | 237 | Args: 238 | string: The string to read from. 239 | """ 240 | return Reader(string) 241 | -------------------------------------------------------------------------------- /external/hue/hsluv.mojo: -------------------------------------------------------------------------------- 1 | from .math import cube, clamp01, sq, pi, max_float64 2 | from .color import Color, linear_rgb, xyz_to_linear_rgb, luv_to_xyz_white_ref 3 | import math 4 | 5 | alias hSLuvD65 = List[Float64](0.95045592705167, 1.0, 1.089057750759878) 6 | 7 | 8 | fn LuvLCh_to_HSLuv(l: Float64, c: Float64, h: Float64) -> (Float64, Float64, Float64): 9 | """[-1..1] but the code expects it to be [-100..100].""" 10 | var c_new = c * 100.0 11 | var l_new = l * 100.0 12 | 13 | var s: Float64 14 | var max_val: Float64 15 | if l_new > 99.9999999 or l_new < 0.00000001: 16 | s = 0.0 17 | else: 18 | max_val = max_chroma_for_lh(l_new, h) 19 | s = c_new / max_val * 100.0 20 | 21 | return h, clamp01(s / 100.0), clamp01(l_new / 100.0) 22 | 23 | 24 | fn HSLuvToLuvLCh(h: Float64, s: Float64, l: Float64) -> (Float64, Float64, Float64): 25 | var tmp_l = l * 100.0 26 | var tmp_s = s * 100.0 27 | 28 | var c: Float64 29 | var max: Float64 30 | if l > 99.9999999 or l < 0.00000001: 31 | c = 0.0 32 | else: 33 | max = max_chroma_for_lh(l, h) 34 | c = max / 100.0 * s 35 | 36 | # c is [-100..100], but for LCh it's supposed to be almost [-1..1] 37 | return clamp01(l / 100.0), c / 100.0, h 38 | 39 | 40 | fn LuvLCh_to_Luv(l: Float64, c: Float64, h: Float64) -> (Float64, Float64, Float64): 41 | var H: Float64 = 0.01745329251994329576 * h # Deg2Rad 42 | var u = c * math.cos(H) 43 | var v = c * math.sin(H) 44 | return l, u, v 45 | 46 | 47 | fn LuvLCh_to_HPLuv(l: Float64, c: Float64, h: Float64) -> (Float64, Float64, Float64): 48 | """[-1..1] but the code expects it to be [-100..100].""" 49 | var c_new = c * 100.0 50 | var l_new = l * 100.0 51 | 52 | var s: Float64 53 | var max_val: Float64 54 | if l_new > 99.9999999 or l_new < 0.00000001: 55 | s = 0.0 56 | else: 57 | max_val = max_safe_chroma_for_l(l_new) 58 | s = c_new / max_val * 100.0 59 | 60 | return h, s / 100.0, l_new / 100.0 61 | 62 | 63 | fn HPLuv_to_LuvLCh(h: Float64, s: Float64, l: Float64) -> (Float64, Float64, Float64): 64 | var l_new = l * 100.0 65 | var s_new = s * 100.0 66 | 67 | var c: Float64 68 | var max_val: Float64 69 | if l_new > 99.9999999 or l_new < 0.00000001: 70 | c = 0.0 71 | else: 72 | max_val = max_safe_chroma_for_l(l_new) 73 | c = max_val / 100.0 * s_new 74 | 75 | return l_new / 100.0, c / 100.0, h 76 | 77 | 78 | fn HSLuv(h: Float64, s: Float64, l: Float64) -> Color: 79 | """Creates a new Color from values in the HSLuv color space. 80 | Hue in [0..360], a Saturation [0..1], and a Luminance (lightness) in [0..1]. 81 | 82 | The returned color values are clamped (using .Clamped), so this will never output 83 | an invalid color.""" 84 | # HSLuv -> LuvLCh -> CIELUV -> CIEXYZ -> Linear RGB -> sRGB 85 | var l_new: Float64 86 | var c: Float64 87 | var h_new: Float64 88 | l_new, c, h_new = HSLuvToLuvLCh(h, s, l) 89 | 90 | var L: Float64 91 | var u: Float64 92 | var v: Float64 93 | L, u, v = LuvLCh_to_Luv(l_new, c, h_new) 94 | 95 | var x: Float64 96 | var y: Float64 97 | var z: Float64 98 | x, y, z = luv_to_xyz_white_ref(l, u, v, hSLuvD65) 99 | 100 | var R: Float64 101 | var G: Float64 102 | var B: Float64 103 | R, G, B = xyz_to_linear_rgb(x, y, z) 104 | return linear_rgb(R, G, B).clamped() 105 | 106 | 107 | fn LuvLch_to_HSLuv(l: Float64, c: Float64, h: Float64) -> (Float64, Float64, Float64): 108 | # [-1..1] but the code expects it to be [-100..100] 109 | var tmp_l: Float64 = l * 100.0 110 | var tmp_c: Float64 = c * 100.0 111 | 112 | var s: Float64 113 | var max_val: Float64 114 | if tmp_l > 99.9999999 or tmp_l < 0.00000001: 115 | s = 0.0 116 | else: 117 | max_val = max_chroma_for_lh(tmp_l, h) 118 | s = tmp_c / max_val * 100.0 119 | 120 | return h, clamp01(s / 100.0), clamp01(tmp_l / 100.0) 121 | 122 | 123 | fn HPLuv(h: Float64, s: Float64, l: Float64) -> Color: 124 | """HPLuv creates a new Color from values in the HPLuv color space. 125 | Hue in [0..360], a Saturation [0..1], and a Luminance (lightness) in [0..1]. 126 | 127 | The returned color values are clamped (using .Clamped), so this will never output 128 | an invalid color.""" 129 | # HPLuv -> LuvLCh -> CIELUV -> CIEXYZ -> Linear RGB -> sRGB 130 | var l_new: Float64 131 | var c: Float64 132 | var h_new: Float64 133 | l_new, c, h_new = HPLuv_to_LuvLCh(h, s, l) 134 | 135 | var L: Float64 136 | var u: Float64 137 | var v: Float64 138 | L, u, v = LuvLCh_to_Luv(l_new, c, h_new) 139 | 140 | var x: Float64 141 | var y: Float64 142 | var z: Float64 143 | x, y, z = luv_to_xyz_white_ref(l, u, v, hSLuvD65) 144 | 145 | var R: Float64 146 | var G: Float64 147 | var B: Float64 148 | R, G, B = xyz_to_linear_rgb(x, y, z) 149 | return linear_rgb(R, G, B).clamped() 150 | 151 | 152 | fn HSLuv(self: Color) -> (Float64, Float64, Float64): 153 | """HSLuv returns the Hue, Saturation and Luminance of the color in the HSLuv 154 | color space. Hue in [0..360], a Saturation [0..1], and a Luminance 155 | (lightness) in [0..1].""" 156 | # sRGB -> Linear RGB -> CIEXYZ -> CIELUV -> LuvLCh -> HSLuv 157 | var l: Float64 158 | var c: Float64 159 | var h: Float64 160 | l, c, h = self.LuvLCh_white_ref(hSLuvD65) 161 | 162 | return LuvLCh_to_HSLuv(l, c, h) 163 | 164 | 165 | fn HPLuv(self: Color) -> (Float64, Float64, Float64): 166 | """HPLuv returns the Hue, Saturation and Luminance of the color in the HSLuv 167 | color space. Hue in [0..360], a Saturation [0..1], and a Luminance 168 | (lightness) in [0..1]. 169 | 170 | Note that HPLuv can only represent pastel colors, and so the Saturation 171 | value could be much larger than 1 for colors it can't represent.""" 172 | # sRGB -> Linear RGB -> CIEXYZ -> CIELUV -> LuvLCh -> HSLuv 173 | var l: Float64 174 | var c: Float64 175 | var h: Float64 176 | l, c, h = self.LuvLCh_white_ref(hSLuvD65) 177 | 178 | return LuvLCh_to_HPLuv(l, c, h) 179 | 180 | 181 | fn DistanceHPLuv(self: Color, other: Color) -> Float64: 182 | """DistanceHPLuv calculates Euclidean distance in the HPLuv colorspace. No idea 183 | how useful this is. 184 | 185 | The Hue value is divided by 100 before the calculation, so that H, S, and L 186 | have the same relative ranges.""" 187 | var h1: Float64 188 | var s1: Float64 189 | var l1: Float64 190 | h1, s1, l1 = self.HPLuv() 191 | 192 | var h2: Float64 193 | var s2: Float64 194 | var l2: Float64 195 | h2, s2, l2 = other.HPLuv() 196 | 197 | return math.sqrt(sq((h1 - h2) / 100.0) + sq(s1 - s2) + sq(l1 - l2)) 198 | 199 | 200 | alias m = List[List[Float64]]( 201 | List[Float64](3.2409699419045214, -1.5373831775700935, -0.49861076029300328), 202 | List[Float64](-0.96924363628087983, 1.8759675015077207, 0.041555057407175613), 203 | List[Float64](0.055630079696993609, -0.20397695888897657, 1.0569715142428786), 204 | ) 205 | 206 | alias kappa = 903.2962962962963 207 | alias epsilon = 0.0088564516790356308 208 | 209 | 210 | fn get_bounds(l: Float64) -> List[List[Float64]]: 211 | var sub2: Float64 212 | var sub1 = (l + 16.0**3.0) / 1560896.0 213 | 214 | var ret = List[List[Float64]]( 215 | List[Float64](0, 0), 216 | List[Float64](0, 0), 217 | List[Float64](0, 0), 218 | List[Float64](0, 0), 219 | List[Float64](0, 0), 220 | List[Float64](0, 0), 221 | ) 222 | 223 | if sub1 > epsilon: 224 | sub2 = sub1 225 | else: 226 | sub2 = l / kappa 227 | 228 | for i in range(len(m)): 229 | var k = 0 230 | while k < 2: 231 | var top1 = (284517.0 * m[i][0] - 94839.0 * m[i][2]) * sub2 232 | var top2 = (838422.0 * m[i][2] + 769860.0 * m[i][1] + 731718.0 * m[i][0]) * l * sub2 - 769860.0 * Float64( 233 | k 234 | ) * l 235 | var bottom = (632260.0 * m[i][2] - 126452.0 * m[i][1]) * sub2 + 126452.0 * Float64(k) 236 | ret[i * 2 + k][0] = top1 / bottom 237 | ret[i * 2 + k][1] = top2 / bottom 238 | k += 1 239 | 240 | return ret 241 | 242 | 243 | fn length_of_ray_until_intersect(theta: Float64, x: Float64, y: Float64) -> Float64: 244 | return y / (math.sin(theta) - x * math.cos(theta)) 245 | 246 | 247 | fn max_chroma_for_lh(l: Float64, h: Float64) -> Float64: 248 | var hRad = h / 360.0 * pi * 2.0 249 | var minLength = max_float64 250 | var bounds = get_bounds(l) 251 | 252 | for i in range(len(bounds)): 253 | var line = bounds[i] 254 | var length = length_of_ray_until_intersect(hRad, line[0], line[1]) 255 | if length > 0.0 and length < minLength: 256 | minLength = length 257 | 258 | return minLength 259 | 260 | 261 | fn max_safe_chroma_for_l(l: Float64) -> Float64: 262 | var min_length = max_float64 263 | for line in get_bounds(l): 264 | var m1 = line[][0] 265 | var b1 = line[][1] 266 | var x = intersect_line_line(m1, b1, -1.0 / m1, 0.0) 267 | var dist = distance_from_pole(x, b1 + x * m1) 268 | if dist < min_length: 269 | min_length = dist 270 | return min_length 271 | 272 | 273 | fn intersect_line_line(x1: Float64, y1: Float64, x2: Float64, y2: Float64) -> Float64: 274 | return (y1 - y2) / (x2 - x1) 275 | 276 | 277 | fn distance_from_pole(x: Float64, y: Float64) -> Float64: 278 | return math.sqrt(sq(x) + sq(y)) 279 | -------------------------------------------------------------------------------- /external/mist/color.mojo: -------------------------------------------------------------------------------- 1 | from collections.dict import Dict, KeyElement 2 | from utils.variant import Variant 3 | import external.hue 4 | from external.hue.math import max_float64 5 | from .ansi_colors import ANSI_HEX_CODES 6 | 7 | 8 | @value 9 | struct StringKey(KeyElement): 10 | var s: String 11 | 12 | fn __init__(inout self, owned s: String): 13 | self.s = s^ 14 | 15 | fn __init__(inout self, s: StringLiteral): 16 | self.s = String(s) 17 | 18 | fn __hash__(self) -> Int: 19 | return hash(self.s) 20 | 21 | fn __eq__(self, other: Self) -> Bool: 22 | return self.s == other.s 23 | 24 | fn __ne__(self, other: Self) -> Bool: 25 | return self.s != other.s 26 | 27 | fn __str__(self) -> String: 28 | return self.s 29 | 30 | 31 | alias foreground = "38" 32 | alias background = "48" 33 | alias AnyColor = Variant[NoColor, ANSIColor, ANSI256Color, RGBColor] 34 | 35 | 36 | trait Equalable: 37 | fn __eq__(self: Self, other: Self) -> Bool: 38 | ... 39 | 40 | 41 | trait NotEqualable: 42 | fn __ne__(self: Self, other: Self) -> Bool: 43 | ... 44 | 45 | 46 | trait Color(Movable, Copyable, Equalable, NotEqualable, CollectionElement): 47 | fn sequence(self, is_background: Bool) -> String: 48 | """Sequence returns the ANSI Sequence for the color.""" 49 | ... 50 | 51 | 52 | @value 53 | struct NoColor(Color, Stringable): 54 | fn __eq__(self, other: NoColor) -> Bool: 55 | return True 56 | 57 | fn __ne__(self, other: NoColor) -> Bool: 58 | return False 59 | 60 | fn sequence(self, is_background: Bool) -> String: 61 | return "" 62 | 63 | fn __str__(self) -> String: 64 | """String returns the ANSI Sequence for the color and the text.""" 65 | return "" 66 | 67 | 68 | @value 69 | struct ANSIColor(Color, Stringable): 70 | """ANSIColor is a color (0-15) as defined by the ANSI Standard.""" 71 | 72 | var value: Int 73 | 74 | fn __eq__(self, other: ANSIColor) -> Bool: 75 | return self.value == other.value 76 | 77 | fn __ne__(self, other: ANSIColor) -> Bool: 78 | return self.value != other.value 79 | 80 | fn sequence(self, is_background: Bool) -> String: 81 | """Returns the ANSI Sequence for the color and the text. 82 | 83 | Args: 84 | is_background: Whether the color is a background color. 85 | """ 86 | var modifier: Int = 0 87 | if is_background: 88 | modifier += 10 89 | 90 | if self.value < 8: 91 | return String(modifier + self.value + 30) 92 | else: 93 | return String(modifier + self.value - 8 + 90) 94 | 95 | fn __str__(self) -> String: 96 | """String returns the ANSI Sequence for the color and the text.""" 97 | return ANSI_HEX_CODES[self.value] 98 | 99 | fn convert_to_rgb(self) -> hue.Color: 100 | """Converts an ANSI color to hue.Color by looking up the hex value and converting it.""" 101 | var hex: String = ANSI_HEX_CODES[self.value] 102 | 103 | return hex_to_rgb(hex) 104 | 105 | 106 | @value 107 | struct ANSI256Color(Color, Stringable): 108 | """ANSI256Color is a color (16-255) as defined by the ANSI Standard.""" 109 | 110 | var value: Int 111 | 112 | fn __eq__(self, other: ANSI256Color) -> Bool: 113 | return self.value == other.value 114 | 115 | fn __ne__(self, other: ANSI256Color) -> Bool: 116 | return self.value != other.value 117 | 118 | fn sequence(self, is_background: Bool) -> String: 119 | """Returns the ANSI Sequence for the color and the text. 120 | 121 | Args: 122 | is_background: Whether the color is a background color. 123 | """ 124 | var prefix: String = foreground 125 | if is_background: 126 | prefix = background 127 | 128 | return prefix + ";5;" + String(self.value) 129 | 130 | fn __str__(self) -> String: 131 | """String returns the ANSI Sequence for the color and the text.""" 132 | return ANSI_HEX_CODES[self.value] 133 | 134 | fn convert_to_rgb(self) -> hue.Color: 135 | """Converts an ANSI color to hue.Color by looking up the hex value and converting it.""" 136 | var hex: String = ANSI_HEX_CODES[self.value] 137 | 138 | return hex_to_rgb(hex) 139 | 140 | 141 | # fn convert_base10_to_base16(value: Int) -> String: 142 | # """Converts a base 10 number to base 16.""" 143 | # var sum: Int = value 144 | # while value > 1: 145 | # var remainder = sum % 16 146 | # sum = sum / 16 147 | # print(remainder, sum) 148 | 149 | # print(remainder * 16) 150 | 151 | 152 | fn convert_base16_to_base10(value: String) -> Int: 153 | """Converts a base 16 number to base 10. 154 | https://www.catalyst2.com/knowledgebase/dictionary/hexadecimal-base-16-numbers/#:~:text=To%20convert%20the%20hex%20number,16%20%2B%200%20%3D%2016). 155 | 156 | Args: 157 | value: Hexadecimal number. 158 | 159 | Returns: 160 | Base 10 number. 161 | """ 162 | var mapping = Dict[StringKey, Int]() 163 | mapping["0"] = 0 164 | mapping["1"] = 1 165 | mapping["2"] = 2 166 | mapping["3"] = 3 167 | mapping["4"] = 4 168 | mapping["5"] = 5 169 | mapping["6"] = 6 170 | mapping["7"] = 7 171 | mapping["8"] = 8 172 | mapping["9"] = 9 173 | mapping["a"] = 10 174 | mapping["b"] = 11 175 | mapping["c"] = 12 176 | mapping["d"] = 13 177 | mapping["e"] = 14 178 | mapping["f"] = 15 179 | 180 | # We assume mapping.find always returns a value considering the value passed in is a valid hex value 181 | # and the mapping has all the values. 182 | var length = len(value) 183 | var total: Int = 0 184 | for i in range(length - 1, -1, -1): 185 | var exponent = length - 1 - i 186 | total += mapping.find(value[i]).value()[] * (16**exponent) 187 | 188 | return total 189 | 190 | 191 | fn hex_to_rgb(value: String) -> hue.Color: 192 | """Converts a hex color to hue.Color. 193 | 194 | Args: 195 | value: Hex color value. 196 | 197 | Returns: 198 | Color. 199 | """ 200 | var hex = value[1:] 201 | var indices = List[Int](0, 2, 4) 202 | var results = List[Int]() 203 | for i in indices: 204 | results.append(convert_base16_to_base10(hex[i[] : i[] + 2])) 205 | 206 | return hue.Color(results[0], results[1], results[2]) 207 | 208 | 209 | @value 210 | struct RGBColor(Color): 211 | """RGBColor is a hex-encoded color, e.g. '#abcdef'.""" 212 | 213 | var value: String 214 | 215 | fn __init__(inout self, value: String): 216 | self.value = value.lower() 217 | 218 | fn __eq__(self, other: RGBColor) -> Bool: 219 | return self.value == other.value 220 | 221 | fn __ne__(self, other: RGBColor) -> Bool: 222 | return self.value != other.value 223 | 224 | fn sequence(self, is_background: Bool) -> String: 225 | """Returns the ANSI Sequence for the color and the text. 226 | 227 | Args: 228 | is_background: Whether the color is a background color. 229 | """ 230 | var rgb = hex_to_rgb(self.value) 231 | 232 | var prefix = foreground 233 | if is_background: 234 | prefix = background 235 | 236 | return prefix + String(";2;") + String(int(rgb.R)) + ";" + String(int(rgb.G)) + ";" + String(int(rgb.B)) 237 | 238 | fn convert_to_rgb(self) -> hue.Color: 239 | """Converts the Hex code value to hue.Color.""" 240 | return hex_to_rgb(self.value) 241 | 242 | 243 | fn ansi256_to_ansi(value: Int) -> ANSIColor: 244 | """Converts an ANSI256 color to an ANSI color. 245 | 246 | Args: 247 | value: ANSI256 color value. 248 | """ 249 | var r: Int = 0 250 | var md = max_float64 251 | 252 | var h = hex_to_rgb(ANSI_HEX_CODES[value]) 253 | 254 | var i: Int = 0 255 | while i <= 15: 256 | var hb = hex_to_rgb(ANSI_HEX_CODES[i]) 257 | var d = h.distance_HSLuv(hb) 258 | 259 | if d < md: 260 | md = d 261 | r = i 262 | 263 | i += 1 264 | 265 | return ANSIColor(r) 266 | 267 | 268 | fn v2ci(value: Float64) -> Int: 269 | if value < 48: 270 | return 0 271 | elif value < 115: 272 | return 1 273 | else: 274 | return int((value - 35) / 40) 275 | 276 | 277 | fn hex_to_ansi256(color: hue.Color) -> ANSI256Color: 278 | """Converts a hex code to a ANSI256 color. 279 | 280 | Args: 281 | color: Hex code color from hue.Color. 282 | """ 283 | # Calculate the nearest 0-based color index at 16..231 284 | # Originally had * 255 in each of these 285 | var r: Float64 = v2ci(color.R) # 0..5 each 286 | var g: Float64 = v2ci(color.G) 287 | var b: Float64 = v2ci(color.B) 288 | var ci: Int = int((36 * r) + (6 * g) + b) # 0..215 289 | 290 | # Calculate the represented colors back from the index 291 | var i2cv = List[Int](0, 0x5F, 0x87, 0xAF, 0xD7, 0xFF) 292 | var cr = i2cv[int(r)] # r/g/b, 0..255 each 293 | var cg = i2cv[int(g)] 294 | var cb = i2cv[int(b)] 295 | 296 | # Calculate the nearest 0-based gray index at 232..255 297 | var grayIdx: Int 298 | var average = (r + g + b) / 3 299 | if average > 238: 300 | grayIdx = 23 301 | else: 302 | grayIdx = int((average - 3) / 10) # 0..23 303 | var gv = 8 + 10 * grayIdx # same value for r/g/b, 0..255 304 | 305 | # Return the one which is nearer to the original input rgb value 306 | # Originall had / 255.0 for r, g, and b in each of these 307 | var c2 = hue.Color(cr, cg, cb) 308 | var g2 = hue.Color(gv, gv, gv) 309 | var color_dist = color.distance_HSLuv(c2) 310 | var gray_dist = color.distance_HSLuv(g2) 311 | 312 | if color_dist <= gray_dist: 313 | return ANSI256Color(16 + ci) 314 | return ANSI256Color(232 + grayIdx) 315 | -------------------------------------------------------------------------------- /external/mist/style.mojo: -------------------------------------------------------------------------------- 1 | from .color import ( 2 | Color, 3 | NoColor, 4 | ANSIColor, 5 | ANSI256Color, 6 | RGBColor, 7 | AnyColor, 8 | hex_to_rgb, 9 | hex_to_ansi256, 10 | ansi256_to_ansi, 11 | ) 12 | from .profile import get_color_profile, ASCII 13 | import time 14 | 15 | # Text formatting sequences 16 | alias reset = "0" 17 | alias bold = "1" 18 | alias faint = "2" 19 | alias italic = "3" 20 | alias underline = "4" 21 | alias blink = "5" 22 | alias reverse = "7" 23 | alias crossout = "9" 24 | alias overline = "53" 25 | 26 | # ANSI Operations 27 | alias escape = chr(27) # Escape character 28 | alias bel = "\a" # Bell 29 | alias csi = escape + "[" # Control Sequence Introducer 30 | alias osc = escape + "]" # Operating System Command 31 | alias st = escape + chr(92) # String Terminator - Might not work, haven't tried. 92 should be a raw backslash 32 | 33 | # clear terminal and return cursor to top left 34 | alias clear = escape + "[2J" + escape + "[H" 35 | 36 | 37 | @value 38 | struct TerminalStyle: 39 | """TerminalStyle stores a list of styles to format text with. These styles are ANSI sequences which modify text (and control the terminal). 40 | In reality, these styles are turning visual terminal features on and off around the text it's styling. 41 | 42 | This struct should be considered immutable and each style added returns a new instance of itself rather than modifying the struct in place. 43 | It's recommended to use the `new` static method to create a new instance of TerminalStyle so that you can chain style methods together. 44 | Example: 45 | ``` 46 | from mist import TerminalStyle 47 | 48 | var style = TerminalStyle.new().foreground("#E88388").render("red") 49 | print(style.render("Hello World")) 50 | ``` 51 | """ 52 | 53 | var styles: List[String] 54 | var profile: Profile 55 | 56 | fn __init__(inout self, profile: Profile, *, styles: List[String] = List[String]()): 57 | """Constructs a TerminalStyle. Use new instead of __init__ to chain function calls. 58 | 59 | Args: 60 | profile: The color profile to use for color conversion. 61 | styles: A list of ANSI styles to apply to the text. 62 | """ 63 | self.styles = styles 64 | self.profile = profile 65 | 66 | fn __init__(inout self, *, styles: List[String] = List[String]()): 67 | """Constructs a TerminalStyle. Use new instead of __init__ to chain function calls. 68 | 69 | Args: 70 | styles: A list of ANSI styles to apply to the text. 71 | """ 72 | self.styles = styles 73 | self.profile = Profile() 74 | 75 | @staticmethod 76 | fn new(profile: Profile, *, styles: List[String] = List[String]()) -> Self: 77 | """Constructs a TerminalStyle. Use new instead of __init__ to chain function calls. 78 | 79 | Args: 80 | profile: The color profile to use for color conversion. 81 | styles: A list of ANSI styles to apply to the text. 82 | """ 83 | return Self(profile, styles=styles) 84 | 85 | @staticmethod 86 | fn new(styles: List[String] = List[String]()) -> Self: 87 | """Constructs a TerminalStyle. Use new instead of __init__ to chain function calls. 88 | 89 | Args: 90 | styles: A list of ANSI styles to apply to the text. 91 | """ 92 | return Self(styles=styles) 93 | 94 | fn copy(self) -> Self: 95 | """Creates a deepcopy of Self and returns that. Immutability instead of mutating the object.""" 96 | return Self(self.profile, styles=self.get_styles()) 97 | 98 | fn _add_style(self, style: String) -> Self: 99 | """Creates a deepcopy of Self, adds a style to it's list of styles, and returns that. Immutability instead of mutating the object. 100 | 101 | Args: 102 | style: The ANSI style to add to the list of styles. 103 | """ 104 | var new_styles = self.get_styles() 105 | new_styles.append(style) 106 | return Self(self.profile, styles=new_styles) 107 | 108 | fn get_styles(self) -> List[String]: 109 | """Return a deepcopy of the styles list.""" 110 | return List[String](self.styles) 111 | 112 | fn bold(self) -> Self: 113 | """Makes the text bold when rendered.""" 114 | return self._add_style(bold) 115 | 116 | fn faint(self) -> Self: 117 | """Makes the text faint when rendered.""" 118 | return self._add_style(faint) 119 | 120 | fn italic(self) -> Self: 121 | """Makes the text italic when rendered.""" 122 | return self._add_style(italic) 123 | 124 | fn underline(self) -> Self: 125 | """Makes the text underlined when rendered.""" 126 | return self._add_style(underline) 127 | 128 | fn blink(self) -> Self: 129 | """Makes the text blink when rendered.""" 130 | return self._add_style(blink) 131 | 132 | fn reverse(self) -> Self: 133 | """Makes the text have reversed background and foreground colors when rendered.""" 134 | return self._add_style(reverse) 135 | 136 | fn crossout(self) -> Self: 137 | """Makes the text crossed out when rendered.""" 138 | return self._add_style(crossout) 139 | 140 | fn overline(self) -> Self: 141 | """Makes the text overlined when rendered.""" 142 | return self._add_style(overline) 143 | 144 | fn background(self, color: AnyColor) -> Self: 145 | """Set the background color of the text when it's rendered. 146 | 147 | Args: 148 | color: The color value to set the background to. This can be a hex value, an ANSI color, or an RGB color. 149 | 150 | Returns: 151 | A new TerminalStyle with the background color set. 152 | """ 153 | if color.isa[NoColor](): 154 | return Self(self.profile, styles=self.styles) 155 | 156 | var sequence: String = "" 157 | if color.isa[ANSIColor](): 158 | var c = color.get[ANSIColor]()[] 159 | sequence = c.sequence(True) 160 | elif color.isa[ANSI256Color](): 161 | var c = color.get[ANSI256Color]()[] 162 | sequence = c.sequence(True) 163 | elif color.isa[RGBColor](): 164 | var c = color.get[RGBColor]()[] 165 | sequence = c.sequence(True) 166 | return self._add_style(sequence) 167 | 168 | fn background(self, color_value: String) -> Self: 169 | """Shorthand for using the style profile to set the background color of the text. 170 | 171 | Args: 172 | color_value: The color value to set the background to. This can be a hex value, an ANSI color, or an RGB color. 173 | 174 | Returns: 175 | A new TerminalStyle with the background color set. 176 | """ 177 | return self.background(self.profile.color(color_value)) 178 | 179 | fn background(self, color_value: StringLiteral) -> Self: 180 | """Shorthand for using the style profile to set the background color of the text. 181 | 182 | Args: 183 | color_value: The color value to set the background to. This can be a hex value, an ANSI color, or an RGB color. 184 | 185 | Returns: 186 | A new TerminalStyle with the background color set. 187 | """ 188 | return self.background(self.profile.color(color_value)) 189 | 190 | fn foreground(self, color: AnyColor) -> Self: 191 | """Set the foreground color of the text. 192 | 193 | Args: 194 | color: The color value to set the foreground to. This can be a hex value, an ANSI color, or an RGB color. 195 | 196 | Returns: 197 | A new TerminalStyle with the foreground color set. 198 | """ 199 | if color.isa[NoColor](): 200 | return Self(self.profile, styles=self.styles) 201 | 202 | var sequence: String = "" 203 | if color.isa[ANSIColor](): 204 | var c = color.get[ANSIColor]()[] 205 | sequence = c.sequence(False) 206 | elif color.isa[ANSI256Color](): 207 | var c = color.get[ANSI256Color]()[] 208 | sequence = c.sequence(False) 209 | elif color.isa[RGBColor](): 210 | var c = color.get[RGBColor]()[] 211 | sequence = c.sequence(False) 212 | return self._add_style(sequence) 213 | 214 | fn foreground(self, color_value: String) -> Self: 215 | """Shorthand for using the style profile to set the foreground color of the text. 216 | 217 | Args: 218 | color_value: The color value to set the foreground to. This can be a hex value, an ANSI color, or an RGB color. 219 | 220 | Returns: 221 | A new TerminalStyle with the foreground color set. 222 | """ 223 | return self.foreground(self.profile.color(color_value)) 224 | 225 | fn foreground(self, color_value: StringLiteral) -> Self: 226 | """Shorthand for using the style profile to set the foreground color of the text. 227 | 228 | Args: 229 | color_value: The color value to set the foreground to. This can be a hex value, an ANSI color, or an RGB color. 230 | 231 | Returns: 232 | A new TerminalStyle with the foreground color set. 233 | """ 234 | return self.foreground(self.profile.color(color_value)) 235 | 236 | fn render(self, text: String) -> String: 237 | """Renders text with the styles applied to it. 238 | 239 | Args: 240 | text: The text to render with the styles applied. 241 | 242 | Returns: 243 | The text with the styles applied. 244 | """ 245 | var start = time.now() 246 | if self.profile.value == ASCII: 247 | return text 248 | 249 | if len(self.styles) == 0: 250 | return text 251 | 252 | var seq: String = "" 253 | for i in range(len(self.styles)): 254 | seq = seq + ";" + self.styles[i] 255 | return csi + seq + "m" + text + csi + reset + "m" 256 | -------------------------------------------------------------------------------- /external/gojo/io/traits.mojo: -------------------------------------------------------------------------------- 1 | from collections.optional import Optional 2 | from ..builtins import Byte, Error 3 | 4 | alias Rune = Int32 5 | 6 | # Package io provides basic interfaces to I/O primitives. 7 | # Its primary job is to wrap existing implementations of such primitives, 8 | # such as those in package os, into shared public interfaces that 9 | # abstract the fntionality, plus some other related primitives. 10 | # 11 | # Because these interfaces and primitives wrap lower-level operations with 12 | # various implementations, unless otherwise informed clients should not 13 | # assume they are safe for parallel execution. 14 | # Seek whence values. 15 | alias SEEK_START = 0 # seek relative to the origin of the file 16 | alias SEEK_CURRENT = 1 # seek relative to the current offset 17 | alias SEEK_END = 2 # seek relative to the end 18 | 19 | # ERR_SHORT_WRITE means that a write accepted fewer bytes than requested 20 | # but failed to return an explicit error. 21 | alias ERR_SHORT_WRITE = "short write" 22 | 23 | # ERR_INVALID_WRITE means that a write returned an impossible count. 24 | alias ERR_INVALID_WRITE = "invalid write result" 25 | 26 | # ERR_SHORT_BUFFER means that a read required a longer buffer than was provided. 27 | alias ERR_SHORT_BUFFER = "short buffer" 28 | 29 | # EOF is the error returned by Read when no more input is available. 30 | # (Read must return EOF itself, not an error wrapping EOF, 31 | # because callers will test for EOF using ==.) 32 | # fntions should return EOF only to signal a graceful end of input. 33 | # If the EOF occurs unexpectedly in a structured data stream, 34 | # the appropriate error is either [ERR_UNEXPECTED_EOF] or some other error 35 | # giving more detail. 36 | alias EOF = "EOF" 37 | 38 | # ERR_UNEXPECTED_EOF means that EOF was encountered in the 39 | # middle of reading a fixed-size block or data structure. 40 | alias ERR_UNEXPECTED_EOF = "unexpected EOF" 41 | 42 | # ERR_NO_PROGRESS is returned by some clients of a [Reader] when 43 | # many calls to Read have failed to return any data or error, 44 | # usually the sign of a broken [Reader] implementation. 45 | alias ERR_NO_PROGRESS = "multiple Read calls return no data or error" 46 | 47 | 48 | trait Reader(Movable): 49 | """Reader is the trait that wraps the basic Read method. 50 | 51 | Read reads up to len(p) bytes into p. It returns the number of bytes 52 | read (0 <= n <= len(p)) and any error encountered. Even if Read 53 | returns n < len(p), it may use all of p as scratch space during the call. 54 | If some data is available but not len(p) bytes, Read conventionally 55 | returns what is available instead of waiting for more. 56 | 57 | When Read encounters an error or end-of-file condition after 58 | successfully reading n > 0 bytes, it returns the number of 59 | bytes read. It may return the (non-nil) error from the same call 60 | or return the error (and n == 0) from a subsequent call. 61 | An instance of this general case is that a Reader returning 62 | a non-zero number of bytes at the end of the input stream may 63 | return either err == EOF or err == nil. The next Read should 64 | return 0, EOF. 65 | 66 | Callers should always process the n > 0 bytes returned before 67 | considering the error err. Doing so correctly handles I/O errors 68 | that happen after reading some bytes and also both of the 69 | allowed EOF behaviors. 70 | 71 | If len(p) == 0, Read should always return n == 0. It may return a 72 | non-nil error if some error condition is known, such as EOF. 73 | 74 | Implementations of Read are discouraged from returning a 75 | zero byte count with a nil error, except when len(p) == 0. 76 | Callers should treat a return of 0 and nil as indicating that 77 | nothing happened; in particular it does not indicate EOF. 78 | 79 | Implementations must not retain p.""" 80 | 81 | fn read(inout self, inout dest: List[Byte]) -> (Int, Error): 82 | ... 83 | 84 | 85 | trait Writer(Movable): 86 | """Writer is the trait that wraps the basic Write method. 87 | 88 | Write writes len(p) bytes from p to the underlying data stream. 89 | It returns the number of bytes written from p (0 <= n <= len(p)) 90 | and any error encountered that caused the write to stop early. 91 | Write must return a non-nil error if it returns n < len(p). 92 | Write must not modify the slice data, even temporarily. 93 | 94 | Implementations must not retain p. 95 | """ 96 | 97 | fn write(inout self, src: List[Byte]) -> (Int, Error): 98 | ... 99 | 100 | 101 | trait Closer(Movable): 102 | """ 103 | Closer is the trait that wraps the basic Close method. 104 | 105 | The behavior of Close after the first call is undefined. 106 | Specific implementations may document their own behavior. 107 | """ 108 | 109 | fn close(inout self) -> Error: 110 | ... 111 | 112 | 113 | trait Seeker(Movable): 114 | """ 115 | Seeker is the trait that wraps the basic Seek method. 116 | 117 | Seek sets the offset for the next Read or Write to offset, 118 | interpreted according to whence: 119 | [SEEK_START] means relative to the start of the file, 120 | [SEEK_CURRENT] means relative to the current offset, and 121 | [SEEK_END] means relative to the end 122 | (for example, offset = -2 specifies the penultimate byte of the file). 123 | Seek returns the new offset relative to the start of the 124 | file or an error, if any. 125 | 126 | Seeking to an offset before the start of the file is an error. 127 | Seeking to any positive offset may be allowed, but if the new offset exceeds 128 | the size of the underlying object the behavior of subsequent I/O operations 129 | is implementation-dependent. 130 | """ 131 | 132 | fn seek(inout self, offset: Int64, whence: Int) -> (Int64, Error): 133 | ... 134 | 135 | 136 | trait ReadWriter(Reader, Writer): 137 | ... 138 | 139 | 140 | trait ReadCloser(Reader, Closer): 141 | ... 142 | 143 | 144 | trait WriteCloser(Writer, Closer): 145 | ... 146 | 147 | 148 | trait ReadWriteCloser(Reader, Writer, Closer): 149 | ... 150 | 151 | 152 | trait ReadSeeker(Reader, Seeker): 153 | ... 154 | 155 | 156 | trait ReadSeekCloser(Reader, Seeker, Closer): 157 | ... 158 | 159 | 160 | trait WriteSeeker(Writer, Seeker): 161 | ... 162 | 163 | 164 | trait ReadWriteSeeker(Reader, Writer, Seeker): 165 | ... 166 | 167 | 168 | trait ReaderFrom: 169 | """ReaderFrom is the trait that wraps the ReadFrom method. 170 | 171 | ReadFrom reads data from r until EOF or error. 172 | The return value n is the number of bytes read. 173 | Any error except EOF encountered during the read is also returned. 174 | 175 | The [copy] function uses [ReaderFrom] if available.""" 176 | 177 | fn read_from[R: Reader](inout self, inout reader: R) -> (Int64, Error): 178 | ... 179 | 180 | 181 | trait WriterReadFrom(Writer, ReaderFrom): 182 | ... 183 | 184 | 185 | trait WriterTo: 186 | """WriterTo is the trait that wraps the WriteTo method. 187 | 188 | WriteTo writes data to w until there's no more data to write or 189 | when an error occurs. The return value n is the number of bytes 190 | written. Any error encountered during the write is also returned. 191 | 192 | The copy function uses WriterTo if available.""" 193 | 194 | fn write_to[W: Writer](inout self, inout writer: W) -> (Int64, Error): 195 | ... 196 | 197 | 198 | trait ReaderWriteTo(Reader, WriterTo): 199 | ... 200 | 201 | 202 | trait ReaderAt: 203 | """ReaderAt is the trait that wraps the basic ReadAt method. 204 | 205 | ReadAt reads len(p) bytes into p starting at offset off in the 206 | underlying input source. It returns the number of bytes 207 | read (0 <= n <= len(p)) and any error encountered. 208 | 209 | When ReadAt returns n < len(p), it returns a non-nil error 210 | explaining why more bytes were not returned. In this respect, 211 | ReadAt is stricter than Read. 212 | 213 | Even if ReadAt returns n < len(p), it may use all of p as scratch 214 | space during the call. If some data is available but not len(p) bytes, 215 | ReadAt blocks until either all the data is available or an error occurs. 216 | In this respect ReadAt is different from Read. 217 | 218 | If the n = len(p) bytes returned by ReadAt are at the end of the 219 | input source, ReadAt may return either err == EOF or err == nil. 220 | 221 | If ReadAt is reading from an input source with a seek offset, 222 | ReadAt should not affect nor be affected by the underlying 223 | seek offset. 224 | 225 | Clients of ReadAt can execute parallel ReadAt calls on the 226 | same input source. 227 | 228 | Implementations must not retain p.""" 229 | 230 | fn read_at(self, inout dest: List[Byte], off: Int64) -> (Int, Error): 231 | ... 232 | 233 | 234 | trait WriterAt: 235 | """WriterAt is the trait that wraps the basic WriteAt method. 236 | 237 | WriteAt writes len(p) bytes from p to the underlying data stream 238 | at offset off. It returns the number of bytes written from p (0 <= n <= len(p)) 239 | and any error encountered that caused the write to stop early. 240 | WriteAt must return a non-nil error if it returns n < len(p). 241 | 242 | If WriteAt is writing to a destination with a seek offset, 243 | WriteAt should not affect nor be affected by the underlying 244 | seek offset. 245 | 246 | Clients of WriteAt can execute parallel WriteAt calls on the same 247 | destination if the ranges do not overlap. 248 | 249 | Implementations must not retain p.""" 250 | 251 | fn write_at(self, src: List[Byte], off: Int64) -> (Int, Error): 252 | ... 253 | 254 | 255 | trait ByteReader: 256 | """ByteReader is the trait that wraps the read_byte method. 257 | 258 | read_byte reads and returns the next byte from the input or 259 | any error encountered. If read_byte returns an error, no input 260 | byte was consumed, and the returned byte value is undefined. 261 | 262 | read_byte provides an efficient trait for byte-at-time 263 | processing. A [Reader] that does not implement ByteReader 264 | can be wrapped using bufio.NewReader to add this method.""" 265 | 266 | fn read_byte(inout self) -> (Byte, Error): 267 | ... 268 | 269 | 270 | trait ByteScanner(ByteReader): 271 | """ByteScanner is the trait that adds the unread_byte method to the 272 | basic read_byte method. 273 | 274 | unread_byte causes the next call to read_byte to return the last byte read. 275 | If the last operation was not a successful call to read_byte, unread_byte may 276 | return an error, unread the last byte read (or the byte prior to the 277 | last-unread byte), or (in implementations that support the [Seeker] trait) 278 | seek to one byte before the current offset.""" 279 | 280 | fn unread_byte(inout self) -> Error: 281 | ... 282 | 283 | 284 | trait ByteWriter: 285 | """ByteWriter is the trait that wraps the write_byte method.""" 286 | 287 | fn write_byte(inout self, byte: Byte) -> (Int, Error): 288 | ... 289 | 290 | 291 | trait RuneReader: 292 | """RuneReader is the trait that wraps the read_rune method. 293 | 294 | read_rune reads a single encoded Unicode character 295 | and returns the rune and its size in bytes. If no character is 296 | available, err will be set.""" 297 | 298 | fn read_rune(inout self) -> (Rune, Int): 299 | ... 300 | 301 | 302 | trait RuneScanner(RuneReader): 303 | """RuneScanner is the trait that adds the unread_rune method to the 304 | basic read_rune method. 305 | 306 | unread_rune causes the next call to read_rune to return the last rune read. 307 | If the last operation was not a successful call to read_rune, unread_rune may 308 | return an error, unread the last rune read (or the rune prior to the 309 | last-unread rune), or (in implementations that support the [Seeker] trait) 310 | seek to the start of the rune before the current offset.""" 311 | 312 | fn unread_rune(inout self) -> Rune: 313 | ... 314 | 315 | 316 | trait StringWriter: 317 | """StringWriter is the trait that wraps the WriteString method.""" 318 | 319 | fn write_string(inout self, src: String) -> (Int, Error): 320 | ... 321 | -------------------------------------------------------------------------------- /external/morrow/morrow.mojo: -------------------------------------------------------------------------------- 1 | from ._py import py_dt_datetime 2 | from .util import normalize_timestamp, rjust, _ymd2ord, _days_before_year 3 | from ._libc import c_gettimeofday, c_localtime, c_gmtime, c_strptime 4 | from ._libc import CTimeval, CTm 5 | from .timezone import TimeZone 6 | from .timedelta import TimeDelta 7 | from .formatter import formatter 8 | from .constants import _DAYS_BEFORE_MONTH, _DAYS_IN_MONTH 9 | from python.object import PythonObject 10 | from python import Python 11 | 12 | 13 | alias _DI400Y = 146097 # number of days in 400 years 14 | alias _DI100Y = 36524 # " " " " 100 " 15 | alias _DI4Y = 1461 # " " " " 4 " 16 | 17 | 18 | @value 19 | struct Morrow(StringableRaising): 20 | var year: Int 21 | var month: Int 22 | var day: Int 23 | var hour: Int 24 | var minute: Int 25 | var second: Int 26 | var microsecond: Int 27 | var tz: TimeZone 28 | 29 | fn __init__( 30 | inout self, 31 | year: Int, 32 | month: Int, 33 | day: Int, 34 | hour: Int = 0, 35 | minute: Int = 0, 36 | second: Int = 0, 37 | microsecond: Int = 0, 38 | tz: TimeZone = TimeZone.none(), 39 | ) raises: 40 | self.year = year 41 | self.month = month 42 | self.day = day 43 | self.hour = hour 44 | self.minute = minute 45 | self.second = second 46 | self.microsecond = microsecond 47 | self.tz = tz 48 | 49 | @staticmethod 50 | fn now() raises -> Self: 51 | var t = c_gettimeofday() 52 | return Self._fromtimestamp(t, False) 53 | 54 | @staticmethod 55 | fn utcnow() raises -> Self: 56 | var t = c_gettimeofday() 57 | return Self._fromtimestamp(t, True) 58 | 59 | @staticmethod 60 | fn _fromtimestamp(t: CTimeval, utc: Bool) raises -> Self: 61 | var tm: CTm 62 | var tz: TimeZone 63 | if utc: 64 | tm = c_gmtime(t.tv_sec) 65 | tz = TimeZone(0, "UTC") 66 | else: 67 | tm = c_localtime(t.tv_sec) 68 | tz = TimeZone(int(tm.tm_gmtoff), "local") 69 | 70 | var result = Self( 71 | int(tm.tm_year) + 1900, 72 | int(tm.tm_mon) + 1, 73 | int(tm.tm_mday), 74 | int(tm.tm_hour), 75 | int(tm.tm_min), 76 | int(tm.tm_sec), 77 | t.tv_usec, 78 | tz, 79 | ) 80 | return result 81 | 82 | @staticmethod 83 | fn fromtimestamp(timestamp: Float64) raises -> Self: 84 | var timestamp_ = normalize_timestamp(timestamp) 85 | var t = CTimeval(int(timestamp)) 86 | return Self._fromtimestamp(t, False) 87 | 88 | @staticmethod 89 | fn utcfromtimestamp(timestamp: Float64) raises -> Self: 90 | var timestamp_ = normalize_timestamp(timestamp) 91 | var t = CTimeval(int(timestamp)) 92 | return Self._fromtimestamp(t, True) 93 | 94 | @staticmethod 95 | fn strptime(date_str: String, fmt: String, tzinfo: TimeZone = TimeZone.none()) raises -> Self: 96 | """ 97 | Create a Morrow instance from a date string and format, 98 | in the style of ``datetime.strptime``. Optionally replaces the parsed TimeZone. 99 | 100 | Usage:: 101 | 102 | >>> Morrow.strptime('20-01-2019 15:49:10', '%d-%m-%Y %H:%M:%S') 103 | 104 | """ 105 | var tm = c_strptime(date_str, fmt) 106 | var tz = TimeZone(int(tm.tm_gmtoff)) if tzinfo.is_none() else tzinfo 107 | return Self( 108 | int(tm.tm_year) + 1900, 109 | int(tm.tm_mon) + 1, 110 | int(tm.tm_mday), 111 | int(tm.tm_hour), 112 | int(tm.tm_min), 113 | int(tm.tm_sec), 114 | 0, 115 | tz, 116 | ) 117 | 118 | @staticmethod 119 | fn strptime(date_str: String, fmt: String, tz_str: String) raises -> Self: 120 | """ 121 | Create a Morrow instance by time_zone_string with utc format. 122 | 123 | Usage:: 124 | 125 | >>> Morrow.strptime('20-01-2019 15:49:10', '%d-%m-%Y %H:%M:%S', '+08:00') 126 | 127 | """ 128 | var tzinfo = TimeZone.from_utc(tz_str) 129 | return Self.strptime(date_str, fmt, tzinfo) 130 | 131 | fn format(self, fmt: String = "YYYY-MM-DD HH:mm:ss ZZ") raises -> String: 132 | """Returns a string representation of the `Morrow` 133 | formatted according to the provided format string. 134 | 135 | :param fmt: the format string. 136 | 137 | Usage:: 138 | >>> var m = Morrow.now() 139 | >>> m.format('YYYY-MM-DD HH:mm:ss ZZ') 140 | '2013-05-09 03:56:47 -00:00' 141 | 142 | >>> m.format('MMMM DD, YYYY') 143 | 'May 09, 2013' 144 | 145 | >>> m.format() 146 | '2013-05-09 03:56:47 -00:00' 147 | 148 | """ 149 | return formatter.format(self, fmt) 150 | 151 | fn isoformat(self, sep: String = "T", timespec: StringLiteral = "auto") raises -> String: 152 | """Return the time formatted according to ISO. 153 | 154 | The full format looks like 'YYYY-MM-DD HH:MM:SS.mmmmmm'. 155 | 156 | If self.tzinfo is not None, the UTC offset is also attached, giving 157 | giving a full format of 'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM'. 158 | 159 | Optional argument sep specifies the separator between date and 160 | time, default 'T'. 161 | 162 | The optional argument timespec specifies the number of additional 163 | terms of the time to include. Valid options are 'auto', 'hours', 164 | 'minutes', 'seconds', 'milliseconds' and 'microseconds'. 165 | """ 166 | var date_str = (rjust(self.year, 4, "0") + "-" + rjust(self.month, 2, "0") + "-" + rjust(self.day, 2, "0")) 167 | var time_str = String("") 168 | if timespec == "auto" or timespec == "microseconds": 169 | time_str = ( 170 | rjust(self.hour, 2, "0") 171 | + ":" 172 | + rjust(self.minute, 2, "0") 173 | + ":" 174 | + rjust(self.second, 2, "0") 175 | + "." 176 | + rjust(self.microsecond, 6, "0") 177 | ) 178 | elif timespec == "milliseconds": 179 | time_str = ( 180 | rjust(self.hour, 2, "0") 181 | + ":" 182 | + rjust(self.minute, 2, "0") 183 | + ":" 184 | + rjust(self.second, 2, "0") 185 | + "." 186 | + rjust(self.microsecond // 1000, 3, "0") 187 | ) 188 | elif timespec == "seconds": 189 | time_str = rjust(self.hour, 2, "0") + ":" + rjust(self.minute, 2, "0") + ":" + rjust(self.second, 2, "0") 190 | elif timespec == "minutes": 191 | time_str = rjust(self.hour, 2, "0") + ":" + rjust(self.minute, 2, "0") 192 | elif timespec == "hours": 193 | time_str = rjust(self.hour, 2, "0") 194 | else: 195 | raise Error() 196 | if self.tz.is_none(): 197 | return sep.join(date_str, time_str) 198 | else: 199 | return sep.join(date_str, time_str) + self.tz.format() 200 | 201 | fn toordinal(self) raises -> Int: 202 | """Return proleptic Gregorian ordinal for the year, month and day. 203 | 204 | January 1 of year 1 is day 1. Only the year, month and day values 205 | contribute to the result. 206 | """ 207 | return _ymd2ord(self.year, self.month, self.day) 208 | 209 | @staticmethod 210 | fn fromordinal(ordinal: Int) raises -> Self: 211 | """Construct a Morrow from a proleptic Gregorian ordinal. 212 | 213 | January 1 of year 1 is day 1. Only the year, month and day are 214 | non-zero in the result. 215 | """ 216 | # n is a 1-based index, starting at 1-Jan-1. The pattern of leap years 217 | # repeats exactly every 400 years. The basic strategy is to find the 218 | # closest 400-year boundary at or before n, then work with the offset 219 | # from that boundary to n. Life is much clearer if we subtract 1 from 220 | # n first -- then the values of n at 400-year boundaries are exactly 221 | # those divisible by _DI400Y: 222 | # 223 | # D M Y n n-1 224 | # -- --- ---- ---------- ---------------- 225 | # 31 Dec -400 -_DI400Y -_DI400Y -1 226 | # 1 Jan -399 -_DI400Y +1 -_DI400Y 400-year boundary 227 | # ... 228 | # 30 Dec 000 -1 -2 229 | # 31 Dec 000 0 -1 230 | # 1 Jan 001 1 0 400-year boundary 231 | # 2 Jan 001 2 1 232 | # 3 Jan 001 3 2 233 | # ... 234 | # 31 Dec 400 _DI400Y _DI400Y -1 235 | # 1 Jan 401 _DI400Y +1 _DI400Y 400-year boundary 236 | var n = ordinal 237 | n -= 1 238 | var n400 = n // _DI400Y 239 | n = n % _DI400Y 240 | var year = n400 * 400 + 1 # ..., -399, 1, 401, ... 241 | 242 | # Now n is the (non-negative) offset, in days, from January 1 of year, to 243 | # the desired date. Now compute how many 100-year cycles precede n. 244 | # Note that it's possible for n100 to equal 4! In that case 4 full 245 | # 100-year cycles precede the desired day, which implies the desired 246 | # day is December 31 at the end of a 400-year cycle. 247 | var n100 = n // _DI100Y 248 | n = n % _DI100Y 249 | 250 | # Now compute how many 4-year cycles precede it. 251 | var n4 = n // _DI4Y 252 | n = n % _DI4Y 253 | 254 | # And now how many single years. Again n1 can be 4, and again meaning 255 | # that the desired day is December 31 at the end of the 4-year cycle. 256 | var n1 = n // 365 257 | n = n % 365 258 | 259 | year += n100 * 100 + n4 * 4 + n1 260 | if n1 == 4 or n100 == 4: 261 | return Self(year - 1, 12, 31) 262 | 263 | # Now the year is correct, and n is the offset from January 1. We find 264 | # the month via an estimate that's either exact or one too large. 265 | var leapyear = n1 == 3 and (n4 != 24 or n100 == 3) 266 | var month = (n + 50) >> 5 267 | var preceding: Int 268 | if month > 2 and leapyear: 269 | preceding = _DAYS_BEFORE_MONTH[month] + 1 270 | else: 271 | preceding = _DAYS_BEFORE_MONTH[month] 272 | if preceding > n: # estimate is too large 273 | month -= 1 274 | if month == 2 and leapyear: 275 | preceding -= _DAYS_BEFORE_MONTH[month] + 1 276 | else: 277 | preceding -= _DAYS_BEFORE_MONTH[month] 278 | n -= preceding 279 | 280 | # Now the year and month are correct, and n is the offset from the 281 | # start of that month: we're done! 282 | return Self(year, month, n + 1) 283 | 284 | fn isoweekday(self) raises -> Int: 285 | # "Return day of the week, where Monday == 1 ... Sunday == 7." 286 | # 1-Jan-0001 is a Monday 287 | return self.toordinal() % 7 or 7 288 | 289 | fn __str__(self) raises -> String: 290 | return self.isoformat() 291 | 292 | fn __sub__(self, other: Self) raises -> TimeDelta: 293 | var days1 = self.toordinal() 294 | var days2 = other.toordinal() 295 | var secs1 = self.second + self.minute * 60 + self.hour * 3600 296 | var secs2 = other.second + other.minute * 60 + other.hour * 3600 297 | var base = TimeDelta(days1 - days2, secs1 - secs2, self.microsecond - other.microsecond) 298 | return base 299 | 300 | fn to_py(self) raises -> PythonObject: 301 | # todo: add tz later 302 | var dateimte = Python.import_module("datetime") 303 | return dateimte.datetime( 304 | self.year, 305 | self.month, 306 | self.day, 307 | self.hour, 308 | self.minute, 309 | self.second, 310 | self.microsecond, 311 | ) 312 | 313 | @staticmethod 314 | fn from_py(py_datetime: PythonObject) raises -> Morrow: 315 | # Python.is_type not working, use __class__.__name__ instead 316 | if py_datetime.__class__.__name__ == "datetime": 317 | return Morrow( 318 | int(py_datetime.year.to_float64()), 319 | int(py_datetime.month.to_float64()), 320 | int(py_datetime.day.to_float64()), 321 | int(py_datetime.hour.to_float64()), 322 | int(py_datetime.minute.to_float64()), 323 | int(py_datetime.second.to_float64()), 324 | int(py_datetime.second.to_float64()), 325 | ) 326 | elif py_datetime.__class__.__name__ == "date": 327 | return Morrow( 328 | int(py_datetime.year.to_float64()), 329 | int(py_datetime.month.to_float64()), 330 | int(py_datetime.day.to_float64()), 331 | ) 332 | else: 333 | raise Error("invalid python object, only support py builtin datetime or date") 334 | --------------------------------------------------------------------------------