pyboomi-cli 0.2.5__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.
@@ -0,0 +1,55 @@
1
+ # PyBoomi CLI - Main Package
2
+ #
3
+ # Copyright 2025 Robert Little
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+ # Development Notes:
18
+ # Contents of this file were produced with the help of code generation tools
19
+ # and subsequently reviewed and edited by the author. While some code was
20
+ # created with AI assistance, manual adjustments have been made to ensure
21
+ # correctness, readability, functionality, and compliance with coding
22
+ # standards. Any future modifications should preserve these manual changes.
23
+ #
24
+ # Author: Robert Little
25
+ # Created: 2025-07-27
26
+
27
+ """
28
+ PyBoomi CLI - Command-line interface for the Boomi Platform API.
29
+
30
+ This package provides a comprehensive command-line tool for interacting with
31
+ the Boomi Platform API. It enables developers and system administrators to
32
+ manage Boomi integrations, processes, folders, and other platform resources
33
+ directly from the terminal.
34
+ """
35
+
36
+ from importlib.metadata import version, PackageNotFoundError
37
+
38
+ try:
39
+ __version__ = version("pyboomi-cli")
40
+ except PackageNotFoundError:
41
+ __version__ = "0.0.0.dev0"
42
+
43
+ __author__ = "Robert Little"
44
+ __copyright__ = "Copyright 2025, Robert Little"
45
+ __license__ = "Apache 2.0"
46
+
47
+ # Import main CLI function for easy access
48
+ from .cli import main
49
+
50
+ __all__ = [
51
+ "__version__",
52
+ "__author__",
53
+ "__license__",
54
+ "main",
55
+ ]
pyboomi_cli/cache.py ADDED
@@ -0,0 +1,111 @@
1
+ # PyBoomi CLI - Cache Management
2
+ #
3
+ # Copyright 2025 Robert Little
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ """Cache management for PyBoomi CLI components."""
18
+
19
+ import hashlib
20
+ import os
21
+ import xml.etree.ElementTree as ET
22
+ from pathlib import Path
23
+ from typing import Optional, Tuple
24
+
25
+
26
+ def get_cache_dir() -> Path:
27
+ """Get the cache directory path, cross-platform compatible."""
28
+ from pyboomi_cli.config import get_config
29
+
30
+ config = get_config()
31
+
32
+ if config and "cache_dir" in config:
33
+ return Path(config["cache_dir"]).expanduser()
34
+
35
+ # Default to .boomicache in user home directory
36
+ home = Path.home()
37
+ return home / ".boomicache"
38
+
39
+
40
+ def get_cache_key(component_id: str, branch_id: str, version: str) -> str:
41
+ """Generate a cache key for a component. Requires both branch_id and version."""
42
+ key_string = f"{component_id}|branch:{branch_id}|version:{version}"
43
+ return hashlib.sha256(key_string.encode()).hexdigest()
44
+
45
+
46
+ def extract_component_info(component_xml: str) -> Tuple[Optional[str], Optional[str]]:
47
+ """Extract branch ID and version from component XML."""
48
+ try:
49
+ root = ET.fromstring(component_xml)
50
+ # Look for common attributes that contain branch and version info
51
+ branch_id = root.get("branchId") or root.get("branch-id")
52
+ version = root.get("version") or root.get("revision")
53
+ return branch_id, version
54
+ except Exception:
55
+ return None, None
56
+
57
+
58
+ def get_cached_component(
59
+ component_id: str, branch_id: Optional[str] = None, version: Optional[str] = None
60
+ ) -> Optional[str]:
61
+ """
62
+ Retrieve a component from cache if it exists.
63
+
64
+ Only works with specific branch_id and version.
65
+ """
66
+ if not branch_id or not version:
67
+ return None # Don't cache without specific branch and version
68
+
69
+ try:
70
+ cache_dir = get_cache_dir()
71
+ if not cache_dir.exists():
72
+ return None
73
+
74
+ cache_key = get_cache_key(component_id, branch_id, version)
75
+ cache_file = cache_dir / f"{cache_key}.xml"
76
+
77
+ if cache_file.exists():
78
+ return cache_file.read_text(encoding="utf-8")
79
+ except Exception:
80
+ pass
81
+
82
+ return None
83
+
84
+
85
+ def cache_component(
86
+ component_data: str,
87
+ component_id: str,
88
+ branch_id: Optional[str] = None,
89
+ version: Optional[str] = None,
90
+ ) -> None:
91
+ """Store a component in the cache. Only caches with specific branch_id and version."""
92
+ # If branch_id or version not provided, try to extract from component XML
93
+ if not branch_id or not version:
94
+ extracted_branch, extracted_version = extract_component_info(component_data)
95
+ branch_id = branch_id or extracted_branch
96
+ version = version or extracted_version
97
+
98
+ # Only cache if we have both branch and version
99
+ if not branch_id or not version:
100
+ return
101
+
102
+ try:
103
+ cache_dir = get_cache_dir()
104
+ cache_dir.mkdir(parents=True, exist_ok=True)
105
+
106
+ cache_key = get_cache_key(component_id, branch_id, version)
107
+ cache_file = cache_dir / f"{cache_key}.xml"
108
+
109
+ cache_file.write_text(component_data, encoding="utf-8")
110
+ except Exception:
111
+ pass # Silently fail if caching doesn't work
pyboomi_cli/cli.py ADDED
@@ -0,0 +1,59 @@
1
+ # PyBoomi CLI - CLI Main
2
+ #
3
+ # Copyright 2025 Robert Little
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+ # Development Notes:
18
+ # Contents of this file were produced with the help of code generation tools
19
+ # and subsequently reviewed and edited by the author. While some code was
20
+ # created with AI assistance, manual adjustments have been made to ensure
21
+ # correctness, readability, functionality, and compliance with coding
22
+ # standards. Any future modifications should preserve these manual changes.
23
+ #
24
+ # Author: Robert Little
25
+ # Created: 2025-12-12
26
+
27
+ """CLI main module for PyBoomi CLI tool."""
28
+
29
+ __author__ = "Robert Little"
30
+ __copyright__ = "Copyright 2025, Robert Little"
31
+ __license__ = "Apache 2.0"
32
+ __version__ = "0.1.0"
33
+
34
+ import click
35
+
36
+ from pyboomi_cli.commands.branch import command as branch_command
37
+ from pyboomi_cli.commands.component import command as component_command
38
+ from pyboomi_cli.commands.environment import command as environment_command
39
+ from pyboomi_cli.commands.folders import command as folders_command
40
+ from pyboomi_cli.commands.package import command as package_command
41
+ from pyboomi_cli.commands.processes import command as processes_command
42
+
43
+
44
+ @click.group()
45
+ def main():
46
+ """PyBoomi CLI — manage Boomi Platform API from the terminal."""
47
+ pass
48
+
49
+
50
+ main.add_command(folders_command)
51
+ # Plural alias for backward compatibility
52
+ main.add_command(folders_command, name="folders")
53
+ main.add_command(processes_command, name="processes")
54
+ main.add_command(processes_command, name="process")
55
+ main.add_command(component_command)
56
+ main.add_command(package_command)
57
+ main.add_command(branch_command)
58
+ main.add_command(environment_command)
59
+ main.add_command(environment_command, name="environments")
pyboomi_cli/config.py ADDED
@@ -0,0 +1,48 @@
1
+ # PyBoomi CLI - Configuration Management
2
+ #
3
+ # Copyright 2025 Robert Little
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ """Configuration management for PyBoomi CLI."""
18
+
19
+ import json
20
+ import os
21
+ from pathlib import Path
22
+ from typing import Any, Dict, Optional
23
+
24
+
25
+ def get_config_file_path() -> Path:
26
+ """Get the configuration file path."""
27
+ # Check for config file in current directory first
28
+ local_config = Path(".pyboomi.json")
29
+ if local_config.exists():
30
+ return local_config
31
+
32
+ # Check in user home directory
33
+ home_config = Path.home() / ".pyboomi.json"
34
+ return home_config
35
+
36
+
37
+ def get_config() -> Optional[Dict[str, Any]]:
38
+ """Load configuration from file if it exists."""
39
+ config_file = get_config_file_path()
40
+
41
+ if not config_file.exists():
42
+ return None
43
+
44
+ try:
45
+ with open(config_file, "r", encoding="utf-8") as f:
46
+ return json.load(f)
47
+ except Exception:
48
+ return None
pyboomi_cli/utils.py ADDED
@@ -0,0 +1,565 @@
1
+ # PyBoomi CLI - Utilities
2
+ #
3
+ # Copyright 2025 Robert Little
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+ # Development Notes:
18
+ # Contents of this file were produced with the help of code generation tools
19
+ # and subsequently reviewed and edited by the author. While some code was
20
+ # created with AI assistance, manual adjustments have been made to ensure
21
+ # correctness, readability, functionality, and compliance with coding
22
+ # standards. Any future modifications should preserve these manual changes.
23
+ #
24
+ # Author: Robert Little
25
+ # Created: 2025-01-27
26
+
27
+ """Utility functions for PyBoomi CLI operations."""
28
+
29
+ __author__ = "Robert Little"
30
+ __copyright__ = "Copyright 2025, Robert Little"
31
+ __license__ = "Apache 2.0"
32
+ __version__ = "0.1.0"
33
+
34
+ import json
35
+ import os
36
+ import re
37
+ from pathlib import Path
38
+ from typing import Dict, List, Optional, Set
39
+
40
+ import click
41
+ from pyboomi_platform.auth import BoomiAuth
42
+ from pyboomi_platform.client import BoomiPlatformClient
43
+
44
+
45
+ def _apply_accept_header(client, accept: Optional[str]) -> None:
46
+ """
47
+ Best-effort application of an Accept header on the underlying client/session.
48
+ Supports multiple possible client shapes without failing hard.
49
+ """
50
+ if not accept:
51
+ return
52
+
53
+ applied = False
54
+
55
+ # Common requests-style session
56
+ session = getattr(client, "session", None)
57
+ if session and hasattr(session, "headers"):
58
+ try:
59
+ session.headers["Accept"] = accept
60
+ applied = True
61
+ except Exception:
62
+ pass
63
+
64
+ # Some clients expose default_headers dict
65
+ default_headers = getattr(client, "default_headers", None)
66
+ if isinstance(default_headers, dict):
67
+ try:
68
+ default_headers["Accept"] = accept
69
+ applied = True
70
+ except Exception:
71
+ pass
72
+
73
+ # Fallback attribute for downstream use if the client inspects it
74
+ if not applied:
75
+ try:
76
+ setattr(client, "accept", accept)
77
+ except Exception:
78
+ pass
79
+
80
+
81
+ def get_client(account_id=None, username=None, api_token=None, accept: Optional[str] = None):
82
+ """
83
+ Get a configured BoomiPlatformClient with authentication.
84
+
85
+ Priority for credentials:
86
+ - BOOMI_AUTH (base64 encoded Basic Auth string)
87
+ - Function arguments (username, api_token)
88
+ - Environment variables (BOOMI_USER, BOOMI_TOKEN)
89
+
90
+ Account ID can come from function argument or BOOMI_ACCT env var.
91
+
92
+ Args:
93
+ account_id: Boomi account ID (optional, can use BOOMI_ACCT env var)
94
+ username: Boomi user email (optional, can use BOOMI_USER env var)
95
+ api_token: Boomi Platform API token (optional, can use BOOMI_TOKEN env var)
96
+
97
+ Returns:
98
+ BoomiPlatformClient: Configured client instance with optional Accept header
99
+
100
+ Raises:
101
+ click.UsageError: If required credentials are missing
102
+ """
103
+ account_id = account_id or os.getenv("BOOMI_ACCT")
104
+ if not account_id:
105
+ raise click.UsageError("Boomi account ID must be provided via --account-id or BOOMI_ACCT environment variable.")
106
+
107
+ boomi_auth_header = os.getenv("BOOMI_AUTH")
108
+
109
+ if boomi_auth_header:
110
+ # BOOMI_AUTH is base64 encoded "BOOMI_TOKEN.email:uuid"
111
+ # Extract username and password for client initialization
112
+ try:
113
+ import base64
114
+
115
+ decoded = base64.b64decode(boomi_auth_header).decode("utf-8")
116
+ if ":" in decoded:
117
+ # Format is "BOOMI_TOKEN.email:uuid"
118
+ client_username, client_api_token = decoded.split(":", 1)
119
+ else:
120
+ # Fallback if no colon found
121
+ client_username = decoded
122
+ client_api_token = "token"
123
+ except Exception:
124
+ # If decoding fails, use placeholders
125
+ client_username = "user"
126
+ client_api_token = "token"
127
+
128
+ # Construct a fake BoomiAuth wrapper that returns the existing auth header
129
+ # The header is already base64 encoded, so we use it directly
130
+ class StaticBoomiAuth:
131
+ """Wrapper for pre-encoded Basic Auth header."""
132
+
133
+ def get_auth_header(self):
134
+ """Return pre-encoded authorization header."""
135
+ return {"Authorization": f"Basic {boomi_auth_header}"}
136
+
137
+ auth = StaticBoomiAuth()
138
+ else:
139
+ username = username or os.getenv("BOOMI_USER")
140
+ api_token = api_token or os.getenv("BOOMI_TOKEN")
141
+
142
+ if not username or not api_token:
143
+ raise click.UsageError(
144
+ "You must provide credentials using either:\n"
145
+ "- BOOMI_AUTH (base64 encoded Basic Auth string), or\n"
146
+ "- --username and --api-token (or BOOMI_USER and BOOMI_TOKEN env vars)"
147
+ )
148
+ auth = BoomiAuth(username, api_token)
149
+ client_username = username
150
+ client_api_token = api_token
151
+
152
+ # Inject the custom auth into the client
153
+ client = BoomiPlatformClient(account_id, username=client_username, api_token=client_api_token, base_url=None)
154
+ client.auth = auth
155
+
156
+ # Apply Accept header if requested (e.g., force XML responses)
157
+ _apply_accept_header(client, accept)
158
+
159
+ return client
160
+
161
+
162
+ def sanitize_filename(name: str) -> str:
163
+ """
164
+ Sanitize a filename by removing filesystem-unsafe characters.
165
+
166
+ Removes or replaces characters that are not safe for filesystem use:
167
+ /, \\, :, *, ?, ", <, >, |
168
+
169
+ Args:
170
+ name: Original filename string
171
+
172
+ Returns:
173
+ str: Sanitized filename safe for filesystem use
174
+ """
175
+ if not name:
176
+ return "unknown"
177
+
178
+ # Remove filesystem-unsafe characters
179
+ unsafe_chars = r'[/\\:*?"<>|]'
180
+ sanitized = re.sub(unsafe_chars, "", name)
181
+
182
+ # Replace spaces with underscores for better filesystem compatibility
183
+ sanitized = sanitized.replace(" ", "_")
184
+
185
+ # Remove leading/trailing dots and spaces (Windows issue)
186
+ sanitized = sanitized.strip(". ")
187
+
188
+ # If empty after sanitization, return a default
189
+ if not sanitized:
190
+ return "unknown"
191
+
192
+ return sanitized
193
+
194
+
195
+ def format_output(data, output_format="json"):
196
+ """
197
+ Format data for output in the specified format.
198
+
199
+ Args:
200
+ data: Data to format (dict, list, etc.)
201
+ output_format: Output format ('json', 'yaml', or 'text')
202
+
203
+ Returns:
204
+ str: Formatted output string
205
+ """
206
+ if output_format == "json":
207
+ return json.dumps(data, indent=2)
208
+ elif output_format == "yaml":
209
+ try:
210
+ import yaml
211
+
212
+ return yaml.dump(data, default_flow_style=False, sort_keys=False)
213
+ except ImportError:
214
+ click.echo("Warning: PyYAML not installed. Falling back to JSON format.", err=True)
215
+ return json.dumps(data, indent=2)
216
+ elif output_format == "text":
217
+ # Simple text formatting for common component fields
218
+ if isinstance(data, dict):
219
+ lines = []
220
+ for key, value in data.items():
221
+ if isinstance(value, (dict, list)):
222
+ lines.append(f"{key}:")
223
+ lines.append(json.dumps(value, indent=2))
224
+ else:
225
+ lines.append(f"{key}: {value}")
226
+ return "\n".join(lines)
227
+ else:
228
+ return str(data)
229
+ elif output_format == "xml":
230
+ # For XML we expect a string or bytes payload; pass through as text
231
+ if isinstance(data, bytes):
232
+ return data.decode("utf-8", errors="replace")
233
+ return str(data)
234
+ else:
235
+ # Default to JSON
236
+ return json.dumps(data, indent=2)
237
+
238
+
239
+ def get_templates_directory() -> Path:
240
+ """
241
+ Get the templates directory path.
242
+
243
+ Checks in order:
244
+ 1. Environment variable PYBOOMI_TEMPLATES_DIR
245
+ 2. Current working directory 'templates/'
246
+ 3. User home directory '~/.pyboomi-cli/templates/'
247
+
248
+ Returns:
249
+ Path: Path to templates directory
250
+ """
251
+ # Check environment variable first
252
+ env_path = os.getenv("PYBOOMI_TEMPLATES_DIR")
253
+ if env_path:
254
+ return Path(env_path).expanduser()
255
+
256
+ # Check current working directory
257
+ cwd_templates = Path.cwd() / "templates"
258
+ if cwd_templates.exists():
259
+ return cwd_templates
260
+
261
+ # Default to current working directory (will be created if needed)
262
+ return Path.cwd() / "templates"
263
+
264
+
265
+ def find_template_file(template_path: str, component_type: Optional[str] = None) -> Path:
266
+ """
267
+ Find a template file by path.
268
+
269
+ The template_path can be:
270
+ - A relative path from templates directory (e.g., "processes/basic.xml")
271
+ - An absolute path
272
+ - A filename that will be searched in component_type subdirectory
273
+
274
+ Args:
275
+ template_path: Path to template file
276
+ component_type: Optional component type subdirectory (e.g., "processes")
277
+
278
+ Returns:
279
+ Path: Path to template file
280
+
281
+ Raises:
282
+ click.UsageError: If template file is not found
283
+ """
284
+ template_path_obj = Path(template_path)
285
+
286
+ # If it's an absolute path and exists, use it
287
+ if template_path_obj.is_absolute() and template_path_obj.exists():
288
+ return template_path_obj
289
+
290
+ # Get templates directory
291
+ templates_dir = get_templates_directory()
292
+
293
+ # If template_path contains a slash, treat as relative to templates_dir
294
+ if "/" in template_path or "\\" in template_path:
295
+ full_path = templates_dir / template_path
296
+ if full_path.exists():
297
+ return full_path
298
+
299
+ # Otherwise, search in component_type subdirectory
300
+ if component_type:
301
+ full_path = templates_dir / component_type / template_path
302
+ if full_path.exists():
303
+ return full_path
304
+
305
+ # Try just in templates_dir
306
+ full_path = templates_dir / template_path
307
+ if full_path.exists():
308
+ return full_path
309
+
310
+ # Not found
311
+ raise click.UsageError(
312
+ f"Template file not found: {template_path}\n"
313
+ f"Searched in: {templates_dir}" + (f"/{component_type}" if component_type else "")
314
+ )
315
+
316
+
317
+ def scan_template_parameters(template_content: str) -> Dict[str, Optional[str]]:
318
+ """
319
+ Scan template content for all unique template parameters.
320
+
321
+ Template parameters use {{parameterName}} or {{parameterName:default value}} syntax.
322
+ Default values can span multiple lines.
323
+
324
+ Args:
325
+ template_content: Template file content as string
326
+
327
+ Returns:
328
+ Dict[str, Optional[str]]: Dictionary mapping parameter names to their default values
329
+ (None if no default provided)
330
+ """
331
+ parameters = {}
332
+
333
+ # Pattern to match {{parameterName:default value}} (with default, supports multi-line)
334
+ # Uses non-greedy matching to stop at the first }} (closing braces)
335
+ # The (?s) flag makes . match newlines
336
+ pattern_with_default = r"\{\{(\w+):(.*?)\}\}"
337
+ matches_with_default = re.findall(pattern_with_default, template_content, re.DOTALL)
338
+ for param_name, default_value in matches_with_default:
339
+ # Strip leading/trailing whitespace but preserve internal structure
340
+ parameters[param_name] = default_value.strip()
341
+
342
+ # Pattern to match {{parameterName}} (without default)
343
+ # But exclude those that were already matched with defaults
344
+ pattern_no_default = r"\{\{(\w+)\}\}"
345
+ matches_no_default = re.findall(pattern_no_default, template_content)
346
+ for param_name in matches_no_default:
347
+ # Only add if not already found with a default value
348
+ if param_name not in parameters:
349
+ parameters[param_name] = None
350
+
351
+ return parameters
352
+
353
+
354
+ def display_template_parameter_summary(
355
+ parameters: Dict[str, Optional[str]],
356
+ provided_values: Optional[Dict[str, str]] = None,
357
+ ) -> None:
358
+ """
359
+ Display a summary of template parameters to the user.
360
+
361
+ Shows all parameters, indicating which have defaults, which are already provided,
362
+ and which still need values.
363
+
364
+ Args:
365
+ parameters: Dictionary mapping parameter names to their default values (None if no default)
366
+ provided_values: Optional dictionary of already-provided parameter values
367
+ """
368
+ if not parameters:
369
+ return
370
+
371
+ if provided_values is None:
372
+ provided_values = {}
373
+
374
+ click.echo("\n" + "=" * 70)
375
+ click.echo("Template Parameter Summary")
376
+ click.echo("=" * 70)
377
+
378
+ # Separate parameters into categories
379
+ with_defaults = []
380
+ without_defaults = []
381
+ already_provided = []
382
+
383
+ for param_name in sorted(parameters.keys()):
384
+ if param_name in provided_values:
385
+ already_provided.append((param_name, provided_values[param_name]))
386
+ elif parameters[param_name] is not None:
387
+ with_defaults.append((param_name, parameters[param_name]))
388
+ else:
389
+ without_defaults.append(param_name)
390
+
391
+ # Display already provided parameters
392
+ if already_provided:
393
+ click.echo("\n✓ Already Provided:")
394
+ for param_name, value in already_provided:
395
+ # Truncate long values for display
396
+ display_value = value if len(value) <= 60 else value[:57] + "..."
397
+ if "\n" in display_value:
398
+ display_value = display_value.split("\n")[0] + " ... (multi-line)"
399
+ click.echo(f" • {param_name}: {display_value}")
400
+
401
+ # Display parameters with defaults
402
+ if with_defaults:
403
+ click.echo("\nParameters with Default Values:")
404
+ for param_name, default_value in with_defaults:
405
+ # Truncate long defaults for display
406
+ display_default = default_value if len(default_value) <= 60 else default_value[:57] + "..."
407
+ if "\n" in display_default:
408
+ display_default = display_default.split("\n")[0] + " ... (multi-line)"
409
+ click.echo(f" • {param_name}")
410
+ click.echo(f" Default: {display_default}")
411
+
412
+ # Display parameters without defaults
413
+ if without_defaults:
414
+ click.echo("\nParameters Requiring Values (no defaults):")
415
+ for param_name in without_defaults:
416
+ click.echo(f" • {param_name}")
417
+
418
+ click.echo("\n" + "=" * 70)
419
+
420
+
421
+ def prompt_for_template_parameters(
422
+ parameters: Dict[str, Optional[str]],
423
+ existing_values: Optional[Dict[str, str]] = None,
424
+ ) -> Dict[str, str]:
425
+ """
426
+ Prompt user for values for all template parameters.
427
+
428
+ Args:
429
+ parameters: Dictionary mapping parameter names to their default values (None if no default)
430
+ existing_values: Optional dict of pre-filled values (won't prompt for these)
431
+
432
+ Returns:
433
+ Dict[str, str]: Dictionary mapping parameter names to values
434
+ """
435
+ if existing_values is None:
436
+ existing_values = {}
437
+
438
+ values = {}
439
+ sorted_params = sorted(parameters.keys())
440
+
441
+ for param in sorted_params:
442
+ if param in existing_values:
443
+ values[param] = existing_values[param]
444
+ click.echo(f"{param}: {existing_values[param]} (using provided value)")
445
+ else:
446
+ # Convert parameter name to a more readable prompt
447
+ prompt_text = param.replace("_", " ").replace("-", " ").title()
448
+ default_value = parameters.get(param)
449
+
450
+ if default_value is not None:
451
+ # Use default value in prompt
452
+ value = click.prompt(
453
+ f"Enter value for {prompt_text} ({param})",
454
+ default=default_value,
455
+ type=str,
456
+ )
457
+ else:
458
+ # No default, require input
459
+ value = click.prompt(f"Enter value for {prompt_text} ({param})", type=str)
460
+ values[param] = value
461
+
462
+ return values
463
+
464
+
465
+ def replace_template_parameters(template_content: str, parameter_values: Dict[str, str]) -> str:
466
+ """
467
+ Replace template parameters with provided values.
468
+
469
+ Handles both formats:
470
+ - {{parameterName}} (no default)
471
+ - {{parameterName:default value}} (with default, supports multi-line)
472
+
473
+ Args:
474
+ template_content: Template content with {{parameterName}} or {{parameterName:default}} placeholders
475
+ parameter_values: Dictionary mapping parameter names to values
476
+
477
+ Returns:
478
+ str: Template content with parameters replaced
479
+ """
480
+ result = template_content
481
+ for param_name, param_value in parameter_values.items():
482
+ # Replace {{paramName:default}} format first (more specific, supports multi-line)
483
+ # Use non-greedy matching with DOTALL flag to handle multi-line defaults
484
+ pattern_with_default = r"\{\{" + re.escape(param_name) + r":.*?\}\}"
485
+ result = re.sub(pattern_with_default, str(param_value), result, flags=re.DOTALL)
486
+
487
+ # Then replace {{paramName}} format (no default)
488
+ pattern_no_default = r"\{\{" + re.escape(param_name) + r"\}\}"
489
+ result = re.sub(pattern_no_default, str(param_value), result)
490
+ return result
491
+
492
+
493
+ def load_and_process_template(
494
+ template_path: str,
495
+ component_type: Optional[str] = None,
496
+ parameter_values: Optional[Dict[str, str]] = None,
497
+ prompt_missing: bool = True,
498
+ ) -> str:
499
+ """
500
+ Load a template file, scan for parameters, prompt for values, and replace them.
501
+
502
+ Args:
503
+ template_path: Path to template file
504
+ component_type: Optional component type subdirectory
505
+ parameter_values: Optional pre-filled parameter values
506
+ prompt_missing: Whether to prompt for missing parameters (default: True)
507
+
508
+ Returns:
509
+ str: Processed template content with all parameters replaced
510
+ """
511
+ # Find and load template file
512
+ template_file = find_template_file(template_path, component_type)
513
+
514
+ try:
515
+ with open(template_file, "r", encoding="utf-8") as f:
516
+ template_content = f.read()
517
+ except Exception as e:
518
+ raise click.UsageError(f"Error reading template file {template_file}: {e}")
519
+
520
+ # Scan for parameters (returns dict with parameter names and default values)
521
+ parameters = scan_template_parameters(template_content)
522
+
523
+ if not parameters:
524
+ # No parameters to replace, return as-is
525
+ return template_content
526
+
527
+ # Get parameter values
528
+ if parameter_values is None:
529
+ parameter_values = {}
530
+
531
+ # Prompt for missing parameters if requested
532
+ if prompt_missing:
533
+ missing_params = {k: v for k, v in parameters.items() if k not in parameter_values}
534
+ if missing_params:
535
+ # Display summary of all parameters (including those already provided)
536
+ display_template_parameter_summary(parameters, parameter_values)
537
+
538
+ # Ask user if they want to continue or abort
539
+ click.echo("\nYou can proceed to provide values for the missing parameters,")
540
+ click.echo("or abort now to gather the necessary information first.")
541
+ if not click.confirm("\nDo you want to continue with parameter entry?", default=True):
542
+ click.echo("\nOperation cancelled. Please gather the required information and try again.")
543
+ raise click.Abort()
544
+
545
+ click.echo(f"\nTemplate contains {len(missing_params)} parameter(s) that need values:")
546
+ prompted_values = prompt_for_template_parameters(missing_params, parameter_values)
547
+ parameter_values.update(prompted_values)
548
+ else:
549
+ # If not prompting, apply default values for parameters that don't have values yet
550
+ for param_name, default_value in parameters.items():
551
+ if param_name not in parameter_values and default_value is not None:
552
+ parameter_values[param_name] = default_value
553
+
554
+ # Check that all parameters have values
555
+ missing = set(parameters.keys()) - set(parameter_values.keys())
556
+ if missing:
557
+ raise click.UsageError(
558
+ f"Missing values for template parameters: {', '.join(sorted(missing))}\n"
559
+ f"Please provide values for all parameters."
560
+ )
561
+
562
+ # Replace parameters
563
+ processed_content = replace_template_parameters(template_content, parameter_values)
564
+
565
+ return processed_content
@@ -0,0 +1,210 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyboomi-cli
3
+ Version: 0.2.5
4
+ Summary: Command-line interface for Boomi Platform API
5
+ Author-email: Robert Little <robert@iesoftwaredeveloper.com>
6
+ Maintainer-email: Robert Little <robert@iesoftwaredeveloper.com>
7
+ License-Expression: Apache-2.0
8
+ Project-URL: Homepage, https://github.com/iesoftwaredeveloper/pyboomi-cli
9
+ Project-URL: Documentation, https://github.com/iesoftwaredeveloper/pyboomi-cli#readme
10
+ Project-URL: Repository, https://github.com/iesoftwaredeveloper/pyboomi-cli.git
11
+ Project-URL: Bug Tracker, https://github.com/iesoftwaredeveloper/pyboomi-cli/issues
12
+ Project-URL: Changelog, https://github.com/iesoftwaredeveloper/pyboomi-cli/blob/main/CHANGELOG.md
13
+ Keywords: boomi,api,cli,integration,platform,etl,middleware
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: System Administrators
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.8
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Classifier: Topic :: System :: Systems Administration
27
+ Classifier: Topic :: Utilities
28
+ Classifier: Typing :: Typed
29
+ Requires-Python: >=3.8
30
+ Description-Content-Type: text/markdown
31
+ License-File: LICENSE
32
+ Requires-Dist: pyboomi_platform>=0.1.0
33
+ Requires-Dist: click>=8.1.0
34
+ Requires-Dist: rich>=13.0.0
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
37
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
38
+ Requires-Dist: black>=23.0.0; extra == "dev"
39
+ Requires-Dist: flake8>=6.0.0; extra == "dev"
40
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
41
+ Requires-Dist: isort>=5.12.0; extra == "dev"
42
+ Requires-Dist: pre-commit>=3.0.0; extra == "dev"
43
+ Requires-Dist: tox>=4.0.0; extra == "dev"
44
+ Requires-Dist: build>=0.10.0; extra == "dev"
45
+ Requires-Dist: twine>=4.0.0; extra == "dev"
46
+ Provides-Extra: test
47
+ Requires-Dist: pytest>=7.0.0; extra == "test"
48
+ Requires-Dist: pytest-cov>=4.0.0; extra == "test"
49
+ Provides-Extra: lint
50
+ Requires-Dist: black>=23.0.0; extra == "lint"
51
+ Requires-Dist: flake8>=6.0.0; extra == "lint"
52
+ Requires-Dist: mypy>=1.0.0; extra == "lint"
53
+ Requires-Dist: isort>=5.12.0; extra == "lint"
54
+ Provides-Extra: docs
55
+ Requires-Dist: sphinx>=5.0.0; extra == "docs"
56
+ Requires-Dist: sphinx-rtd-theme>=1.0.0; extra == "docs"
57
+ Requires-Dist: sphinx-autodoc-typehints>=1.19.0; extra == "docs"
58
+ Dynamic: license-file
59
+
60
+ # pyboomi-cli
61
+
62
+ Command-line interface for the Boomi Platform API.
63
+
64
+ ## Installation
65
+
66
+ ### For Development
67
+
68
+ ```bash
69
+ # Clone the repository
70
+ git clone https://github.com/iesoftwaredeveloper/pyboomi-cli
71
+ cd pyboomi-cli
72
+
73
+ # Install with development dependencies
74
+ pip install -e .[dev]
75
+ ```
76
+
77
+ ### For Usage (when published)
78
+
79
+ ```bash
80
+ pip install pyboomi-cli
81
+ ```
82
+
83
+ ### From Source
84
+
85
+ ```bash
86
+ git clone https://github.com/iesoftwaredeveloper/pyboomi-cli
87
+ cd pyboomi-cli
88
+ pip install -e .
89
+ ```
90
+
91
+ ## Usage
92
+
93
+ ```bash
94
+ # Get help (both entry points are available)
95
+ boomi --help
96
+ pyboomi --help
97
+
98
+ # Query folders (folders alias is supported)
99
+ boomi folder query --account-id YOUR_ACCOUNT_ID --username YOUR_USERNAME --api-token YOUR_API_TOKEN
100
+ # or
101
+ boomi folders query --account-id YOUR_ACCOUNT_ID --username YOUR_USERNAME --api-token YOUR_API_TOKEN
102
+
103
+ # Filter folders by name and print verbose request/response
104
+ boomi folder query --name "My Folder" --verbose
105
+
106
+ # Filter folders with parent constraints
107
+ boomi folder query --name "My Folder" --parent-id 0000-parent-id
108
+ boomi folder query --name "My Folder" --parent-name "Parent Folder"
109
+ boomi folder query --name "My Folder" --parent-path "/Root/Parent"
110
+
111
+ # Create a folder (parent can be provided by id or name/path)
112
+ boomi folder create --name "New Folder" --parent-id 0000-parent-id
113
+ boomi folder create --name "New Folder" --parent-name "Parent Folder"
114
+ boomi folder create --name "New Folder" --parent-name "Parent Folder" --parent-path "/Root/Parent"
115
+
116
+ # Using environment variables
117
+ export BOOMI_ACCT=your-account-id
118
+ export BOOMI_USER=your-username
119
+ export BOOMI_TOKEN=your-api-token
120
+ boomi folder query
121
+
122
+ # Get component XML (optionally include branch/version with ~)
123
+ boomi component get 1234-abcd
124
+ boomi component get 1234-abcd --branch-id 5678-branch --version 1.0
125
+
126
+ # Get component metadata for a branch (json|yaml|text)
127
+ boomi component metadata 1234-abcd --branch-id 5678-branch --output-format json
128
+
129
+ # Create a packaged component
130
+ boomi package create 1234-abcd --version 1.0.0 --notes "Initial package"
131
+
132
+ # Query packaged components by component ID or version (supports xml)
133
+ boomi package query --component-id 1234-abcd --package-version 1.0.0 --output-format xml
134
+
135
+ # Query branches (filter by name or parent)
136
+ boomi branch query --name "feature-x"
137
+
138
+ # Create a new branch under a parent branch
139
+ boomi branch create 1111-parent "feature-x"
140
+
141
+ # Update a branch
142
+ boomi branch update 2222-branch --name "feature-x-renamed" --ready
143
+
144
+ # Delete a branch
145
+ boomi branch delete 2222-branch
146
+ ```
147
+
148
+ ## Commands
149
+
150
+ - `folder` — Manage Boomi folders (group command, alias: `folders`)
151
+ - Subcommands:
152
+ - `folder query` — Query and list folders (`--name`, `--parent-id`, `--parent-name`, `--parent-path`, `--verbose`)
153
+ - `folder create` — Create a folder (`--name` required; `--parent-id` or `--parent-name` required, with optional `--parent-path` to disambiguate; `--output-format` `json|yaml|text`)
154
+ - Common options: `--account-id`, `--username`, `--api-token`
155
+ - `component` — Manage Boomi components (group command)
156
+ - Subcommands:
157
+ - `component get <component_id>` — Get component XML (`--branch-id`, `--version` optional; always returns XML)
158
+ - `component metadata <component_id>` — Get component metadata (`--branch-id` optional; `--output-format` `json|yaml|text`)
159
+ - Common options: `--account-id`, `--username`, `--api-token`
160
+ - `package` — Manage Boomi packages (group command)
161
+ - Subcommands:
162
+ - `package create <component_id> --version <version>` — Create packaged component (`--notes`, `--branch-name`, `--output-format` `json|yaml|text|xml`)
163
+ - `package get <packaged_component_id>` — Get packaged component (`--output-format` `json|yaml|text|xml`)
164
+ - `package query` — Query packaged components (`--component-id`, `--package-version`, `--output-format` `json|yaml|text|xml`)
165
+ - Common options: `--account-id`, `--username`, `--api-token`
166
+ - `branch` — Manage Boomi branches (group command)
167
+ - Subcommands:
168
+ - `branch get <branch_id>` — Get a branch
169
+ - `branch query` — Query branches (`--name`, `--parent-branch-id`, `--package-id`, `--ready`, `--output-format` `json|yaml|text`)
170
+ - `branch create <parent_branch_id> <branch_name>` — Create a branch (`--package-id`, `--output-format` `json|yaml|text`)
171
+ - `branch update <branch_id>` — Update a branch (`--name`, `--description`, `--ready/--not-ready`, `--output-format` `json|yaml|text`)
172
+ - `branch delete <branch_id>` — Delete a branch
173
+ - Common options: `--account-id`, `--username`, `--api-token`
174
+ - `process` — Manage Boomi processes (alias: `processes`) — placeholder
175
+
176
+ ## Environment Variables
177
+
178
+ - `BOOMI_ACCT`: Your Boomi account ID
179
+ - `BOOMI_USER`: Your Boomi username (email)
180
+ - `BOOMI_TOKEN`: Your Boomi API token
181
+ - `BOOMI_AUTH`: Base64 encoded Basic Auth string (alternative to username/token)
182
+
183
+ ## Output Formats
184
+
185
+ Many commands support `--output-format` with the following values:
186
+
187
+ - `json` (default)
188
+ - `yaml` (falls back to json if PyYAML is not installed)
189
+ - `text` (simple key/value text)
190
+ - `xml` (returns raw XML when requested; defaults to XML for `component get`)
191
+
192
+ ## Templates
193
+
194
+ The `templates/` directory contains XML templates for various Boomi component types including:
195
+
196
+ - **Processes**: Unit test harnesses and process templates
197
+ - **Connectors**: Database, API, and file connectors
198
+ - **Profiles**: JSON, XML, flat file, and database profiles
199
+ - **Maps**: Data mapping templates and functions
200
+ - **APIs**: API services and proxy configurations
201
+ - **Scripts**: Process and map scripts
202
+ - **Certificates**: Security certificate templates
203
+
204
+ These templates provide standardized starting points for creating Boomi components. See [TEMPLATE_SYSTEM.md](TEMPLATE_SYSTEM.md) for detailed information about the template system and usage.
205
+
206
+ ## Documentation
207
+
208
+ - [CHANGELOG.md](CHANGELOG.md) - Version history and release notes
209
+ - [TEMPLATE_SYSTEM.md](TEMPLATE_SYSTEM.md) - Template system documentation
210
+ - [TEMPLATES_STRUCTURE.md](TEMPLATES_STRUCTURE.md) - Template directory structure
@@ -0,0 +1,11 @@
1
+ pyboomi_cli/__init__.py,sha256=DDPpf2yYk4-AwHdhfDZPDe8Nvi2-Ca-4aNSMtPdPvKc,1824
2
+ pyboomi_cli/cache.py,sha256=-FYcR1DeRos9bF3M9yEeOLYBlDsK1_RzD89TJNJhKLw,3677
3
+ pyboomi_cli/cli.py,sha256=tmd7MS83o0EOZS0M-jfilZOj1Yg6sgZ6ZVB1FGaeECs,2218
4
+ pyboomi_cli/config.py,sha256=JxlVQr43xrNWSwr18nLigKDVQ55Ughl0z0JD1Q3uDUQ,1438
5
+ pyboomi_cli/utils.py,sha256=37u0CE2UWnBrZrcHSSJv5zrvCe5jeZ7eVVdxYHKgCvA,20286
6
+ pyboomi_cli-0.2.5.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
7
+ pyboomi_cli-0.2.5.dist-info/METADATA,sha256=zApTduaWleJDFp8bg0n7VdTaNOcINIrZlpUSGT3Eb10,8657
8
+ pyboomi_cli-0.2.5.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
9
+ pyboomi_cli-0.2.5.dist-info/entry_points.txt,sha256=qigTo4ZwZ5MVK5OfkpB75_i9_2_q36dPZsyqa-grsK0,78
10
+ pyboomi_cli-0.2.5.dist-info/top_level.txt,sha256=hUdkMJxOXraWdexC9lBVRhSOs_XeN1q1ZGdFofQizF0,12
11
+ pyboomi_cli-0.2.5.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,3 @@
1
+ [console_scripts]
2
+ boomi = pyboomi_cli.cli:main
3
+ pyboomi = pyboomi_cli.cli:main
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1 @@
1
+ pyboomi_cli