cedbox 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.
cedbox/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ """
2
+ CedBox - A Python utility package for data handling, input validation, Morse code processing, and audio generation.
3
+ """
4
+
5
+ from cedbox.yggdrasil import Yggdrasil
6
+ from cedbox.inputs import (
7
+ string_put, int_put, float_put, choice_put,
8
+ bool_put, file_put, date_put, mail_put
9
+ )
10
+ from cedbox.easymorse import EasyMorse, MORSE_CODE_DICT
11
+ from cedbox.easywav import EasyWav
12
+
13
+ __version__ = "0.1.0"
cedbox/easymorse.py ADDED
@@ -0,0 +1,80 @@
1
+ MORSE_CODE_DICT = {
2
+ 'A': '.-', 'B': '-...',
3
+ 'C': '-.-.', 'D': '-..', 'E': '.',
4
+ 'F': '..-.', 'G': '--.', 'H': '....',
5
+ 'I': '..', 'J': '.---', 'K': '-.-',
6
+ 'L': '.-..', 'M': '--', 'N': '-.',
7
+ 'O': '---', 'P': '.--.', 'Q': '--.-',
8
+ 'R': '.-.', 'S': '...', 'T': '-',
9
+ 'U': '..-', 'V': '...-', 'W': '.--',
10
+ 'X': '-..-', 'Y': '-.--', 'Z': '--..',
11
+ '1': '.----', '2': '..---', '3': '...--',
12
+ '4': '....-', '5': '.....', '6': '-....',
13
+ '7': '--...', '8': '---..', '9': '----.',
14
+ '0': '-----', ', ': '--..--', '.': '.-.-.-',
15
+ '?': '..--..', '/': '-..-.', '-': '-....-',
16
+ '(': '-.--.', ')': '-.--.-', ':': '---...',
17
+ }
18
+
19
+
20
+ class EasyMorse:
21
+ """Class for creating and managing Morse code time units."""
22
+
23
+ def __init__(self, text, morse_dict=None, prefix:bool|str=False):
24
+ """
25
+ Initialize the MorseCodeTimes class.
26
+
27
+ Args:
28
+
29
+ """
30
+ morse_dict = morse_dict if morse_dict is not None else MORSE_CODE_DICT
31
+ morse_dict.update({' ': '_____'})
32
+ self.morse_dict = morse_dict
33
+ self.times_dict = self._create_times_dict()
34
+
35
+ if isinstance(prefix, bool):
36
+ self.prefix = "._._._-___._._._-___._._._-___-_._._._._-_____" if prefix else "" #VVV- as prefix
37
+ elif isinstance(prefix, str):
38
+ self.prefix = prefix
39
+
40
+ self.raw_text = text
41
+ self.text_to_morse = None
42
+ self.morse_code = None
43
+ self.morse_code_times = None
44
+ self.morse_message = None
45
+ self.morse_seq = None
46
+ self.set_text(text)
47
+
48
+
49
+ def _create_times_dict(self):
50
+ """
51
+ Convert Morse code dictionary to time units dictionary.
52
+
53
+ Returns:
54
+ dict: Dictionary mapping characters to tuples of time units.
55
+ """
56
+ times_dict = {
57
+ char: tuple(1 if c == '.' else 3 for c in code)
58
+ for char, code in self.morse_dict.items()
59
+ }
60
+ times_dict.update({' ': (-5,)})
61
+ return times_dict
62
+
63
+
64
+ def set_text(self, raw_text: str):
65
+
66
+ self.raw_text = raw_text
67
+
68
+ self.text_to_morse = [char for char in self.raw_text.upper() if char in self.morse_dict]
69
+
70
+ morse_code = ['_'.join(self.morse_dict[char]) for char in self.text_to_morse]
71
+
72
+ self.morse_code = '___'.join(morse_code).replace('_______________', '_____')
73
+
74
+ self.morse_message = self.prefix + self.morse_code if self.prefix else self.morse_code
75
+
76
+ self.morse_seq = [{'.': 1, '-': 3, '_': -3}[char] for char in self.morse_message]
77
+
78
+ if __name__ == "__main__":
79
+ morse = EasyMorse('KM km')
80
+ print(morse.morse_seq)
cedbox/easywav.py ADDED
@@ -0,0 +1,41 @@
1
+ import wave
2
+ import math
3
+ import struct
4
+
5
+
6
+ class EasyWav:
7
+ def __init__(self, sequence: list[int], sample_rate=44100, frequency=440, time_unit=100):
8
+ self.audio_samples: list[int] = []
9
+ self.sample_rate: int = sample_rate
10
+ self.frequency: int = frequency
11
+ sequence: list[int] = [time_unit*seq for seq in sequence]
12
+ self.sequence: list[int] = sequence
13
+ self.from_seq(sequence)
14
+
15
+ def _generate_samples(self, duration) -> list[int]:
16
+ if duration > 0: # Signal
17
+ num_samples = int(abs(duration) * self.sample_rate / 1000)
18
+ return [int(32767 * math.sin(2 * math.pi * self.frequency * (float(i) / self.sample_rate)))
19
+ for i in range(num_samples)]
20
+ else: # Pause
21
+ num_samples = int(abs(duration) * self.sample_rate / 1000)
22
+ return [0] * num_samples
23
+
24
+ def from_seq(self, durations) -> None:
25
+ audio_samples = []
26
+ for duration in durations:
27
+ audio_samples.extend(self._generate_samples(duration))
28
+ self.audio_samples = audio_samples
29
+
30
+ def save(self, filename='output.wav') -> None:
31
+ with wave.open(filename, 'w') as wave_file:
32
+ wave_file.setnchannels(1)
33
+ wave_file.setsampwidth(2)
34
+ wave_file.setframerate(self.sample_rate)
35
+ wave_file.writeframes(struct.pack('h' * len(self.audio_samples), *self.audio_samples))
36
+
37
+
38
+ if __name__ == '__main__':
39
+ data = [1, -1, 3, 1, -1, 3]
40
+ wav = EasyWav(sequence=data)
41
+ wav.save('output.wav')
cedbox/inputs.py ADDED
@@ -0,0 +1,120 @@
1
+ from datetime import datetime
2
+ import pathlib
3
+ import re
4
+ import email.utils
5
+
6
+
7
+ def string_put(text, max_times=None, times=1, default='') -> str:
8
+ raw_input = input(text)
9
+ if max_times and times == max_times:
10
+ print(f'Exceeded {max_times} Trys, using default Value {default}')
11
+ return default
12
+ else:
13
+ return raw_input
14
+
15
+
16
+ def int_put(text, max_times=None, times=1, default=1, conditions=None) -> int:
17
+ if conditions is None:
18
+ conditions = []
19
+
20
+ raw_input = input(text)
21
+ try:
22
+ int_input = int(raw_input)
23
+ for condition in conditions:
24
+ assert condition(int_input)
25
+ return int_input
26
+ except Exception as e:
27
+ print(f'{raw_input} is not valid {e}')
28
+ if max_times and times >= max_times:
29
+ print(f'Exceeded {max_times} Trys, using default Value {default}')
30
+ return default
31
+ return int_put(text, max_times=max_times, times=times + 1, default=default, conditions=conditions)
32
+
33
+
34
+ def float_put(text, max_times=None, times=1, default=1.0, conditions=None) -> float:
35
+ if conditions is None:
36
+ conditions = []
37
+
38
+ raw_input = input(text)
39
+ try:
40
+ float_input = float(raw_input)
41
+ for condition in conditions:
42
+ assert condition(float_input)
43
+ return float_input
44
+ except Exception as e:
45
+ print(f'{raw_input} is not valid {e}')
46
+ if max_times and times >= max_times:
47
+ print(f'Exceeded {max_times} Trys, using default Value {default}')
48
+ return default
49
+ return float_put(text, max_times=max_times, times=times + 1, default=default, conditions=conditions)
50
+
51
+
52
+ def choice_put(text: str, choices: list, max_times=None, times=1, def_choice_by_index=1) -> str:
53
+ raw_input = input(text + f"({'/'.join(choices)})" + ': ')
54
+
55
+ if raw_input in choices:
56
+ return raw_input
57
+ else:
58
+ if max_times and times >= max_times:
59
+ default = choices[def_choice_by_index]
60
+ print(f'Exceeded {max_times} Trys, using default Value {default}')
61
+ return default
62
+ print(f'{raw_input} is not valid')
63
+ return choice_put(text, choices, max_times=max_times, times=times + 1, def_choice_by_index=def_choice_by_index)
64
+
65
+
66
+ def bool_put(text: str, max_times=None, times=1, default=False) -> bool:
67
+ raw_input = choice_put(text, choices=['y', 'n'])
68
+
69
+ match raw_input:
70
+ case 'y':
71
+ return True
72
+ case 'n':
73
+ return False
74
+ case _:
75
+
76
+ print(f'{raw_input} is not valid')
77
+ return bool_put(text)
78
+
79
+
80
+ def file_put(text, max_times=None, times=1, default=None):
81
+ raw_input = input(text).strip()
82
+
83
+ try:
84
+ path = pathlib.Path(raw_input)
85
+ assert path.exists()
86
+ return path
87
+ except Exception as e:
88
+ print(f'{raw_input} is not a valid Path: {e}')
89
+ if max_times and times >= max_times:
90
+ print(f'Exceeded {max_times} Trys, using default Value {default}')
91
+ return default
92
+ return file_put(text, max_times=max_times, times=times + 1, default=default)
93
+
94
+
95
+ def date_put(text, max_times=None, times=1, default=None):
96
+ date_raw = input(f'{text}(YYYY-MM-DD)')
97
+
98
+ try:
99
+ date = datetime.strptime(date_raw, '%Y-%m-%d')
100
+ except Exception as e:
101
+ print(f'{date_raw} is not valid', e)
102
+ if max_times and times >= max_times:
103
+ print(f'Exceeded {max_times} Trys, using default Value {default}')
104
+ return default
105
+ return date_put(text, max_times=max_times, times=times + 1, default=default)
106
+ return date
107
+
108
+
109
+ def mail_put(text, max_times=None, times=1, default=None):
110
+ email_str = input(text)
111
+ try:
112
+ email_address = email.utils.parseaddr(email_str)[1]
113
+ assert re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email_address)
114
+ return email_address
115
+ except Exception as e:
116
+ print(f'{email_str} is not valid', e)
117
+ if max_times and times >= max_times:
118
+ print(f'Exceeded {max_times} Trys, using default Value {default}')
119
+ return default
120
+ return mail_put(text, max_times=max_times, times=times + 1, default=default)
cedbox/yggdrasil.py ADDED
@@ -0,0 +1,223 @@
1
+ import pandas as pd
2
+ import sqlite3
3
+
4
+ class Yggdrasil(dict):
5
+ def __init__(self, leaf_behavior='overwrite'):
6
+ """
7
+ Initialize a new Yggdrasil tree.
8
+
9
+ Args:
10
+ leaf_behavior (str or callable): How to handle duplicate leaf nodes.
11
+ If str, must be one of:
12
+ 'overwrite': Replace existing value (default)
13
+ 'append': Append to existing value if possible
14
+ 'add': Add to existing value if both are numeric
15
+ 'subtract': Subtract new value from existing value if both are numeric
16
+ 'multiply': Multiply with existing value if both are numeric
17
+ 'divide': Divide existing value by new value if both are numeric
18
+ If callable, must be a function that takes two arguments (existing_value, new_value)
19
+ and returns the value to be stored.
20
+ """
21
+ super().__init__()
22
+ self.leaf_behavior = leaf_behavior
23
+
24
+ def __getitem__(self, key):
25
+ if key not in self:
26
+ self[key] = self.__class__(leaf_behavior=self.leaf_behavior)
27
+ return super().__getitem__(key)
28
+
29
+ def __setitem__(self, key, values=None):
30
+ if not isinstance(values, list) or not values:
31
+ # Handle leaf node behavior if the key already exists
32
+ if key in self and not isinstance(super().__getitem__(key), Yggdrasil):
33
+ existing_value = super().__getitem__(key)
34
+
35
+ # Check if leaf_behavior is a callable (custom function)
36
+ if callable(self.leaf_behavior):
37
+ try:
38
+ # Call the custom function with existing and new values
39
+ new_value = self.leaf_behavior(existing_value, values)
40
+ super().__setitem__(key, new_value)
41
+ except Exception as e:
42
+ # If the custom function fails, fall back to overwrite
43
+ super().__setitem__(key, values)
44
+ # Apply the specified behavior
45
+ elif self.leaf_behavior == 'overwrite' or existing_value is None:
46
+ # Default behavior - just overwrite
47
+ super().__setitem__(key, values)
48
+ elif self.leaf_behavior == 'append':
49
+ # Try to append values
50
+ try:
51
+ new_value = existing_value + values
52
+ super().__setitem__(key, new_value)
53
+ except (TypeError, ValueError):
54
+ # If append fails, fall back to overwrite
55
+ super().__setitem__(key, values)
56
+ elif self.leaf_behavior == 'add' and values is not None:
57
+ # Try to add values numerically - only for numeric types
58
+ if (isinstance(existing_value, (int, float)) and
59
+ isinstance(values, (int, float))):
60
+ new_value = existing_value + values
61
+ super().__setitem__(key, new_value)
62
+ else:
63
+ # For non-numeric types, fall back to overwrite
64
+ super().__setitem__(key, values)
65
+ elif self.leaf_behavior == 'multiply' and values is not None:
66
+ # Try to multiply values - only for numeric types
67
+ if (isinstance(existing_value, (int, float)) and
68
+ isinstance(values, (int, float))):
69
+ new_value = existing_value * values
70
+ super().__setitem__(key, new_value)
71
+ else:
72
+ # For non-numeric types, fall back to overwrite
73
+ super().__setitem__(key, values)
74
+ elif self.leaf_behavior == 'subtract' and values is not None:
75
+ # Try to subtract values - only for numeric types
76
+ if (isinstance(existing_value, (int, float)) and
77
+ isinstance(values, (int, float))):
78
+ new_value = existing_value - values
79
+ super().__setitem__(key, new_value)
80
+ else:
81
+ # For non-numeric types, fall back to overwrite
82
+ super().__setitem__(key, values)
83
+ elif self.leaf_behavior == 'divide' and values is not None:
84
+ # Try to divide values - only for numeric types
85
+ if (isinstance(existing_value, (int, float)) and
86
+ isinstance(values, (int, float))):
87
+ # Check for division by zero
88
+ if values == 0:
89
+ # For division by zero, fall back to overwrite
90
+ super().__setitem__(key, values)
91
+ else:
92
+ new_value = existing_value / values
93
+ super().__setitem__(key, new_value)
94
+ else:
95
+ # For non-numeric types, fall back to overwrite
96
+ super().__setitem__(key, values)
97
+ else:
98
+ # Unknown behavior or incompatible types, fall back to overwrite
99
+ super().__setitem__(key, values)
100
+ else:
101
+ # Key doesn't exist or is a Yggdrasil instance, just set the value
102
+ super().__setitem__(key, values)
103
+ return
104
+
105
+ if key not in self:
106
+ super().__setitem__(key, self.__class__(leaf_behavior=self.leaf_behavior))
107
+
108
+ value = values.pop(0)
109
+ if values:
110
+ self[key].__setitem__(value, values)
111
+ else:
112
+ # When setting a leaf node, just set the value
113
+ # The behavior logic is already handled in the first part of this method
114
+ self[key] = value
115
+
116
+ def add_fiber(self, fiber):
117
+ """
118
+ Add a fiber (path) to the tree.
119
+
120
+ Args:
121
+ fiber: A list-like object or pandas Series representing a path in the tree.
122
+ The first element is the root node, and subsequent elements form the path.
123
+ """
124
+ # Convert pandas Series to list if necessary
125
+ if isinstance(fiber, pd.Series):
126
+ fiber = fiber.tolist()
127
+
128
+ # Make a copy to avoid modifying the original
129
+ fiber_copy = fiber.copy() if hasattr(fiber, 'copy') else list(fiber)
130
+
131
+ sprout = fiber_copy.pop(0)
132
+ self[sprout] = fiber_copy
133
+
134
+ @classmethod
135
+ def from_dataframe(cls, df, leaf_behavior='overwrite'):
136
+ """
137
+ Create a new Yggdrasil tree from a pandas DataFrame.
138
+
139
+ Each row in the DataFrame will be added as a fiber to the tree.
140
+
141
+ Args:
142
+ df (pandas.DataFrame): The DataFrame to convert to a tree
143
+ leaf_behavior (str or callable): How to handle duplicate leaf nodes
144
+ (passed to Yggdrasil constructor)
145
+
146
+ Returns:
147
+ Yggdrasil: A new Yggdrasil tree containing the data from the DataFrame
148
+ """
149
+ tree = cls(leaf_behavior=leaf_behavior)
150
+
151
+ # Iterate through each row in the DataFrame
152
+ for _, row in df.iterrows():
153
+ # Add the row as a fiber to the tree
154
+ tree.add_fiber(row)
155
+
156
+ return tree
157
+
158
+ @classmethod
159
+ def from_sql(cls, query, connection, leaf_behavior='overwrite'):
160
+ """
161
+ Create a new Yggdrasil tree from a SQL query.
162
+
163
+ Executes the query and converts the result to a DataFrame,
164
+ then creates a tree from the DataFrame.
165
+
166
+ Args:
167
+ query (str): The SQL query to execute
168
+ connection: A database connection object (sqlite3.Connection,
169
+ psycopg2.connection, etc.) or a connection string
170
+ leaf_behavior (str or callable): How to handle duplicate leaf nodes
171
+ (passed to Yggdrasil constructor)
172
+
173
+ Returns:
174
+ Yggdrasil: A new Yggdrasil tree containing the data from the query result
175
+ """
176
+ # Handle different types of connections
177
+ if isinstance(connection, str):
178
+ # Assume it's a SQLite connection string
179
+ conn = sqlite3.connect(connection)
180
+ df = pd.read_sql_query(query, conn)
181
+ conn.close()
182
+ else:
183
+ # Assume it's an existing connection object
184
+ df = pd.read_sql_query(query, connection)
185
+
186
+ # Create a tree from the DataFrame
187
+ return cls.from_dataframe(df, leaf_behavior=leaf_behavior)
188
+
189
+ def print_tree(self, prefix="", is_root=True):
190
+ """
191
+ Print the tree structure like a directory tree with lines.
192
+
193
+ Args:
194
+ prefix (str): Prefix to use for the current line (for indentation)
195
+ is_root (bool): Whether this is the root of the tree
196
+ """
197
+ # Get all keys in the current level
198
+ keys = list(self.keys())
199
+
200
+ if is_root and not keys:
201
+ print("Empty tree")
202
+ return
203
+
204
+ # Process each key in the current level
205
+ for i, key in enumerate(keys):
206
+ is_last = i == len(keys) - 1
207
+ connector = "└── " if is_last else "├── "
208
+
209
+ # Print the current key with the appropriate connector
210
+ print(f"{prefix}{connector}{key}")
211
+
212
+ # Get the value for this key
213
+ value = self[key]
214
+
215
+ # Determine the prefix for the next level
216
+ next_prefix = prefix + (" " if is_last else "│ ")
217
+
218
+ # If the value is another Yggdrasil instance, recursively print it
219
+ if isinstance(value, Yggdrasil):
220
+ value.print_tree(prefix=next_prefix, is_root=False)
221
+ # Otherwise, print the value as a leaf node
222
+ elif value is not None:
223
+ print(f"{next_prefix}└── {value}")
@@ -0,0 +1,323 @@
1
+ Metadata-Version: 2.4
2
+ Name: cedbox
3
+ Version: 0.1.0
4
+ Summary: A Python utility package for data handling, input validation, Morse code processing, and audio generation
5
+ Author: CedBox Authors
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Cedric Sascha Wagner
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ Classifier: Programming Language :: Python :: 3
28
+ Classifier: Programming Language :: Python :: 3.10
29
+ Classifier: License :: OSI Approved :: MIT License
30
+ Classifier: Operating System :: OS Independent
31
+ Requires-Python: >=3.10
32
+ Description-Content-Type: text/markdown
33
+ License-File: LICENSE
34
+ Requires-Dist: pandas>=2.0.0
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ # CedBox
40
+
41
+ CedBox is a Python utility package that provides various tools for data handling, user input validation, Morse code processing, and audio generation.
42
+
43
+ ## Features
44
+
45
+ - **Yggdrasil**: A hierarchical tree-like data structure that extends Python's dictionary, with support for:
46
+ - Automatic node creation
47
+ - Loading data from DataFrames and SQL queries
48
+ - Tree visualization
49
+
50
+ - **Input Utilities**: Functions for handling user input with validation and type conversion:
51
+ - String input
52
+ - Integer input with validation
53
+ - Float input with validation
54
+ - Choice selection
55
+ - Boolean (yes/no) input
56
+ - File path input with validation
57
+ - Date input with validation
58
+ - Email input with validation
59
+
60
+ - **Morse Code Processing**: Tools for working with Morse code:
61
+ - Convert text to Morse code
62
+ - Represent Morse code as time units
63
+ - Generate Morse code sequences
64
+
65
+ - **Audio Generation**: Utilities for creating WAV audio files:
66
+ - Generate audio signals from sequences of durations
67
+ - Create Morse code audio signals
68
+
69
+ ## Installation
70
+
71
+ ```bash
72
+ pip install cedbox
73
+ ```
74
+
75
+ ## Usage Examples
76
+
77
+ ### Yggdrasil
78
+
79
+ Yggdrasil is a hierarchical tree-like data structure that extends Python's dictionary with additional functionality.
80
+
81
+ ```python
82
+ from cedbox import Yggdrasil
83
+ import pandas as pd
84
+
85
+ # Create a new tree
86
+ tree = Yggdrasil()
87
+
88
+ # Add data with automatic node creation
89
+ tree['users']['john']['email'] = 'john@example.com'
90
+ tree['users']['john']['age'] = 30
91
+ tree['users']['jane']['email'] = 'jane@example.com'
92
+ tree['users']['jane']['age'] = 28
93
+
94
+ # Print tree structure
95
+ tree.print_tree()
96
+ ```
97
+
98
+ **Output:**
99
+ ```
100
+ Yggdrasil
101
+ ├── users
102
+ │ ├── john
103
+ │ │ ├── email: john@example.com
104
+ │ │ └── age: 30
105
+ │ └── jane
106
+ │ ├── email: jane@example.com
107
+ │ └── age: 28
108
+ ```
109
+
110
+ #### Creating from DataFrame
111
+
112
+ ```python
113
+ # Create from DataFrame
114
+ df = pd.DataFrame({
115
+ 'name': ['John', 'Jane'],
116
+ 'email': ['john@example.com', 'jane@example.com'],
117
+ 'age': [30, 28]
118
+ })
119
+ tree_from_df = Yggdrasil.from_dataframe(df)
120
+ tree_from_df.print_tree()
121
+ ```
122
+
123
+ **Output:**
124
+ ```
125
+ Yggdrasil
126
+ ├── 0
127
+ │ ├── name: John
128
+ │ ├── email: john@example.com
129
+ │ └── age: 30
130
+ └── 1
131
+ ├── name: Jane
132
+ ├── email: jane@example.com
133
+ └── age: 28
134
+ ```
135
+
136
+ #### Creating from SQL Query
137
+
138
+ ```python
139
+ import sqlite3
140
+
141
+ # Create a sample database
142
+ conn = sqlite3.connect(':memory:')
143
+ cursor = conn.cursor()
144
+ cursor.execute('CREATE TABLE users (name TEXT, email TEXT, age INTEGER)')
145
+ cursor.execute('INSERT INTO users VALUES ("John", "john@example.com", 30)')
146
+ cursor.execute('INSERT INTO users VALUES ("Jane", "jane@example.com", 28)')
147
+ conn.commit()
148
+
149
+ # Create tree from SQL query
150
+ tree_from_sql = Yggdrasil.from_sql("SELECT * FROM users", conn)
151
+ tree_from_sql.print_tree()
152
+ ```
153
+
154
+ **Output:**
155
+ ```
156
+ Yggdrasil
157
+ ├── 0
158
+ │ ├── name: John
159
+ │ ├── email: john@example.com
160
+ │ └── age: 30
161
+ └── 1
162
+ ├── name: Jane
163
+ ├── email: jane@example.com
164
+ └── age: 28
165
+ ```
166
+
167
+ ### Input Utilities
168
+
169
+ CedBox provides various functions for handling user input with validation and type conversion.
170
+
171
+ ```python
172
+ from cedbox.inputs import (
173
+ string_put, int_put, float_put, choice_put,
174
+ bool_put, file_put, date_put, mail_put
175
+ )
176
+
177
+ # String input
178
+ name = string_put("Enter your name: ")
179
+ # User enters: John
180
+ print(f"Hello, {name}!")
181
+ # Output: Hello, John!
182
+
183
+ # Integer input with validation
184
+ age = int_put(
185
+ "Enter your age: ",
186
+ conditions=[lambda x: x > 0, lambda x: x < 120],
187
+ max_times=3,
188
+ default=30
189
+ )
190
+ # User enters: -5
191
+ # Output: -5 is not valid assert lambda x: x > 0
192
+ # User enters: 25
193
+ print(f"You are {age} years old.")
194
+ # Output: You are 25 years old.
195
+
196
+ # Float input with validation
197
+ height = float_put(
198
+ "Enter your height in meters: ",
199
+ conditions=[lambda x: 0.5 < x < 2.5],
200
+ default=1.75
201
+ )
202
+ # User enters: 1.85
203
+ print(f"Your height is {height} meters.")
204
+ # Output: Your height is 1.85 meters.
205
+
206
+ # Choice selection
207
+ color = choice_put("Select a color ", choices=['red', 'green', 'blue'])
208
+ # Output: Select a color (red/green/blue):
209
+ # User enters: yellow
210
+ # Output: yellow is not valid
211
+ # User enters: red
212
+ print(f"You selected {color}.")
213
+ # Output: You selected red.
214
+
215
+ # Boolean input
216
+ confirm = bool_put("Confirm? ")
217
+ # Output: Confirm? (y/n):
218
+ # User enters: y
219
+ print(f"Confirmed: {confirm}")
220
+ # Output: Confirmed: True
221
+ ```
222
+
223
+ ### Morse Code Processing
224
+
225
+ The EasyMorse class provides tools for working with Morse code.
226
+
227
+ ```python
228
+ from cedbox import EasyMorse, MORSE_CODE_DICT
229
+
230
+ # Create Morse code from text
231
+ morse = EasyMorse("SOS")
232
+ print(f"Original text: {morse.raw_text}")
233
+ # Output: Original text: SOS
234
+
235
+ print(f"Morse code: {morse.morse_code}")
236
+ # Output: Morse code: ._._.____-_-_-___._._._
237
+
238
+ # View the time sequence (1=dot, 3=dash, -3=pause between symbols)
239
+ print(f"Time sequence: {morse.morse_seq}")
240
+ # Output: Time sequence: [1, 1, 1, -3, 3, 3, 3, -3, 1, 1, 1]
241
+
242
+ # Create with custom prefix (VVV- is a common Morse prefix)
243
+ morse_with_prefix = EasyMorse("HELLO", prefix=True)
244
+ print(f"With prefix: {morse_with_prefix.morse_message}")
245
+ # Output: With prefix: ._._._-___._._._-___._._._-___-_._._._._-_____._._._.___._____._____._._..___._..___._..___---
246
+ ```
247
+
248
+ ### Audio Generation
249
+
250
+ The EasyWav class allows you to create WAV audio files from sequences of durations.
251
+
252
+ ```python
253
+ from cedbox import EasyMorse, EasyWav
254
+
255
+ # Create Morse code sequence for "SOS"
256
+ morse = EasyMorse("SOS")
257
+ sequence = morse.morse_seq
258
+ print(f"Morse sequence: {sequence}")
259
+ # Output: Morse sequence: [1, 1, 1, -3, 3, 3, 3, -3, 1, 1, 1]
260
+
261
+ # Generate WAV file from sequence
262
+ wav = EasyWav(
263
+ sequence=sequence,
264
+ frequency=800, # 800 Hz tone
265
+ time_unit=100 # 100ms per unit
266
+ )
267
+ wav.save('morse_sos.wav')
268
+ print("Audio file created: morse_sos.wav")
269
+ # Output: Audio file created: morse_sos.wav
270
+
271
+ # Create a more complex audio pattern
272
+ custom_sequence = [1, -1, 3, -1, 1, -3, 5, -5] # Custom pattern of tones and pauses
273
+ wav = EasyWav(
274
+ sequence=custom_sequence,
275
+ frequency=440, # A4 note
276
+ sample_rate=48000 # Higher quality
277
+ )
278
+ wav.save('custom_pattern.wav')
279
+ ```
280
+
281
+ ## Complete Example: Morse Code Converter
282
+
283
+ Here's a complete example that combines multiple components to create a Morse code converter:
284
+
285
+ ```python
286
+ from cedbox import EasyMorse, EasyWav, string_put
287
+
288
+ def morse_converter():
289
+ # Get input text from user
290
+ text = string_put("Enter text to convert to Morse code: ")
291
+
292
+ # Convert to Morse code
293
+ morse = EasyMorse(text, prefix=True)
294
+
295
+ print(f"Text: {morse.raw_text}")
296
+ print(f"Morse code: {morse.morse_code}")
297
+
298
+ # Generate audio file
299
+ wav = EasyWav(
300
+ sequence=morse.morse_seq,
301
+ frequency=800,
302
+ time_unit=80 # Faster speed
303
+ )
304
+
305
+ filename = f"morse_{text.replace(' ', '_')}.wav"
306
+ wav.save(filename)
307
+ print(f"Audio saved to {filename}")
308
+
309
+ return morse, filename
310
+
311
+ # Run the converter
312
+ morse_result, audio_file = morse_converter()
313
+ ```
314
+
315
+ ## Dependencies
316
+
317
+ - pandas >= 2.0.0
318
+ - setuptools == 80.9.0
319
+ - pytest (for testing)
320
+
321
+ ## License
322
+
323
+ See the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,10 @@
1
+ cedbox/__init__.py,sha256=SO6IsIVwnWzy0Jfd7YKUmFZ7JjH-wlqP9vw5HyNJRAs,400
2
+ cedbox/easymorse.py,sha256=s7L-T-IjCVVNvehusQPJemdoLdLX5XzQyV4AQsz2Tig,2597
3
+ cedbox/easywav.py,sha256=aghh8nxuCIqmOvT57amnV_xBSkC8ifDTv6NbVoNihs4,1534
4
+ cedbox/inputs.py,sha256=1e-fJn6cAEApnaygmkTwFlIyFtm0uGj9jZGNG-IVzEM,4157
5
+ cedbox/yggdrasil.py,sha256=hrIrxefewEZl_ob-NWZrsnEzGiwfmIv3tOtIzuN0Buo,9889
6
+ cedbox-0.1.0.dist-info/licenses/LICENSE,sha256=Oau7wCqSC3IKMHCKRO1vQnkFIfBQV5C8dv3_0cmssRY,1076
7
+ cedbox-0.1.0.dist-info/METADATA,sha256=70FzcHnRt_JrD1sIdEG3DGe5VL30NVY2rC1PSclIn5A,8936
8
+ cedbox-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ cedbox-0.1.0.dist-info/top_level.txt,sha256=sldRWrukvac7nL5qTyloLvUUuCQgUyCET8tXq8fKyxo,7
10
+ cedbox-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Cedric Sascha Wagner
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
+ cedbox