dankcli-lib 0.5.9__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.
File without changes
@@ -0,0 +1,117 @@
1
+ import os
2
+ import sys
3
+ import argparse
4
+ import datetime
5
+ from .caption import Caption
6
+
7
+
8
+ def get_file_name():
9
+ """Generate timestamp-based filename."""
10
+ return datetime.datetime.now().strftime("%Y%m%d%H%M%S")
11
+
12
+
13
+ def parse_color(color_str):
14
+ """Parse color string in format R,G,B or R G B."""
15
+ if not color_str:
16
+ return None
17
+
18
+ # Try comma-separated first
19
+ if ',' in color_str:
20
+ try:
21
+ r, g, b = map(int, color_str.split(','))
22
+ return (r, g, b)
23
+ except ValueError:
24
+ pass
25
+
26
+ # Try space-separated
27
+ try:
28
+ r, g, b = map(int, color_str.split())
29
+ return (r, g, b)
30
+ except ValueError:
31
+ raise ValueError(f"Invalid color format. Use R,G,B or R G B (e.g., '255,255,255')")
32
+
33
+
34
+ def main():
35
+ parser = argparse.ArgumentParser(prog="dankcli_lib", description="Add captions to images with various styling options")
36
+
37
+ # Required arguments
38
+ parser.add_argument("img", help="Path to the source image")
39
+ parser.add_argument("text", help="Text to caption (use \\n for new lines)")
40
+
41
+ # Output options
42
+ parser.add_argument("-f", "--filename", help="Custom output filename (without extension)")
43
+
44
+ # Font options
45
+ parser.add_argument("--font", default="arial.ttf", help="Path to font file (default: arial.ttf)")
46
+
47
+ # Top text options
48
+ parser.add_argument("--top_font_color", help="Top text color as R,G,B or R G B (e.g., '0,0,0' for black)")
49
+ parser.add_argument("--top_bg_color", help="Top background color as R,G,B or R G B (e.g., '255,255,255' for white)")
50
+
51
+ # Bottom text options
52
+ parser.add_argument("--bottom_text", help="Text to place at the bottom of the image")
53
+ parser.add_argument("--bottom_text_box", action="store_true", default=True,
54
+ help="Add white box for bottom text (default: True)")
55
+ parser.add_argument("--no-bottom-text-box", dest="bottom_text_box", action="store_false",
56
+ help="Overlay bottom text directly on image without box")
57
+ parser.add_argument("--bottom_font_color", help="Bottom text color as R,G,B or R G B (e.g., '255,255,255' for white)")
58
+ parser.add_argument("--bottom_bg_color", help="Bottom background color as R,G,B or R G B (e.g., '255,255,255' for white)")
59
+
60
+ # Separator line options
61
+ parser.add_argument("--separator_line", action="store_true",
62
+ help="Draw a line separating the caption from the image")
63
+ parser.add_argument("--separator_color", default="0,0,0",
64
+ help="Separator line color as R,G,B or R G B (default: '0,0,0' for black)")
65
+
66
+ args = parser.parse_args()
67
+
68
+ # Resolve image path
69
+ img_path = os.path.abspath(os.path.expanduser(args.img))
70
+
71
+ # Check if image exists
72
+ if not os.path.exists(img_path):
73
+ print(f"Error: Image file not found: {img_path}")
74
+ sys.exit(1)
75
+
76
+ try:
77
+ # Parse color arguments
78
+ top_font_color = parse_color(args.top_font_color) if args.top_font_color else None
79
+ top_bg_color = parse_color(args.top_bg_color) if args.top_bg_color else None
80
+ bottom_font_color = parse_color(args.bottom_font_color) if args.bottom_font_color else None
81
+ bottom_bg_color = parse_color(args.bottom_bg_color) if args.bottom_bg_color else None
82
+ separator_color = parse_color(args.separator_color)
83
+
84
+ # Create Caption instance with all options
85
+ caption = Caption(
86
+ img_path,
87
+ args.text,
88
+ font_path=args.font,
89
+ separator_line=args.separator_line,
90
+ separator_line_color=separator_color,
91
+ bottom_text=args.bottom_text,
92
+ bottom_text_box=args.bottom_text_box,
93
+ top_font_color=top_font_color,
94
+ bottom_font_color=bottom_font_color,
95
+ top_background_color=top_bg_color,
96
+ bottom_background_color=bottom_bg_color
97
+ )
98
+
99
+ # Determine output filename and extension
100
+ out_name = args.filename if args.filename else get_file_name()
101
+ # Check if image has transparency or if we're using overlay text (no box)
102
+ # PNG supports transparency better, but we'll let the user specify format eventually
103
+ is_jpeg = img_path.lower().endswith(('.jpg', '.jpeg'))
104
+ extension = "jpg" if is_jpeg else "png"
105
+ output_path = f"{out_name}.{extension}"
106
+
107
+ # Generate and save
108
+ caption.save(output_path)
109
+ print(f"Meme saved successfully: {output_path}")
110
+
111
+ except Exception as e:
112
+ print(f"Error: Could not process image. {e}")
113
+ sys.exit(1)
114
+
115
+
116
+ if __name__ == "__main__":
117
+ main()
dankcli_lib/caption.py ADDED
@@ -0,0 +1,251 @@
1
+ from PIL import Image, ImageFont, ImageDraw
2
+ import math, io
3
+
4
+ class Caption:
5
+ """Handles image captioning with text overlay."""
6
+
7
+ # Default styling constants
8
+ TOP_PADDING = 10
9
+ BOTTOM_PADDING = 10
10
+ WIDTH_PADDING = 10
11
+ MINIMUM_FONT_SIZE = 13
12
+ HW_ASPECT_RATIO_THRESHOLD = 1.666
13
+ MAX_BOTTOM_TEXT_HEIGHT_RATIO = 0.334
14
+
15
+ def __init__(self, image_path, text, font_path="arial.ttf", separator_line=False, separator_line_color=None,
16
+ bottom_text=None, bottom_text_box=True,
17
+ top_font_color=None, bottom_font_color=None, top_background_color=None, bottom_background_color=None):
18
+ """
19
+ Initialize Caption with an image and text.
20
+
21
+ Args:
22
+ image_path: Path to the source image
23
+ text: Caption text (supports \\n for newlines)
24
+ font_path: Path to font file (defaults to arial.ttf)
25
+ separator_line: Whether to draw a black line between text and image
26
+ bottom_text: Optional text to place at the bottom of the image
27
+ bottom_text_box: If True, adds white box for bottom text. If False, overlays text on image
28
+ top_font_color: Font color for top text (defaults to black (0,0,0) if None)
29
+ bottom_font_color: Font color for bottom text (defaults to white (255,255,255) if None)
30
+ """
31
+ self.image = Image.open(image_path)
32
+ self.text = text.replace("\\n", "\n")
33
+ self.bottom_text = bottom_text.replace("\\n", "\n") if bottom_text else None
34
+ self.bottom_text_box = bottom_text_box
35
+ self.font_path = font_path
36
+ self.separator_line = separator_line
37
+ self.separator_line_color = separator_line_color if separator_line_color is not None else (0, 0, 0)
38
+
39
+ self.width, self.height = self.image.size
40
+
41
+ # Set default colors if not provided
42
+ self.top_font_color = top_font_color if top_font_color is not None else (0, 0, 0)
43
+ self.bottom_font_color = bottom_font_color if bottom_font_color is not None else (0, 0, 0)
44
+
45
+ self.top_background_color = top_background_color if top_background_color is not None else (255, 255, 255)
46
+ self.bottom_background_color = bottom_background_color if bottom_background_color is not None else (255, 255, 255)
47
+
48
+ # Initialize font
49
+ try:
50
+ font_size = self._calculate_font_size()
51
+ self.font = ImageFont.truetype(font_path, size=font_size)
52
+ except OSError:
53
+ self.font = ImageFont.load_default()
54
+
55
+ def _calculate_font_size(self):
56
+ """Calculate appropriate font size based on image dimensions."""
57
+ temp_size = max(math.floor(self.height / 13), self.MINIMUM_FONT_SIZE)
58
+
59
+ # Scale down for very tall images
60
+ if self.height / self.width >= self.HW_ASPECT_RATIO_THRESHOLD:
61
+ return math.floor(temp_size / 1.5)
62
+ return temp_size
63
+
64
+ def _get_text_dimensions(self, text):
65
+ """Get width and height of text with current font."""
66
+ bbox = self.font.getbbox(text)
67
+ return bbox[2] - bbox[0], bbox[3] - bbox[1]
68
+
69
+ def _wrap_text(self, text, max_width):
70
+ """Wrap text to fit within max_width."""
71
+ lines = []
72
+ words = text.split(' ')
73
+ i = 0
74
+
75
+ while i < len(words):
76
+ line = ''
77
+ while (i < len(words) and
78
+ self._get_text_dimensions(line + words[i])[0] < max_width - self.WIDTH_PADDING):
79
+ line = line + words[i] + " "
80
+ i += 1
81
+
82
+ if not line:
83
+ line = words[i]
84
+ i += 1
85
+
86
+ lines.append(line.strip())
87
+
88
+ return '\n'.join(lines)
89
+
90
+ def _process_text(self):
91
+ """Process and wrap text for rendering."""
92
+ wrapped_lines = "\n".join([
93
+ self._wrap_text(line, self.width)
94
+ for line in self.text.split("\n")
95
+ ])
96
+ return wrapped_lines
97
+
98
+ def _calculate_text_height(self, text):
99
+ """Calculate total height needed for text."""
100
+ line_list = text.split('\n')
101
+ if not line_list:
102
+ return 0
103
+
104
+ _, line_height = self._get_text_dimensions(line_list[0])
105
+ total_text_height = len(line_list) * (line_height * 1.2)
106
+ return int(total_text_height + self.TOP_PADDING + self.BOTTOM_PADDING)
107
+
108
+ def _get_text_position(self, text):
109
+ """Calculate top-left corner position for centered text."""
110
+ line_list = text.split('\n')
111
+ max_w = max(self._get_text_dimensions(line)[0] for line in line_list)
112
+ return ((self.width - max_w) / 2, self.TOP_PADDING)
113
+
114
+ def _get_text_position_bottom(self, text, y_offset):
115
+ """Calculate top-left corner position for centered bottom text."""
116
+ line_list = text.split('\n')
117
+ max_w = max(self._get_text_dimensions(line)[0] for line in line_list)
118
+ return ((self.width - max_w) / 2, y_offset + self.TOP_PADDING)
119
+
120
+ def _get_text_position_bottom_overlay(self, text):
121
+ """Calculate position for bottom text overlay on the image."""
122
+ line_list = text.split('\n')
123
+ max_w = max(self._get_text_dimensions(line)[0] for line in line_list)
124
+
125
+ _, line_height = self._get_text_dimensions(line_list[0])
126
+ total_text_height = len(line_list) * (line_height * 1.2)
127
+
128
+ y_position = self.height - total_text_height - self.BOTTOM_PADDING
129
+
130
+ if y_position < self.TOP_PADDING:
131
+ y_position = self.TOP_PADDING
132
+
133
+ return ((self.width - max_w) / 2, y_position)
134
+
135
+ def generate(self):
136
+ """
137
+ Generate the captioned image.
138
+
139
+ Returns:
140
+ PIL.Image: The captioned image
141
+ """
142
+ wrapped_top_text = self._process_text()
143
+ top_text_height = self._calculate_text_height(wrapped_top_text)
144
+
145
+ bottom_text_height = 0
146
+ wrapped_bottom_text = None
147
+ if self.bottom_text:
148
+ wrapped_bottom_text = "\n".join([
149
+ self._wrap_text(line, self.width)
150
+ for line in self.bottom_text.split("\n")
151
+ ])
152
+ bottom_text_height = self._calculate_text_height(wrapped_bottom_text)
153
+
154
+ if self.bottom_text_box:
155
+ max_allowed_height = int(self.height * self.MAX_BOTTOM_TEXT_HEIGHT_RATIO)
156
+ if bottom_text_height > max_allowed_height:
157
+ pass
158
+
159
+ if self.bottom_text_box or not self.bottom_text:
160
+ total_height = top_text_height + self.height + bottom_text_height
161
+ canvas = Image.new("RGB", (self.width, total_height), (255, 255, 255))
162
+ canvas.paste(self.image, (0, top_text_height))
163
+ else:
164
+ total_height = top_text_height + self.height
165
+ canvas = Image.new("RGB", (self.width, total_height), (255, 255, 255))
166
+ canvas.paste(self.image, (0, top_text_height))
167
+
168
+ draw = ImageDraw.Draw(canvas)
169
+
170
+ if self.top_background_color:
171
+ draw.rectangle(
172
+ [(0, 0), (self.width, top_text_height)],
173
+ fill=self.top_background_color
174
+ )
175
+
176
+ if self.separator_line:
177
+ line_y = top_text_height - 1
178
+ draw.line([(0, line_y), (self.width, line_y)], fill=self.separator_line_color, width=2)
179
+
180
+ top_text_pos = self._get_text_position(wrapped_top_text)
181
+
182
+ draw.multiline_text(
183
+ top_text_pos,
184
+ wrapped_top_text,
185
+ fill=self.top_font_color,
186
+ font=self.font,
187
+ align="center",
188
+ spacing=4
189
+ )
190
+
191
+ if self.bottom_text and wrapped_bottom_text:
192
+ if self.bottom_text_box:
193
+ # Draw bottom background if specified
194
+ if self.bottom_background_color:
195
+ bottom_bg_y = top_text_height + self.height
196
+ draw.rectangle(
197
+ [(0, bottom_bg_y), (self.width, bottom_bg_y + bottom_text_height)],
198
+ fill=self.bottom_background_color
199
+ )
200
+
201
+ bottom_text_pos = self._get_text_position_bottom(wrapped_bottom_text, top_text_height + self.height)
202
+
203
+ draw.multiline_text(
204
+ bottom_text_pos,
205
+ wrapped_bottom_text,
206
+ fill=self.bottom_font_color,
207
+ font=self.font,
208
+ align="center",
209
+ spacing=4
210
+ )
211
+
212
+ if self.separator_line:
213
+ line_y = top_text_height + self.height
214
+ draw.line([(0, line_y), (self.width, line_y)], fill=self.separator_line_color, width=2)
215
+ else:
216
+ overlay_text_pos = self._get_text_position_bottom_overlay(wrapped_bottom_text)
217
+ adjusted_y = overlay_text_pos[1] + top_text_height
218
+
219
+ draw.multiline_text(
220
+ (overlay_text_pos[0], adjusted_y),
221
+ wrapped_bottom_text,
222
+ fill=self.bottom_font_color,
223
+ font=self.font,
224
+ align="center",
225
+ spacing=4
226
+ )
227
+
228
+ return canvas
229
+
230
+ def to_buffer(self, format="JPEG"):
231
+ captioned_image = self.generate()
232
+ buffer = io.BytesIO()
233
+ captioned_image.save(buffer, format=format)
234
+ buffer.seek(0)
235
+ return buffer
236
+
237
+ def close(self):
238
+ """Close the underlying PIL Image to free memory."""
239
+ if hasattr(self, 'image'):
240
+ self.image.close()
241
+
242
+ def save(self, output_path):
243
+ """
244
+ Generate and save the captioned image.
245
+
246
+ Args:
247
+ output_path: Path to save the output image
248
+ """
249
+ captioned_image = self.generate()
250
+ captioned_image.save(output_path)
251
+ return output_path
Binary file
Binary file
@@ -0,0 +1,54 @@
1
+ from PIL import Image, ImageFont, ImageDraw
2
+ import math
3
+ import datetime
4
+
5
+ TOP_PADDING = 10
6
+ BOTTOM_PADDING = 10
7
+ MINIMUM_FONT_SIZE = 13
8
+ HW_ASPECT_RATIO_THRESHOLD = 1.666
9
+ WIDTH_PADDING = 10
10
+
11
+ def get_font_size(img):
12
+ width, height = img.size
13
+ temp_size = max(math.floor(height / 13), MINIMUM_FONT_SIZE)
14
+ # Scale down for very tall images to prevent text from taking over
15
+ if height / width >= HW_ASPECT_RATIO_THRESHOLD:
16
+ return math.floor(temp_size / 1.5)
17
+ return temp_size
18
+
19
+ def get_text_dimensions(text, font):
20
+ # Modern way to get text width/height in Pillow 10+
21
+ bbox = font.getbbox(text)
22
+ return bbox[2] - bbox[0], bbox[3] - bbox[1]
23
+
24
+ def get_top_left_corner(lines, font, img_width):
25
+ line_list = lines.split('\n')
26
+ # Find width of the widest line for centering
27
+ max_w = max(get_text_dimensions(line, font)[0] for line in line_list)
28
+ return ((img_width - max_w) / 2, TOP_PADDING)
29
+
30
+ def text_wrap(text, font, max_width):
31
+ lines = []
32
+ words = text.split(' ')
33
+ i = 0
34
+ while i < len(words):
35
+ line = ''
36
+ while i < len(words) and get_text_dimensions(line + words[i], font)[0] < max_width - WIDTH_PADDING:
37
+ line = line + words[i] + " "
38
+ i += 1
39
+ if not line:
40
+ line = words[i]
41
+ i += 1
42
+ lines.append(line.strip())
43
+ return '\n'.join(lines)
44
+
45
+ def get_white_space_height(lines, font):
46
+ line_list = lines.split('\n')
47
+ if not line_list: return 0
48
+ _, line_height = get_text_dimensions(line_list[0], font)
49
+ # Add spacing between lines (roughly 20% of line height)
50
+ total_text_height = len(line_list) * (line_height * 1.2)
51
+ return int(total_text_height + TOP_PADDING + BOTTOM_PADDING)
52
+
53
+ def get_file_name():
54
+ return datetime.datetime.now().strftime("%Y%m%d%H%M%S")
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: dankcli_lib
3
+ Version: 0.5.9
4
+ Summary: Patched CLI Meme Generator/Caption Maker to automatically add whitespace and text to top and bottom
5
+ Home-page: https://github.com/TheMrRedSlime/dankcli
6
+ Author: TheMrRedSlime
7
+ License: MIT
8
+ Keywords: dankcli dank meme memegenerator memes generator pillow dankmemes dankcli-lib caption maker make
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: pillow
12
+ Dynamic: author
13
+ Dynamic: description
14
+ Dynamic: description-content-type
15
+ Dynamic: home-page
16
+ Dynamic: keywords
17
+ Dynamic: license
18
+ Dynamic: license-file
19
+ Dynamic: requires-dist
20
+ Dynamic: summary
21
+
22
+ dankcli-lib is a CLI Image Captioning Tool, Meme Generator and Library which automatically adds white space and text to the top of your image.
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ $ pip install dankcli-lib
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ```bash
33
+ $ python -m dankcli_lib "path/to/image" "Meme text you want to add" [-f "final_image_name_without_extension"]
34
+ ```
35
+
36
+ #### Python:
37
+
38
+ ```python
39
+ from dankcli_lib.caption import Caption
40
+
41
+ caption = Caption("/path/to/image", "Text here", bottom_text="Bottom text here", bottom_font_color="#000000", bottom_text_box=False, font_path="arial.ttf", separator_line=True, separator_line_color="#000000", top_font_color="#ffffff", top_background_color="#000000", bottom_background_color="#000000")
42
+ caption.save('file.jpg')
43
+ ```
44
+
45
+ ```python
46
+ from dankcli_lib.caption import Caption
47
+
48
+ with Caption("image.jpg", "Your text") as caption:
49
+ buffer = caption.to_buffer()
50
+ await ctx.send(file=discord.File(buffer, "image.jpg"))
51
+ ```
52
+
53
+ ```python
54
+ from dankcli_lib.caption import Caption
55
+ import discord
56
+
57
+ caption = Caption("image.jpg", "Your text")
58
+ buffer = caption.to_buffer()
59
+ await ctx.send(file=discord.File(buffer, "image.jpg"))
60
+ caption.close()
61
+ ```
62
+
63
+
64
+ The text gets automatically wrapped according to width of image but you can also have intentional \n in your text.
65
+ The image is saved in the current folder with the name as the current date and time, the name can be changed with the optional `-f` or `--filename` argument, specifying a file name without the file extension.
66
+
67
+ ## Example
68
+
69
+ #### Example 1 (showing \n functionality)
70
+ ```bash
71
+ $ python -m dankcli_lib "templates/yesbutno.jpg" "Mom at 2am: Are you awake?\n\nMe:"
72
+ ```
73
+ turns this
74
+
75
+ ![](https://i.imgur.com/nW3XPkF.jpg)
76
+
77
+ to this
78
+
79
+ ![](https://i.imgur.com/h6qgp9m.png)
80
+
81
+ #### Example 2 (showing auto textwrap)
82
+ ```bash
83
+ $ python -m dankcli_lib "mymemes/helpmeme.jpg" "When you make a meme generator but now you can't stop making memes"
84
+ ```
85
+ turns this
86
+
87
+ ![](https://i.imgur.com/6CDBFwF.jpg)
88
+
89
+ to this
90
+
91
+ ![](https://i.imgur.com/lSBUfNb.png)
92
+
@@ -0,0 +1,11 @@
1
+ dankcli_lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ dankcli_lib/__main__.py,sha256=vjc_DYzlVldMyuMao9pSsY0FhZgr_rDnH-QFs3o4X48,4679
3
+ dankcli_lib/caption.py,sha256=Gw-AJH-f8ZmJZ1jEMlNCJBHSbeYbQKYOyGksBu0YcYc,10039
4
+ dankcli_lib/functions.py,sha256=4xJLgt7J7wwHXx9PU8JTHtrYW26cxMC7ofYe2hLeCVQ,1793
5
+ dankcli_lib/fonts/HelveticaNeue.ttf,sha256=pRE5x00eO2aslstBe4I4pMoYZBvUUORPEEupmgFrhXw,104904
6
+ dankcli_lib/fonts/arial.ttf,sha256=gq-zXto6Uu2xAQa8wEr5NkY4RCHe1TjTh5LBRE2BYCI,311636
7
+ dankcli_lib-0.5.9.dist-info/licenses/LICENSE,sha256=36P6BuE6u6jk5eDjRVlsLsuQFHxGfWagG5XT404gTWs,1070
8
+ dankcli_lib-0.5.9.dist-info/METADATA,sha256=ICts1RAaHpg2HVGvYsMJljMeWXI1tKpzXNKkfZTpeKQ,2612
9
+ dankcli_lib-0.5.9.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
10
+ dankcli_lib-0.5.9.dist-info/top_level.txt,sha256=e9vV6O1assbgm4G3QNDYCMpIhhTpjYxnknp5RNhfFG4,12
11
+ dankcli_lib-0.5.9.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Shreyas Gupta
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.
@@ -0,0 +1 @@
1
+ dankcli_lib