0
# Unix Pipes and Utilities
1
2
Unix pipe detection, path expansion utilities, collection testing, and directory creation helpers. This module provides essential utility functions for file system operations, data type checking, and string manipulation commonly needed in CLI applications.
3
4
## Capabilities
5
6
### Unix Pipe Detection
7
8
Detect and read data piped to the application via stdin, enabling CLI tools to work in Unix pipe chains.
9
10
```python { .api }
11
def piped_in():
12
"""
13
Detect and return data piped to the application via stdin.
14
15
Returns:
16
str or None: Content from stdin if data is piped, None otherwise
17
18
Usage:
19
Enables CLI applications to work in Unix pipe chains like:
20
cat file.txt | python app.py
21
echo "data" | python app.py
22
"""
23
```
24
25
**Usage Examples:**
26
27
```python
28
from clint.pipes import piped_in
29
from clint.textui import puts
30
31
# Check for piped input
32
input_data = piped_in()
33
if input_data:
34
puts(f"Received piped data: {len(input_data)} characters")
35
# Process the piped data
36
for line in input_data.splitlines():
37
puts(f"Processing: {line}")
38
else:
39
puts("No piped input detected")
40
41
# CLI tool that works with or without pipes
42
def main():
43
piped_data = piped_in()
44
if piped_data:
45
# Process piped data
46
process_input(piped_data)
47
else:
48
# Interactive mode or file processing
49
filename = input("Enter filename: ")
50
with open(filename) as f:
51
process_input(f.read())
52
```
53
54
### Path Expansion
55
56
Expand user paths, environment variables, directories, and glob patterns into complete file lists.
57
58
```python { .api }
59
def expand_path(path):
60
"""
61
Expand directories and globs in given path to list of files.
62
63
Args:
64
path (str): Path with potential user shortcuts, env vars, directories, or globs
65
66
Returns:
67
list: List of expanded file paths matching the input pattern
68
69
Expansion includes:
70
- User home directory (~)
71
- Environment variables ($HOME, etc.)
72
- Directory traversal (returns all files in directory)
73
- Glob patterns (*.txt, **/*.py, etc.)
74
"""
75
```
76
77
**Usage Examples:**
78
79
```python
80
from clint.utils import expand_path
81
82
# Expand user home directory
83
files = expand_path('~/Documents/*.txt')
84
# Returns: ['/home/user/Documents/file1.txt', '/home/user/Documents/file2.txt']
85
86
# Expand environment variables
87
files = expand_path('$HOME/projects/**/*.py')
88
# Returns all Python files in projects directory and subdirectories
89
90
# Expand directory to all contained files
91
files = expand_path('/etc/nginx/')
92
# Returns: ['/etc/nginx/nginx.conf', '/etc/nginx/sites-available/default', ...]
93
94
# Glob patterns
95
files = expand_path('/var/log/*.log')
96
# Returns: ['/var/log/system.log', '/var/log/app.log', ...]
97
98
# Use in CLI applications
99
import sys
100
from clint.arguments import Args
101
102
args = Args()
103
for pattern in args.all:
104
for filepath in expand_path(pattern):
105
print(f"Processing: {filepath}")
106
# Process each file
107
```
108
109
### Collection Testing
110
111
Test whether an object is a collection type, excluding strings which are treated as atomic values.
112
113
```python { .api }
114
def is_collection(obj):
115
"""
116
Test if an object is a collection. Strings don't count as collections.
117
118
Args:
119
obj: Any Python object to test
120
121
Returns:
122
bool: True if object is a collection (list, tuple, set, etc.) but not a string
123
124
Note:
125
Strings are explicitly excluded because they are iterable but typically
126
treated as atomic values in CLI contexts rather than collections.
127
"""
128
```
129
130
**Usage Examples:**
131
132
```python
133
from clint.utils import is_collection
134
135
# Test various types
136
print(is_collection([1, 2, 3])) # True - list
137
print(is_collection((1, 2, 3))) # True - tuple
138
print(is_collection({1, 2, 3})) # True - set
139
print(is_collection({'a': 1, 'b': 2})) # True - dict
140
print(is_collection("hello")) # False - string
141
print(is_collection(42)) # False - int
142
print(is_collection(range(5))) # True - range object
143
144
# Use in argument processing
145
def process_args(*args):
146
for arg in args:
147
if is_collection(arg):
148
# Handle collections (lists, tuples, etc.)
149
for item in arg:
150
process_item(item)
151
else:
152
# Handle atomic values (strings, numbers, etc.)
153
process_item(arg)
154
155
# CLI application usage
156
from clint.arguments import Args
157
args = Args()
158
159
for arg_value in args.all:
160
if is_collection(arg_value):
161
print(f"Collection with {len(arg_value)} items")
162
else:
163
print(f"Single value: {arg_value}")
164
```
165
166
### Directory Creation
167
168
Create directories recursively, similar to the Unix `mkdir -p` command.
169
170
```python { .api }
171
def mkdir_p(path):
172
"""
173
Create directory and any necessary parent directories (like 'mkdir -p').
174
175
Args:
176
path (str): Directory path to create
177
178
Side Effects:
179
Creates the directory and all parent directories as needed
180
Does nothing if directory already exists (no error)
181
182
Raises:
183
OSError: If directory creation fails for reasons other than already existing
184
"""
185
```
186
187
**Usage Examples:**
188
189
```python
190
from clint.utils import mkdir_p
191
192
# Create nested directories
193
mkdir_p('/tmp/my-app/data/logs') # Creates all intermediate directories
194
195
# Safe to call on existing directories
196
mkdir_p('/tmp/my-app/data/logs') # No error, does nothing
197
198
# Use in application setup
199
import os
200
from clint import resources
201
202
def setup_app_directories():
203
app_base = os.path.expanduser('~/.myapp')
204
mkdir_p(os.path.join(app_base, 'config'))
205
mkdir_p(os.path.join(app_base, 'cache'))
206
mkdir_p(os.path.join(app_base, 'logs'))
207
mkdir_p(os.path.join(app_base, 'plugins'))
208
209
# Error handling
210
try:
211
mkdir_p('/root/protected/directory')
212
except OSError as e:
213
print(f"Failed to create directory: {e}")
214
```
215
216
### String Manipulation
217
218
Advanced string splitting and chunking utilities for text processing.
219
220
```python { .api }
221
def tsplit(string, delimiters):
222
"""
223
Split string using multiple delimiters (like str.split but with multiple delimiters).
224
225
Args:
226
string (str): String to split
227
delimiters (tuple or list): Multiple delimiter characters/strings
228
229
Returns:
230
list: List of string parts split by any of the delimiters
231
"""
232
233
def schunk(string, size):
234
"""
235
Split string into fixed-size chunks.
236
237
Args:
238
string (str): String to split into chunks
239
size (int): Size of each chunk
240
241
Returns:
242
list: List of string chunks, each of specified size (last may be shorter)
243
"""
244
```
245
246
**Usage Examples:**
247
248
```python
249
from clint.utils import tsplit, schunk
250
251
# Split by multiple delimiters
252
text = "apple,banana;orange:grape"
253
parts = tsplit(text, (',', ';', ':'))
254
# Returns: ['apple', 'banana', 'orange', 'grape']
255
256
# Parse CSV-like data with multiple separators
257
data = "name=John|age=25;city=NYC,country=USA"
258
fields = tsplit(data, ('|', ';', ','))
259
# Returns: ['name=John', 'age=25', 'city=NYC', 'country=USA']
260
261
# Process log entries with various separators
262
log_line = "2023-01-01 12:00:00 ERROR: Database connection failed"
263
parts = tsplit(log_line, (' ', ':', '-'))
264
# Returns: ['2023', '01', '01', '12', '00', '00', 'ERROR', '', 'Database', 'connection', 'failed']
265
266
# Chunk large strings for processing
267
large_text = "A" * 1000
268
chunks = schunk(large_text, 100)
269
# Returns: ['A' * 100, 'A' * 100, ..., remaining A's]
270
271
# Process data in fixed-size blocks
272
def process_in_chunks(data, chunk_size=1024):
273
chunks = schunk(data, chunk_size)
274
for i, chunk in enumerate(chunks):
275
print(f"Processing chunk {i+1}/{len(chunks)}: {len(chunk)} characters")
276
# Process each chunk
277
278
# Format data for display
279
def format_hex_dump(data, width=16):
280
hex_chars = data.encode('hex') if hasattr(data, 'encode') else data.hex()
281
chunks = schunk(hex_chars, width * 2) # 2 hex chars per byte
282
for i, chunk in enumerate(chunks):
283
offset = i * width
284
formatted_hex = ' '.join(schunk(chunk, 2))
285
print(f"{offset:08x}: {formatted_hex}")
286
```
287
288
## Complete Usage Examples
289
290
### CLI Tool with Pipe Support
291
292
```python
293
#!/usr/bin/env python
294
from clint.pipes import piped_in
295
from clint.utils import expand_path, is_collection
296
from clint.arguments import Args
297
from clint.textui import puts, colored
298
299
def main():
300
args = Args()
301
302
# Check for piped input first
303
piped_data = piped_in()
304
if piped_data:
305
puts(colored.green("Processing piped data..."))
306
process_text(piped_data)
307
return
308
309
# Process file arguments
310
if args.files:
311
for file_pattern in args.files:
312
file_paths = expand_path(file_pattern)
313
for file_path in file_paths:
314
puts(f"Processing: {file_path}")
315
with open(file_path) as f:
316
process_text(f.read())
317
else:
318
puts(colored.red("No input provided. Use: program < file.txt or program *.txt"))
319
320
def process_text(text):
321
# Process the text data
322
lines = text.splitlines()
323
puts(f"Processing {len(lines)} lines of text")
324
325
if __name__ == '__main__':
326
main()
327
```
328
329
### Application Setup Utility
330
331
```python
332
from clint.utils import mkdir_p, expand_path, is_collection
333
from clint import resources
334
import os
335
336
def setup_application(app_name, config_files=None):
337
"""Set up application directories and copy configuration files."""
338
339
# Initialize application directories
340
resources.init("MyCompany", app_name)
341
342
# Create additional subdirectories
343
mkdir_p(resources.user.path + "/plugins")
344
mkdir_p(resources.user.path + "/themes")
345
mkdir_p(resources.cache.path + "/downloads")
346
347
# Copy configuration files if provided
348
if config_files:
349
if not is_collection(config_files):
350
config_files = [config_files]
351
352
for config_pattern in config_files:
353
config_paths = expand_path(config_pattern)
354
for config_path in config_paths:
355
filename = os.path.basename(config_path)
356
with open(config_path) as f:
357
resources.user.write(f"config/{filename}", f.read())
358
print(f"Copied config: {filename}")
359
360
# Usage
361
setup_application("MyApp", ["~/.myapp-configs/*.ini", "/etc/myapp/defaults/*"])
362
```