├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .pre-commit-config.yaml ├── Makefile ├── README.md ├── color-code.py ├── color_code.tmpl ├── demo.py ├── index.css ├── index.tmpl ├── requirements.txt └── setup.cfg /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: [main] 5 | 6 | jobs: 7 | pr: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - uses: actions/setup-python@v1 12 | - run: pip install virtualenv 13 | - run: make 14 | - run: make push 15 | env: 16 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | /color_code.py 3 | /index.html 4 | /index.py 5 | /out 6 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.5.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-yaml 8 | - id: debug-statements 9 | - id: double-quote-string-fixer 10 | - id: name-tests-test 11 | - id: requirements-txt-fixer 12 | - repo: https://github.com/asottile/reorder-python-imports 13 | rev: v3.12.0 14 | hooks: 15 | - id: reorder-python-imports 16 | args: [--py38-plus, --add-import, 'from __future__ import annotations'] 17 | - repo: https://github.com/asottile/add-trailing-comma 18 | rev: v3.1.0 19 | hooks: 20 | - id: add-trailing-comma 21 | - repo: https://github.com/asottile/pyupgrade 22 | rev: v3.15.0 23 | hooks: 24 | - id: pyupgrade 25 | args: [--py38-plus] 26 | - repo: https://github.com/hhatto/autopep8 27 | rev: v2.0.4 28 | hooks: 29 | - id: autopep8 30 | - repo: https://github.com/PyCQA/flake8 31 | rev: 6.1.0 32 | hooks: 33 | - id: flake8 34 | - repo: https://github.com/pre-commit/mirrors-mypy 35 | rev: v1.6.0 36 | hooks: 37 | - id: mypy 38 | - repo: https://github.com/asottile/cheetah_lint 39 | rev: v1.4.0 40 | hooks: 41 | - id: cheetah-reorder-imports 42 | - id: cheetah-flake 43 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: demo 3 | 4 | venv: requirements.txt 5 | rm -rf venv 6 | virtualenv venv -ppython3 7 | venv/bin/pip install -rrequirements.txt 8 | 9 | %.py: %.tmpl venv 10 | venv/bin/cheetah-compile $< 11 | 12 | .PHONY: demo 13 | demo: color_code.py index.py 14 | venv/bin/python demo.py 15 | 16 | .PHONY: push 17 | push: venv 18 | venv/bin/markdown-to-presentation push index.html index.css out 19 | 20 | .PHONY: clean 21 | clean: 22 | rm -rf venv color_code.py index.html out *.pyc 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://github.com/asottile/color-code/workflows/deploy/badge.svg)](https://github.com/asottile/color-code/actions) 2 | [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/asottile/color-code/main.svg)](https://results.pre-commit.ci/latest/github/asottile/color-code/main) 3 | 4 | color-code 5 | ========== 6 | 7 | This was a derpy project made for hackathon 19 (the "color code" hackathon). 8 | 9 | It encodes any file into colored rectangles. 10 | 11 | You can see some examples on [github pages](https://asottile.github.io/color-code) 12 | 13 | Usage: 14 | 15 | ``` 16 | make 17 | . venv/bin/activate 18 | ./color-code.py FILENAME OUTPUT --name NAME [--widths] 19 | ``` 20 | -------------------------------------------------------------------------------- /color-code.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import annotations 3 | 4 | import argparse 5 | import subprocess 6 | import sys 7 | 8 | 9 | def main() -> int: 10 | parser = argparse.ArgumentParser() 11 | parser.add_argument('filename') 12 | parser.add_argument('output') 13 | parser.add_argument('--widths', action='store_true') 14 | parser.add_argument('--name', required=True) 15 | args = parser.parse_args() 16 | args.name = args.name or args.filename 17 | 18 | with open(args.output, 'wb') as output: 19 | return subprocess.call( 20 | (sys.executable, '-m', 'color_code'), 21 | env={ 22 | 'FILENAME': args.filename, 'WIDTHS': str(args.widths), 23 | 'NAME': args.name, 24 | }, 25 | stdout=output, 26 | ) 27 | 28 | 29 | if __name__ == '__main__': 30 | raise SystemExit(main()) 31 | -------------------------------------------------------------------------------- /color_code.tmpl: -------------------------------------------------------------------------------- 1 | #import codecs 2 | 3 | 4 | 5 | 6 | 7 | $NAME as colors 8 | 20 | 21 | 22 | #py use_widths = $WIDTHS == 'True' 23 | #py width = 50 24 | #with open($FILENAME, 'rb') as f: 25 | #while True: 26 | #py triplet = codecs.encode(f.read(3), 'hex') 27 | #if not triplet: 28 | #break 29 | #end if 30 | #if use_widths: 31 | #py width = ord(f.read(1) or b'\x00') + 20 32 | #end if 33 |
34 | #end while 35 | #end with 36 | 37 | 38 | -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import copyreg 4 | import json 5 | import os.path 6 | import random 7 | import subprocess 8 | import sys 9 | import tempfile 10 | import urllib.request 11 | 12 | 13 | DOWNLOAD = ( 14 | ( 15 | 'dumb-init', 16 | 'https://github.com/Yelp/dumb-init/releases/download/v1.0.0/' 17 | 'dumb-init_1.0.0_amd64', 18 | ), 19 | ( 20 | 'pip_faster.py', 21 | 'https://raw.githubusercontent.com/Yelp/pip-faster/' 22 | '275da8/pip_faster.py', 23 | ), 24 | ( 25 | 'yelp.com-logo.png', 26 | 'https://s3-media2.fl.yelpcdn.com/assets/srv0/yelp_styleguide/' 27 | 'e0bcd848f6ae/assets/img/logos/header_logo.png', 28 | ), 29 | ( 30 | 'pre-commit-favicon', 31 | 'https://raw.githubusercontent.com/pre-commit/pre-commit.github.io/' 32 | '8135597/favicon.ico', 33 | ), 34 | ) 35 | 36 | ON_DISK: tuple[str, ...] = ( 37 | '/bin/echo', 38 | '/bin/cat', 39 | # https://github.com/python/mypy/issues/9726 40 | copyreg.__cached__, # type: ignore 41 | ) 42 | 43 | 44 | def to_filename(name: str) -> str: 45 | return os.path.basename(name) 46 | 47 | 48 | def run(filename: str, output_name: str) -> None: 49 | print(f'Making {output_name}') 50 | out_fname = to_filename(output_name) 51 | subprocess.check_call(( 52 | sys.executable, 'color-code.py', '--name', output_name, 53 | filename, f'out/{out_fname}.html', 54 | )) 55 | subprocess.check_call(( 56 | sys.executable, 'color-code.py', '--name', output_name, '--widths', 57 | filename, f'out/{out_fname}_widths.html', 58 | )) 59 | 60 | 61 | def main() -> int: 62 | os.makedirs('out', exist_ok=True) 63 | with tempfile.TemporaryDirectory() as tmpdir: 64 | path = os.path.join(tmpdir, 'tmpfile') 65 | for name, url in DOWNLOAD: 66 | resp = urllib.request.urlopen(url) 67 | with open(path, 'wb') as f: 68 | f.write(resp.read()) 69 | run(path, name) 70 | 71 | random.seed(0) 72 | some_bytes = bytes(random.getrandbits(8) for _ in range(2400)) 73 | with open(path, 'wb') as f: 74 | f.write(some_bytes) 75 | run(path, 'random') 76 | 77 | for filename in ON_DISK: 78 | run(filename, filename) 79 | 80 | names = [name for name, _ in DOWNLOAD] + list(ON_DISK) + ['random'] 81 | names_fnames = [ 82 | (name.rpartition('/')[2], to_filename(name)) for name in names 83 | ] 84 | with open('index.html', 'wb') as f: 85 | subprocess.check_call( 86 | (sys.executable, 'index.py'), 87 | stdout=f, 88 | env={'NAMES_FNAMES': json.dumps(names_fnames)}, 89 | ) 90 | 91 | return 0 92 | 93 | 94 | if __name__ == '__main__': 95 | raise SystemExit(main()) 96 | -------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | body { 2 | height: 100%; 3 | width: 100%; 4 | margin: 0; 5 | padding: 0; 6 | overflow: hidden; 7 | font-family: monospace; 8 | } 9 | 10 | h1, p { 11 | color: #333; 12 | } 13 | 14 | h1 { 15 | font-size: 48px; 16 | font-weight: bold; 17 | background: linear-gradient( 18 | to right, 19 | salmon, 20 | tomato, 21 | coral, 22 | gold, 23 | darkseagreen, 24 | lightblue, 25 | violet, 26 | indigo 27 | ); 28 | -webkit-background-clip: text; 29 | -webkit-text-fill-color: transparent; 30 | } 31 | 32 | main { 33 | height: 100%; 34 | width: 100%; 35 | } 36 | 37 | .square { 38 | border-radius: 3px; 39 | display: inline-block; 40 | margin: 1px; 41 | height: 36px; 42 | opacity: 0.6; 43 | cursor: pointer; 44 | } 45 | 46 | .square:hover { 47 | opacity: 1; 48 | } 49 | 50 | .wrapper { 51 | display: flex; 52 | flex-flow: row nowrap; 53 | overflow: hidden; 54 | align-content: flex-start; 55 | } 56 | 57 | .wrapper a { 58 | flex-grow: 1; 59 | } 60 | 61 | .box { 62 | flex-basis: 150px; 63 | } 64 | 65 | .box, .inner-box { 66 | box-shadow: 2px 2px rgba(0, 0, 0, 0.3); 67 | cursor: pointer; 68 | opacity: 0.7; 69 | min-height: 35px; 70 | height: 6vh; 71 | max-height: 45px; 72 | margin: 2px; 73 | border-radius: 4px; 74 | } 75 | 76 | .box:hover, .inner-box:hover { 77 | box-shadow: none; 78 | opacity: 1; 79 | } 80 | 81 | .weird-middle { 82 | display: flex; 83 | flex-flow: row nowrap; 84 | } 85 | 86 | .info { 87 | margin: 2px; 88 | border-radius: 4px; 89 | text-align: center; 90 | flex-basis: 700px; 91 | } 92 | 93 | .info-wrapper { 94 | max-width: 90%; 95 | margin: auto; 96 | 97 | } 98 | 99 | .inner-column { 100 | display: flex; 101 | flex-flow: column nowrap; 102 | flex-grow: 1; 103 | } 104 | 105 | .inner-wrapper { 106 | display: flex; 107 | flex-flow: row nowrap; 108 | align-items: flex-start; 109 | } 110 | 111 | .inner-wrapper a { 112 | flex-grow: 1; 113 | } 114 | 115 | @media only screen and (max-width: 768px) and (min-width: 481px) { 116 | .info { 117 | flex-basis: 450px; 118 | } 119 | 120 | h1 { 121 | font-size: 36px; 122 | } 123 | } 124 | 125 | @media only screen and (max-width: 480px) { 126 | .info { 127 | flex-basis: 100%; 128 | } 129 | 130 | .inner-column { 131 | display: none !important; 132 | } 133 | 134 | h1 { 135 | font-size: 28px; 136 | } 137 | 138 | .wrapper:last-child { 139 | display: none; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /index.tmpl: -------------------------------------------------------------------------------- 1 | #import codecs 2 | #import json 3 | #import random 4 | #import struct 5 | 6 | 7 | #py random.seed(0) 8 | 9 | 10 | 11 | 12 | 13 | Color Code 14 | 15 | 16 | 17 |
18 | $self._render_top_bottom(4) 19 |
20 | $self._render_info_sides() 21 |
22 |
23 |

color code

24 |

25 | This is a digital art program made for yelp's hackathon 19 (theme: "color code"). It encodes any file into colored rectangles by converting every 3 bytes of the file into a color and optionally, the 4th byte into a width. 26 |

27 | View on GitHub 28 |

29 | Examples: 30 | #py names_fnames = json.loads($NAMES_FNAMES) 31 | #for name, fname in names_fnames[:-1]: 32 | $name 33 | (w/o widths) * 34 | #end for 35 | $names_fnames[-1][0] 36 | (w/o widths) 37 |

38 |
39 |
40 | $self._render_info_sides() 41 |
42 | $self._render_top_bottom(20) 43 |
44 | 45 | 46 | 47 | 48 | #def _rect(minsize, maxsize) 49 | #py width = random.randint(minsize, maxsize) 50 | #py color = codecs.encode(struct.pack(b'I', random.getrandbits(24)), 'hex')[:6] 51 | #py names_fnames = json.loads($NAMES_FNAMES) 52 | #py rand = random.choice(names_fnames)[1] + random.choice(('_widths', '')) 53 | 54 |
55 |
56 | #end def 57 | 58 | 59 | #def _render_top_bottom(num) 60 |
61 | #for _ in range($num) 62 |
63 | #for _ in range(random.randint(5, 11)) 64 | $self._rect(10, 170) 65 | #end for 66 |
67 | #end for 68 |
69 | #end def 70 | 71 | #def _render_info_sides() 72 |
73 | #for _ in range(8) 74 |
75 | #for _ in range(random.randint(2, 5)) 76 | $self._rect(20, 100) 77 | #end for 78 |
79 | #end for 80 |
81 | #end def 82 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | markdown-to-presentation>=0.0.28 2 | pre-commit 3 | yelp-cheetah 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [mypy] 2 | check_untyped_defs = true 3 | disallow_any_generics = true 4 | disallow_incomplete_defs = true 5 | disallow_untyped_defs = true 6 | warn_redundant_casts = true 7 | warn_unused_ignores = true 8 | 9 | [mypy-testing.*] 10 | disallow_untyped_defs = false 11 | 12 | [mypy-tests.*] 13 | disallow_untyped_defs = false 14 | --------------------------------------------------------------------------------