hexicon 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hexicon/__init__.py ADDED
@@ -0,0 +1,43 @@
1
+ """Hexicon – visual fingerprints for network addresses."""
2
+
3
+ from hexicon._core import (
4
+ CHARSET_DEFAULT,
5
+ DEFAULT_WIDTH,
6
+ LAYOUT_CHOICES,
7
+ OUTPUT_CHOICES,
8
+ TYPE_CHOICES,
9
+ AddressSchema,
10
+ Part,
11
+ addr_to_nibbles,
12
+ detect_type,
13
+ format_json,
14
+ format_text,
15
+ int_to_nibbles,
16
+ nibbles_to_bits,
17
+ parse_addr,
18
+ random_addr,
19
+ render_address,
20
+ render_grid,
21
+ )
22
+
23
+ __all__ = [
24
+ "CHARSET_DEFAULT",
25
+ "DEFAULT_WIDTH",
26
+ "LAYOUT_CHOICES",
27
+ "OUTPUT_CHOICES",
28
+ "TYPE_CHOICES",
29
+ "AddressSchema",
30
+ "Part",
31
+ "addr_to_nibbles",
32
+ "detect_type",
33
+ "format_json",
34
+ "format_text",
35
+ "int_to_nibbles",
36
+ "nibbles_to_bits",
37
+ "parse_addr",
38
+ "random_addr",
39
+ "render_address",
40
+ "render_grid",
41
+ ]
42
+
43
+ __version__ = "0.1.0"
hexicon/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Allow running hexicon as ``python -m hexicon``"""
2
+
3
+ from hexicon.cli import main
4
+
5
+ main()
hexicon/_core.py ADDED
@@ -0,0 +1,304 @@
1
+ """Core rendering logic for hexicon"""
2
+
3
+ import dataclasses
4
+ import ipaddress
5
+ import json
6
+ import random
7
+ import re
8
+ import sys
9
+
10
+ # ── Charset ───────────────────────────────────────────────────────────────────
11
+
12
+ CHARSET_DEFAULT = " ▄▀█" # index: top*2 + bottom → 0=' ' 1='▄' 2='▀' 3='█'
13
+
14
+ # ── Renderer ──────────────────────────────────────────────────────────────────
15
+
16
+ def nibbles_to_bits(nibbles):
17
+ """Convert a list of nibbles to a flat list of bits, MSB first."""
18
+ bits = []
19
+ for n in nibbles:
20
+ for i in (3, 2, 1, 0):
21
+ bits.append((n >> i) & 1)
22
+ return bits
23
+
24
+
25
+ def int_to_nibbles(n, count):
26
+ """Extract count nibbles from integer n, MSB first"""
27
+ return [
28
+ (n >> (4 * shift)) & 0xF
29
+ for shift in reversed(range(count))
30
+ ]
31
+
32
+
33
+ # ── Schema types ──────────────────────────────────────────────────────────────
34
+
35
+ @dataclasses.dataclass
36
+ class Part:
37
+ name: str
38
+ nibbles: list
39
+ bit_range: list
40
+ label: str
41
+
42
+
43
+ @dataclasses.dataclass
44
+ class AddressSchema:
45
+ type: str
46
+ input: str
47
+ nibbles: list
48
+ parts: list
49
+
50
+
51
+ # ── Address → nibbles (unified schema) ────────────────────────────────────────
52
+
53
+ _MAC_RE = re.compile(
54
+ r'^([0-9a-fA-F]{2})[:\-]'
55
+ r'([0-9a-fA-F]{2})[:\-]'
56
+ r'([0-9a-fA-F]{2})[:\-]'
57
+ r'([0-9a-fA-F]{2})[:\-]'
58
+ r'([0-9a-fA-F]{2})[:\-]'
59
+ r'([0-9a-fA-F]{2})$'
60
+ )
61
+
62
+
63
+ def detect_type(raw):
64
+ """Guess address type from string format"""
65
+ raw = raw.strip()
66
+ if _MAC_RE.match(raw):
67
+ return "mac"
68
+ try:
69
+ ipaddress.IPv4Address(raw)
70
+ return "ipv4"
71
+ except ValueError:
72
+ pass
73
+ try:
74
+ ipaddress.IPv6Address(raw)
75
+ return "ipv6"
76
+ except ValueError:
77
+ pass
78
+ raise ValueError(f"Cannot detect address type for: {raw!r}")
79
+
80
+
81
+ def _parse_ip_nibbles(raw, cls, nibble_count):
82
+ """Helper for IPv4/IPv6: parses, normalises, extracts nibbles"""
83
+ obj = cls(raw)
84
+ return str(obj), int_to_nibbles(int(obj), nibble_count)
85
+
86
+
87
+ def _parse_ipv6(raw):
88
+ normalised, all_nibbles = _parse_ip_nibbles(raw, ipaddress.IPv6Address, 32)
89
+ return AddressSchema(
90
+ type="ipv6",
91
+ input=normalised,
92
+ nibbles=all_nibbles,
93
+ parts=[
94
+ Part(name="net", nibbles=all_nibbles[:16], bit_range=[0, 64], label="network"),
95
+ Part(name="host", nibbles=all_nibbles[16:], bit_range=[64, 128], label="host"),
96
+ ],
97
+ )
98
+
99
+
100
+ def _parse_ipv4(raw):
101
+ normalised, all_nibbles = _parse_ip_nibbles(raw, ipaddress.IPv4Address, 8)
102
+ parts = [
103
+ Part(
104
+ name=f"octet{i}",
105
+ nibbles=all_nibbles[i*2 : i*2+2],
106
+ bit_range=[i * 8, (i + 1) * 8],
107
+ label=f"octet {i}",
108
+ )
109
+ for i in range(4)
110
+ ]
111
+ return AddressSchema(
112
+ type="ipv4",
113
+ input=normalised,
114
+ nibbles=all_nibbles,
115
+ parts=parts,
116
+ )
117
+
118
+
119
+ def _parse_mac(raw):
120
+ m = _MAC_RE.match(raw)
121
+ if not m:
122
+ raise ValueError(f"Invalid MAC address: {raw!r}")
123
+ octets = [int(g, 16) for g in m.groups()]
124
+ n = 0
125
+ for b in octets:
126
+ n = (n << 8) | b
127
+ all_nibbles = int_to_nibbles(n, 12)
128
+ normalised = ":".join(f"{b:02x}" for b in octets)
129
+ return AddressSchema(
130
+ type="mac",
131
+ input=normalised,
132
+ nibbles=all_nibbles,
133
+ parts=[
134
+ Part(name="oui", nibbles=all_nibbles[:6], bit_range=[0, 24], label="OUI"),
135
+ Part(name="nic", nibbles=all_nibbles[6:], bit_range=[24, 48], label="NIC"),
136
+ ],
137
+ )
138
+
139
+
140
+ _PARSERS = {"ipv6": _parse_ipv6, "ipv4": _parse_ipv4, "mac": _parse_mac}
141
+
142
+
143
+ def addr_to_nibbles(addr, addr_type="auto"):
144
+ """Parses an addr string and return an AddressSchema."""
145
+ raw = addr.strip()
146
+
147
+ if addr_type == "auto":
148
+ addr_type = detect_type(raw)
149
+
150
+ if addr_type not in _PARSERS:
151
+ raise ValueError(f"Unknown address type: {addr_type!r}")
152
+ return _PARSERS[addr_type](raw)
153
+
154
+
155
+ # ── Constants ─────────────────────────────────────────────────────────────────
156
+
157
+ DEFAULT_WIDTH = {"ipv6": 8, "ipv4": 4, "mac": 6}
158
+ TYPE_CHOICES = ["auto", "ipv6", "ipv4", "mac"]
159
+ LAYOUT_CHOICES = ["auto", "grid", "split", "inline", "barcode"]
160
+ OUTPUT_CHOICES = ["text", "json"]
161
+
162
+
163
+ def render_grid(nibbles, width, charset, invert=False):
164
+ """Render nibbles as scanline pairs using halfblock characters.
165
+
166
+ Converts nibbles to a flat bitstream, groups into scanlines of
167
+ `width` bits, pairs adjacent scanlines (top-bottom), and maps
168
+ each (top_bit, bottom_bit) pair to a halfblock character.
169
+ """
170
+ bits = nibbles_to_bits(nibbles)
171
+
172
+ # Pad to fill complete scanline pairs
173
+ pair_size = width * 2
174
+ remainder = len(bits) % pair_size
175
+ if remainder:
176
+ bits = bits + [0] * (pair_size - remainder)
177
+
178
+ # Group into scanlines
179
+ scanlines = [bits[i:i+width] for i in range(0, len(bits), width)]
180
+
181
+ # Pad to even number of scanlines
182
+ if len(scanlines) % 2:
183
+ scanlines.append([0] * width)
184
+
185
+ rows = []
186
+ for i in range(0, len(scanlines), 2):
187
+ top = scanlines[i]
188
+ bot = scanlines[i + 1]
189
+ row = ''
190
+ for t, b in zip(top, bot):
191
+ idx = t * 2 + b
192
+ row += charset[3 - idx if invert else idx]
193
+ rows.append(row)
194
+ return rows
195
+
196
+ # ── Formatter ─────────────────────────────────────────────────────────────────
197
+
198
+ def format_text(part_rows, layout, scale):
199
+ """
200
+ part_rows: list of (part_name, [rendered_row_strings])
201
+ layout: "split" → side-by-side for 2-part addresses, else stacked
202
+ """
203
+ lines = []
204
+ if layout == "split" and len(part_rows) == 2:
205
+ # side-by-side
206
+ left = part_rows[0][1]
207
+ right = part_rows[1][1]
208
+ left_w = len(left[0]) if left else 0
209
+ right_w = len(right[0]) if right else 0
210
+ max_len = max(len(left), len(right))
211
+ left = left + [" " * left_w] * (max_len - len(left))
212
+ right = right + [" " * right_w] * (max_len - len(right))
213
+ for l, r in zip(left, right):
214
+ row = l + " " + r
215
+ for _ in range(scale):
216
+ lines.append(row)
217
+ else:
218
+ for _name, rows in part_rows:
219
+ for row in rows:
220
+ for _ in range(scale):
221
+ lines.append(row)
222
+ return '\n'.join(lines)
223
+
224
+
225
+ def format_json(schema, part_rows):
226
+ parts_out = []
227
+ for name, rows in part_rows:
228
+ entry = {"name": name, "rows": rows}
229
+ part = next((p for p in schema.parts if p.name == name), None)
230
+ if part:
231
+ entry["bit_range"] = part.bit_range
232
+ entry["label"] = part.label
233
+ parts_out.append(entry)
234
+ return json.dumps({
235
+ "type": schema.type,
236
+ "address": schema.input,
237
+ "parts": parts_out,
238
+ }, ensure_ascii=False)
239
+
240
+ # ── Input handling ────────────────────────────────────────────────────────────
241
+
242
+ def parse_addr(raw, addr_type="auto"):
243
+ """Validate a raw address string. Returns (normalised_str, resolved_type)"""
244
+ raw = raw.strip()
245
+ if addr_type == "auto":
246
+ addr_type = detect_type(raw)
247
+ # full parse to validate
248
+ addr_to_nibbles(raw, addr_type)
249
+ return raw, addr_type
250
+
251
+
252
+ def random_addr(addr_type="ipv6"):
253
+ """Generate a random address string of the given type"""
254
+ if addr_type == "ipv6":
255
+ return str(ipaddress.IPv6Address(random.getrandbits(128)))
256
+ if addr_type == "ipv4":
257
+ return str(ipaddress.IPv4Address(random.getrandbits(32)))
258
+ if addr_type == "mac":
259
+ octets = [random.randint(0, 255) for _ in range(6)]
260
+ return ":".join(f"{b:02x}" for b in octets)
261
+ raise ValueError(f"Cannot generate random address for type: {addr_type!r}")
262
+
263
+
264
+ # ── Core render pipeline ──────────────────────────────────────────────────────
265
+
266
+ def render_address(addr, addr_type="auto", charset=CHARSET_DEFAULT, invert=False,
267
+ scale=1, layout="auto", width="auto", output="text", show_bits=False):
268
+ schema = addr_to_nibbles(addr, addr_type)
269
+ atype = schema.type
270
+
271
+ # Resolve layout
272
+ if layout == "auto":
273
+ layout = "split" if len(schema.parts) == 2 else "grid"
274
+
275
+ # Resolve width
276
+ if width == "auto":
277
+ if layout == "inline":
278
+ total_bits = len(schema.nibbles) * 4
279
+ width = total_bits // 2
280
+ elif layout == "barcode":
281
+ total_bits = len(schema.nibbles) * 4
282
+ width = max(total_bits // 4, 1)
283
+ else:
284
+ width = DEFAULT_WIDTH[atype]
285
+
286
+ if show_bits:
287
+ for part in schema.parts:
288
+ bits = nibbles_to_bits(part.nibbles)
289
+ print(f"{part.name} bits: {''.join(str(b) for b in bits)}", file=sys.stderr)
290
+
291
+ # inline/barcode, render all nibbles as one block
292
+ if layout in ("inline", "barcode"):
293
+ rows = render_grid(schema.nibbles, width, charset, invert=invert)
294
+ part_rows = [("all", rows)]
295
+ else:
296
+ part_rows = []
297
+ for part in schema.parts:
298
+ rows = render_grid(part.nibbles, width, charset, invert=invert)
299
+ part_rows.append((part.name, rows))
300
+
301
+ if output == "json":
302
+ return format_json(schema, part_rows)
303
+ else:
304
+ return format_text(part_rows, layout, scale)
hexicon/cli.py ADDED
@@ -0,0 +1,145 @@
1
+ """Command-line interface for hexicon"""
2
+
3
+ import argparse
4
+ import io
5
+ import sys
6
+
7
+ from hexicon._core import (
8
+ LAYOUT_CHOICES,
9
+ OUTPUT_CHOICES,
10
+ TYPE_CHOICES,
11
+ parse_addr,
12
+ random_addr,
13
+ render_address,
14
+ )
15
+
16
+
17
+ def build_parser():
18
+ parser = argparse.ArgumentParser(
19
+ prog="hexicon",
20
+ description="Render a human-readable identicon for an IPv6 address.",
21
+ )
22
+
23
+ src = parser.add_mutually_exclusive_group(required=True)
24
+ src.add_argument(
25
+ "address", nargs="?", metavar="ADDRESS",
26
+ help="Address to render (IPv6, IPv4, or MAC). Use '-' to read from stdin.",
27
+ )
28
+ src.add_argument(
29
+ "--random", action="store_true",
30
+ help="Generate and render a random address.",
31
+ )
32
+
33
+ parser.add_argument(
34
+ "--type", choices=TYPE_CHOICES, default="auto",
35
+ dest="addr_type",
36
+ help="Address type (default: auto-detect).",
37
+ )
38
+
39
+ parser.add_argument(
40
+ "--invert", action="store_true",
41
+ help="Invert filled and empty pixels.",
42
+ )
43
+ parser.add_argument(
44
+ "--scale", type=int, default=1, metavar="N",
45
+ help="Repeat each row N times (default: 1).",
46
+ )
47
+ parser.add_argument(
48
+ "--layout", choices=LAYOUT_CHOICES,
49
+ default="auto",
50
+ help="Layout mode (default: auto). grid=stacked, split=side-by-side, "
51
+ "inline=single row, barcode=wide strip.",
52
+ )
53
+ parser.add_argument(
54
+ "--width", default="auto", metavar="N|auto",
55
+ help="Bits per scanline row (default: auto, uses type-appropriate width).",
56
+ )
57
+ parser.add_argument(
58
+ "--output", choices=OUTPUT_CHOICES, default="text",
59
+ help="Output format (default: text).",
60
+ )
61
+ parser.add_argument(
62
+ "--no-newline", action="store_true",
63
+ help="Suppress trailing newline (useful for piping).",
64
+ )
65
+ parser.add_argument(
66
+ "--show-bits", action="store_true",
67
+ help="Print raw nibble values to stderr before rendering.",
68
+ )
69
+
70
+ return parser
71
+
72
+
73
+ def emit(text, no_newline=False):
74
+ if no_newline:
75
+ print(text, end="")
76
+ else:
77
+ print(text)
78
+
79
+
80
+ def main():
81
+ # ensure UTF8 output for cp1252 terminals
82
+ if isinstance(sys.stdout, io.TextIOWrapper):
83
+ sys.stdout.reconfigure(encoding="utf-8")
84
+ if isinstance(sys.stderr, io.TextIOWrapper):
85
+ sys.stderr.reconfigure(encoding="utf-8")
86
+
87
+ parser = build_parser()
88
+ args = parser.parse_args()
89
+
90
+ if args.width != "auto":
91
+ try:
92
+ width = int(args.width)
93
+ except ValueError:
94
+ parser.error("--width must be a positive integer or 'auto'")
95
+ if width < 1:
96
+ parser.error("--width must be a positive integer or 'auto'")
97
+ else:
98
+ width = "auto"
99
+
100
+ opts = dict(
101
+ addr_type = args.addr_type,
102
+ invert = args.invert,
103
+ scale = args.scale,
104
+ layout = args.layout,
105
+ width = width,
106
+ output = args.output,
107
+ show_bits = args.show_bits,
108
+ )
109
+
110
+ if args.scale < 1:
111
+ parser.error("--scale must be a positive integer")
112
+
113
+ if args.random:
114
+ addr = random_addr(args.addr_type if args.addr_type != "auto" else "ipv6")
115
+ if args.output == "text":
116
+ print(f"# {addr}", file=sys.stderr)
117
+ result = render_address(addr, **opts)
118
+ emit(result, args.no_newline)
119
+ return
120
+
121
+ # Stdin batch mode
122
+ if args.address == "-":
123
+ for line in sys.stdin:
124
+ line = line.strip()
125
+ if not line:
126
+ continue
127
+ try:
128
+ addr, _ = parse_addr(line, args.addr_type)
129
+ except ValueError as e:
130
+ print(f"hexicon: {e}", file=sys.stderr)
131
+ continue
132
+ result = render_address(addr, **opts)
133
+ emit(result, args.no_newline)
134
+ if not args.no_newline:
135
+ print() # blank separator between addresses
136
+ return
137
+
138
+ # normal single address mode
139
+ try:
140
+ addr, _ = parse_addr(args.address, args.addr_type)
141
+ except ValueError as e:
142
+ parser.error(str(e))
143
+
144
+ result = render_address(addr, **opts)
145
+ emit(result, args.no_newline)
@@ -0,0 +1,147 @@
1
+ Metadata-Version: 2.4
2
+ Name: hexicon
3
+ Version: 0.1.0
4
+ Summary: Visual fingerprints for network addresses — lossless, bit-accurate identicons for IPv6, IPv4, and MAC addresses.
5
+ Project-URL: Homepage, https://github.com/phatbuddha/hexicon
6
+ Project-URL: Repository, https://github.com/phatbuddha/hexicon
7
+ Project-URL: Issues, https://github.com/phatbuddha/hexicon/issues
8
+ Author: phatbuddha
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: identicon,ipv4,ipv6,mac,network,terminal,visualization
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: System Administrators
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: System :: Networking
25
+ Classifier: Topic :: Utilities
26
+ Requires-Python: >=3.9
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Hexicon
30
+
31
+ **Visual fingerprints for network addresses.**
32
+
33
+ A terminal-first tool to turn IPv6, IPv4, and MAC addresses into bit-accurate, lossless, human-readable visual patterns. Visualize structured binary data in the terminal without losing bit order or structure.
34
+ Or identicons, but for network addresses.
35
+
36
+ ## Why?
37
+
38
+ Network addresses are hard to compare at a glance.
39
+ Hexicon lets you compare addresses in logs visually, get an intuitive feel for address distribution, and simplify debugging.
40
+ Or just create pixel art to represent your addresses.
41
+
42
+ ---
43
+
44
+ ## Features
45
+
46
+ - Supports **IPv6, IPv4, and MAC addresses**
47
+ - Bit-accurate rendering. No hashing, no data loss
48
+ - Semantic structure (network/host, OUI/NIC, octets)
49
+ - Terminal-friendly block rendering with multiple layouts: grid, split, inline, barcode
50
+ - JSON output for programmatic use
51
+ - Zero dependencies. Python stdlib only
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ pip install hexicon
57
+ ```
58
+
59
+ ## How it works
60
+
61
+ Hexicon converts addresses into bit-accurate patterns through a deterministic, lossless pipeline. No hashing.
62
+
63
+ Addresses are normalised to their canonical form, converted to a bitstream, grouped into scanlines of `width` bits, paired as adjacent scanlines (top/bottom), and mapped to half-block characters.
64
+ Each cell (half a block) represents a single bit.
65
+
66
+ ## Usage
67
+
68
+ ### CLI
69
+
70
+ ```bash
71
+ hexicon 2001:db8::1
72
+
73
+ hexicon --random
74
+
75
+ hexicon --random --type ipv6 --layout barcode --show-bits --output text
76
+
77
+ # Read from stdin
78
+ echo "::1" | hexicon -
79
+ ```
80
+
81
+ ### Python API
82
+
83
+ ```python
84
+ from hexicon import render_address, addr_to_nibbles
85
+
86
+ # Render an address to text
87
+ print(render_address("2001:db8::1"))
88
+
89
+ # Get structured data
90
+ schema = addr_to_nibbles("2001:db8::1")
91
+ print(schema.type) # "ipv6"
92
+ print(schema.parts) # [Part(name="net", ...), Part(name="host", ...)]
93
+
94
+ # JSON output
95
+ print(render_address("::1", output="json"))
96
+ ```
97
+
98
+ ## Options
99
+
100
+ | Flag | Values | Default | Description |
101
+ |------|--------|---------|-------------|
102
+ | `--type` | `auto`, `ipv6`, `ipv4`, `mac` | `auto` | Address type |
103
+ | `--layout` | `auto`, `grid`, `split`, `inline`, `barcode` | `auto` | Layout mode |
104
+ | `--width` | `N` or `auto` | `auto` | Bits per scanline row |
105
+ | `--scale` | `N` | `1` | Vertical scaling factor |
106
+ | `--invert` | flag | — | Invert filled/empty pixels |
107
+ | `--output` | `text`, `json` | `text` | Output format |
108
+ | `--random` | flag | — | Generate a random address |
109
+ | `--show-bits` | flag | — | Debug: print bit values |
110
+ | `--no-newline` | flag | — | Suppress trailing newline |
111
+
112
+ ## Layouts
113
+
114
+ ### Split
115
+ Default view. Semantic separation of address parts. For IPv6: network │ host. For MAC: OUI │ NIC.
116
+
117
+ ### Grid
118
+ Vertical stacked version of split view.
119
+
120
+ ### Inline
121
+ Single 1-height continuous strip. Good for logs or embedding.
122
+
123
+ ### Barcode
124
+ Compact 2-height view.
125
+
126
+ ## JSON Output
127
+
128
+ Returns a JSON object with the following fields:
129
+
130
+ ```json
131
+ {
132
+ "type": "ipv6",
133
+ "address": "2001:db8::1",
134
+ "parts": [
135
+ {
136
+ "name": "net",
137
+ "rows": ["▀ ▀▄", "..."],
138
+ "bit_range": [0, 64],
139
+ "label": "network"
140
+ }
141
+ ]
142
+ }
143
+ ```
144
+
145
+ ## License
146
+
147
+ MIT
@@ -0,0 +1,9 @@
1
+ hexicon/__init__.py,sha256=8tf51ciIPo3aRoGHZJqsHiFqAqQXebD99PmW67K3UxI,784
2
+ hexicon/__main__.py,sha256=FSvg-4kY7YZpcDHpO2O1qfhrxd71X8eHYHY4figN02s,91
3
+ hexicon/_core.py,sha256=YzOu8JoYBnKiA9s47WExFttgHd0XUAKuP5cV6IBUoRo,10076
4
+ hexicon/cli.py,sha256=OgJJ6xTRXVWXA3S8gbE-4HWU7zc2e8wZM5TRhzi0kec,4165
5
+ hexicon-0.1.0.dist-info/METADATA,sha256=BI4CZ9ru2ADlUocpQh_5GM4jQpX2zWdqfyM2IfJTNEo,4296
6
+ hexicon-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
7
+ hexicon-0.1.0.dist-info/entry_points.txt,sha256=rcrUa5lFjMpBoNEpw5FLRKjuxM4fDoMQTgoVJs9Qa_s,45
8
+ hexicon-0.1.0.dist-info/licenses/LICENSE,sha256=6GMdiHGZ5zQ6lGYe_XIV1blfmJkndxotU7HgbGxETVM,1063
9
+ hexicon-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ hexicon = hexicon.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 buddha
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.