ostruct-cli 0.1.4__py3-none-any.whl → 0.2.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.
ostruct/cli/cli.py CHANGED
@@ -206,7 +206,6 @@ def _get_type_with_constraints(
206
206
  Returns:
207
207
  Tuple of (type, field)
208
208
  """
209
- field_type = field_schema.get("type")
210
209
  field_kwargs: Dict[str, Any] = {}
211
210
 
212
211
  # Add common field metadata
@@ -219,21 +218,40 @@ def _get_type_with_constraints(
219
218
  if "readOnly" in field_schema:
220
219
  field_kwargs["frozen"] = field_schema["readOnly"]
221
220
 
221
+ field_type = field_schema.get("type")
222
+
222
223
  # Handle array type
223
224
  if field_type == "array":
224
225
  items_schema = field_schema.get("items", {})
225
226
  if not items_schema:
226
227
  return (List[Any], Field(**field_kwargs))
227
228
 
228
- # Create nested model with explicit type annotation
229
- array_item_model = create_dynamic_model(
230
- items_schema,
231
- base_name=f"{base_name}_{field_name}_Item",
232
- show_schema=False,
233
- debug_validation=False,
234
- )
235
- array_type: Type[List[Any]] = List[array_item_model] # type: ignore[valid-type]
236
- return (array_type, Field(**field_kwargs))
229
+ # Create nested model for object items
230
+ if (
231
+ isinstance(items_schema, dict)
232
+ and items_schema.get("type") == "object"
233
+ ):
234
+ array_item_model = create_dynamic_model(
235
+ items_schema,
236
+ base_name=f"{base_name}_{field_name}_Item",
237
+ show_schema=False,
238
+ debug_validation=False,
239
+ )
240
+ array_type: Type[List[Any]] = List[array_item_model] # type: ignore[valid-type]
241
+ return (array_type, Field(**field_kwargs))
242
+
243
+ # For non-object items, use the type directly
244
+ item_type = items_schema.get("type", "string")
245
+ if item_type == "string":
246
+ return (List[str], Field(**field_kwargs))
247
+ elif item_type == "integer":
248
+ return (List[int], Field(**field_kwargs))
249
+ elif item_type == "number":
250
+ return (List[float], Field(**field_kwargs))
251
+ elif item_type == "boolean":
252
+ return (List[bool], Field(**field_kwargs))
253
+ else:
254
+ return (List[Any], Field(**field_kwargs))
237
255
 
238
256
  # Handle object type
239
257
  if field_type == "object":
@@ -936,11 +954,7 @@ def create_template_context(
936
954
  # Add file variables
937
955
  if files:
938
956
  for name, file_list in files.items():
939
- # For single files, extract the first FileInfo object
940
- if len(file_list) == 1:
941
- context[name] = file_list[0]
942
- else:
943
- context[name] = file_list
957
+ context[name] = file_list # Always keep FileInfoList wrapper
944
958
 
945
959
  # Add simple variables
946
960
  if variables:
@@ -1828,13 +1842,17 @@ def create_dynamic_model(
1828
1842
  logger.info("Schema missing type field, assuming object type")
1829
1843
  schema["type"] = "object"
1830
1844
 
1831
- # Validate root schema is object type
1845
+ # For non-object root schemas, create a wrapper model
1832
1846
  if schema["type"] != "object":
1833
1847
  if debug_validation:
1834
- logger.error(
1835
- "Schema type must be 'object', got %s", schema["type"]
1848
+ logger.info(
1849
+ "Converting non-object root schema to object wrapper"
1836
1850
  )
1837
- raise SchemaValidationError("Root schema must be of type 'object'")
1851
+ schema = {
1852
+ "type": "object",
1853
+ "properties": {"value": schema},
1854
+ "required": ["value"],
1855
+ }
1838
1856
 
1839
1857
  # Create model configuration
1840
1858
  config = ConfigDict(
ostruct/cli/file_info.py CHANGED
@@ -46,6 +46,8 @@ class FileInfo:
46
46
  FileNotFoundError: If the file does not exist
47
47
  PathSecurityError: If the path is not allowed
48
48
  """
49
+ logger.debug("Creating FileInfo for path: %s", path)
50
+
49
51
  # Validate path
50
52
  if not path:
51
53
  raise ValueError("Path cannot be empty")
@@ -61,6 +63,8 @@ class FileInfo:
61
63
 
62
64
  # First check if file exists
63
65
  abs_path = os.path.abspath(self.__path)
66
+ logger.debug("Absolute path for %s: %s", path, abs_path)
67
+
64
68
  if not os.path.exists(abs_path):
65
69
  raise FileNotFoundError(f"File not found: {path}")
66
70
  if not os.path.isfile(abs_path):
@@ -69,7 +73,10 @@ class FileInfo:
69
73
  # Then validate security
70
74
  try:
71
75
  # This will raise PathSecurityError if path is not allowed
72
- self.abs_path
76
+ resolved_path = self.abs_path
77
+ logger.debug(
78
+ "Security-resolved path for %s: %s", path, resolved_path
79
+ )
73
80
  except PathSecurityError:
74
81
  raise
75
82
  except Exception as e:
@@ -77,6 +84,7 @@ class FileInfo:
77
84
 
78
85
  # If content/encoding weren't provided, read them now
79
86
  if self.__content is None or self.__encoding is None:
87
+ logger.debug("Reading content for %s", path)
80
88
  self._read_file()
81
89
 
82
90
  @property
ostruct/cli/file_list.py CHANGED
@@ -1,11 +1,14 @@
1
1
  """FileInfoList implementation providing smart file content access."""
2
2
 
3
- from typing import List, Union
3
+ import logging
4
+ from typing import Iterator, List, SupportsIndex, Union, overload
4
5
 
5
6
  from .file_info import FileInfo
6
7
 
7
8
  __all__ = ["FileInfoList", "FileInfo"]
8
9
 
10
+ logger = logging.getLogger(__name__)
11
+
9
12
 
10
13
  class FileInfoList(List[FileInfo]):
11
14
  """List of FileInfo objects with smart content access.
@@ -44,6 +47,11 @@ class FileInfoList(List[FileInfo]):
44
47
  files: List of FileInfo objects
45
48
  from_dir: Whether this list was created from a directory mapping
46
49
  """
50
+ logger.debug(
51
+ "Creating FileInfoList with %d files, from_dir=%s",
52
+ len(files),
53
+ from_dir,
54
+ )
47
55
  super().__init__(files)
48
56
  self._from_dir = from_dir
49
57
 
@@ -59,9 +67,18 @@ class FileInfoList(List[FileInfo]):
59
67
  ValueError: If the list is empty
60
68
  """
61
69
  if not self:
70
+ logger.debug("FileInfoList.content called but list is empty")
62
71
  raise ValueError("No files in FileInfoList")
63
72
  if len(self) == 1 and not self._from_dir:
73
+ logger.debug(
74
+ "FileInfoList.content returning single file content (not from dir)"
75
+ )
64
76
  return self[0].content
77
+ logger.debug(
78
+ "FileInfoList.content returning list of %d contents (from_dir=%s)",
79
+ len(self),
80
+ self._from_dir,
81
+ )
65
82
  return [f.content for f in self]
66
83
 
67
84
  @property
@@ -149,3 +166,42 @@ class FileInfoList(List[FileInfo]):
149
166
  str: Same as str() for consistency
150
167
  """
151
168
  return str(self)
169
+
170
+ def __iter__(self) -> Iterator[FileInfo]:
171
+ """Return iterator over files."""
172
+ logger.debug(
173
+ "Starting iteration over FileInfoList with %d files", len(self)
174
+ )
175
+ return super().__iter__()
176
+
177
+ def __len__(self) -> int:
178
+ """Return number of files."""
179
+ return super().__len__()
180
+
181
+ def __bool__(self) -> bool:
182
+ """Return True if there are files."""
183
+ return super().__len__() > 0
184
+
185
+ @overload
186
+ def __getitem__(self, index: SupportsIndex, /) -> FileInfo: ...
187
+
188
+ @overload
189
+ def __getitem__(self, index: slice, /) -> "FileInfoList": ...
190
+
191
+ def __getitem__(
192
+ self, index: Union[SupportsIndex, slice], /
193
+ ) -> Union[FileInfo, "FileInfoList"]:
194
+ """Get file at index."""
195
+ logger.debug("Getting file at index %s", index)
196
+ result = super().__getitem__(index)
197
+ if isinstance(index, slice):
198
+ if not isinstance(result, list):
199
+ raise TypeError(
200
+ f"Expected list from slice, got {type(result)}"
201
+ )
202
+ return FileInfoList(result, self._from_dir)
203
+ if not isinstance(result, FileInfo):
204
+ raise TypeError(
205
+ f"Expected FileInfo from index, got {type(result)}"
206
+ )
207
+ return result
ostruct/cli/file_utils.py CHANGED
@@ -132,7 +132,7 @@ def collect_files_from_pattern(
132
132
  return []
133
133
 
134
134
  # Create FileInfo objects
135
- files = []
135
+ files: List[FileInfo] = []
136
136
  for path in matched_paths:
137
137
  try:
138
138
  file_info = FileInfo.from_path(path, security_manager)
@@ -169,37 +169,77 @@ def collect_files_from_directory(
169
169
  DirectoryNotFoundError: If directory does not exist
170
170
  PathSecurityError: If directory is not allowed
171
171
  """
172
+ logger.debug(
173
+ "Collecting files from directory: %s (recursive=%s, extensions=%s)",
174
+ directory,
175
+ recursive,
176
+ allowed_extensions,
177
+ )
178
+
172
179
  # Validate directory exists and is allowed
173
180
  try:
174
181
  abs_dir = str(security_manager.resolve_path(directory))
175
- except PathSecurityError:
182
+ logger.debug("Resolved absolute directory path: %s", abs_dir)
183
+ logger.debug(
184
+ "Security manager base_dir: %s", security_manager.base_dir
185
+ )
186
+ logger.debug(
187
+ "Security manager allowed_dirs: %s", security_manager.allowed_dirs
188
+ )
189
+ except PathSecurityError as e:
190
+ logger.debug("PathSecurityError while resolving directory: %s", str(e))
176
191
  # Let the original error propagate
177
192
  raise
178
193
 
179
194
  if not os.path.exists(abs_dir):
195
+ logger.debug("Directory not found: %s (abs: %s)", directory, abs_dir)
180
196
  raise DirectoryNotFoundError(f"Directory not found: {directory}")
181
197
  if not os.path.isdir(abs_dir):
198
+ logger.debug(
199
+ "Path is not a directory: %s (abs: %s)", directory, abs_dir
200
+ )
182
201
  raise DirectoryNotFoundError(f"Path is not a directory: {directory}")
183
202
 
184
203
  # Collect files
185
- files = []
186
- for root, _, filenames in os.walk(abs_dir):
204
+ files: List[FileInfo] = []
205
+ for root, dirs, filenames in os.walk(abs_dir):
206
+ logger.debug("Walking directory: %s", root)
207
+ logger.debug("Found subdirectories: %s", dirs)
208
+ logger.debug("Found files: %s", filenames)
209
+
187
210
  if not recursive and root != abs_dir:
211
+ logger.debug(
212
+ "Skipping subdirectory (non-recursive mode): %s", root
213
+ )
188
214
  continue
189
215
 
216
+ logger.debug("Scanning directory: %s", root)
217
+ logger.debug("Current files collected: %d", len(files))
190
218
  for filename in filenames:
191
219
  # Get relative path from base directory
192
220
  abs_path = os.path.join(root, filename)
193
221
  try:
194
222
  rel_path = os.path.relpath(abs_path, security_manager.base_dir)
195
- except ValueError:
223
+ logger.debug("Processing file: %s -> %s", abs_path, rel_path)
224
+ except ValueError as e:
196
225
  # Skip files that can't be made relative
226
+ logger.debug(
227
+ "Skipping file that can't be made relative: %s (error: %s)",
228
+ abs_path,
229
+ str(e),
230
+ )
197
231
  continue
198
232
 
199
233
  # Check extension if filter is specified
200
234
  if allowed_extensions is not None:
201
235
  ext = os.path.splitext(filename)[1].lstrip(".")
202
236
  if ext not in allowed_extensions:
237
+ logger.debug(
238
+ "Skipping file with non-matching extension: %s (ext=%s, allowed=%s)",
239
+ filename,
240
+ ext,
241
+ allowed_extensions,
242
+ )
203
243
  continue
204
244
 
205
245
  try:
@@ -207,10 +247,17 @@ def collect_files_from_directory(
207
247
  rel_path, security_manager=security_manager, **kwargs
208
248
  )
209
249
  files.append(file_info)
210
- except (FileNotFoundError, PathSecurityError):
250
+ logger.debug("Added file to list: %s", rel_path)
251
+ except (FileNotFoundError, PathSecurityError) as e:
211
252
  # Skip files that can't be accessed
253
+ logger.debug(
254
+ "Skipping inaccessible file: %s (error: %s)",
255
+ rel_path,
256
+ str(e),
257
+ )
212
258
  continue
213
259
 
260
+ logger.debug("Collected %d files from directory %s", len(files), directory)
214
261
  return files
215
262
 
216
263
 
@@ -272,18 +319,38 @@ def collect_files(
272
319
  PathSecurityError: If a path is outside the base directory
273
320
  DirectoryNotFoundError: If a directory is not found
274
321
  """
322
+ logger.debug(
323
+ "Collecting files (recursive=%s, extensions=%s):\n files=%s\n patterns=%s\n dirs=%s",
324
+ dir_recursive,
325
+ dir_extensions,
326
+ file_mappings,
327
+ pattern_mappings,
328
+ dir_mappings,
329
+ )
330
+
275
331
  if security_manager is None:
276
332
  security_manager = SecurityManager(base_dir=os.getcwd())
333
+ logger.debug(
334
+ "Created default security manager with base_dir=%s", os.getcwd()
335
+ )
336
+ else:
337
+ logger.debug(
338
+ "Using provided security manager with base_dir=%s and allowed_dirs=%s",
339
+ security_manager.base_dir,
340
+ security_manager.allowed_dirs,
341
+ )
277
342
 
278
343
  # Normalize extensions by removing leading dots
279
344
  if dir_extensions:
280
345
  dir_extensions = [ext.lstrip(".") for ext in dir_extensions]
346
+ logger.debug("Normalized extensions: %s", dir_extensions)
281
347
 
282
348
  files: Dict[str, FileInfoList] = {}
283
349
 
284
350
  # Process file mappings
285
351
  if file_mappings:
286
352
  for mapping in file_mappings:
353
+ logger.debug("Processing file mapping: %s", mapping)
287
354
  name, path = _validate_and_split_mapping(mapping, "file")
288
355
  if name in files:
289
356
  raise ValueError(f"Duplicate file mapping: {name}")
@@ -292,10 +359,12 @@ def collect_files(
292
359
  path, security_manager=security_manager, **kwargs
293
360
  )
294
361
  files[name] = FileInfoList([file_info], from_dir=False)
362
+ logger.debug("Added single file mapping: %s -> %s", name, path)
295
363
 
296
364
  # Process pattern mappings
297
365
  if pattern_mappings:
298
366
  for mapping in pattern_mappings:
367
+ logger.debug("Processing pattern mapping: %s", mapping)
299
368
  name, pattern = _validate_and_split_mapping(mapping, "pattern")
300
369
  if name in files:
301
370
  raise ValueError(f"Duplicate pattern mapping: {name}")
@@ -305,6 +374,7 @@ def collect_files(
305
374
  pattern, security_manager=security_manager, **kwargs
306
375
  )
307
376
  except PathSecurityError as e:
377
+ logger.debug("Security error in pattern mapping: %s", str(e))
308
378
  raise PathSecurityError(
309
379
  "Pattern mapping error: Access denied: "
310
380
  f"{pattern} is outside base directory and not in allowed directories"
@@ -315,14 +385,24 @@ def collect_files(
315
385
  continue
316
386
 
317
387
  files[name] = FileInfoList(matched_files, from_dir=False)
388
+ logger.debug(
389
+ "Added pattern mapping: %s -> %s (%d files)",
390
+ name,
391
+ pattern,
392
+ len(matched_files),
393
+ )
318
394
 
319
395
  # Process directory mappings
320
396
  if dir_mappings:
321
397
  for mapping in dir_mappings:
398
+ logger.debug("Processing directory mapping: %s", mapping)
322
399
  name, directory = _validate_and_split_mapping(mapping, "directory")
323
400
  if name in files:
324
401
  raise ValueError(f"Duplicate directory mapping: {name}")
325
402
 
403
+ logger.debug(
404
+ "Processing directory mapping: %s -> %s", name, directory
405
+ )
326
406
  try:
327
407
  dir_files = collect_files_from_directory(
328
408
  directory=directory,
@@ -332,11 +412,13 @@ def collect_files(
332
412
  **kwargs,
333
413
  )
334
414
  except PathSecurityError as e:
415
+ logger.debug("Security error in directory mapping: %s", str(e))
335
416
  raise PathSecurityError(
336
417
  "Directory mapping error: Access denied: "
337
418
  f"{directory} is outside base directory and not in allowed directories"
338
419
  ) from e
339
- except DirectoryNotFoundError:
420
+ except DirectoryNotFoundError as e:
421
+ logger.debug("Directory not found: %s", str(e))
340
422
  raise DirectoryNotFoundError(
341
423
  f"Directory not found: {directory}"
342
424
  )
@@ -346,10 +428,18 @@ def collect_files(
346
428
  files[name] = FileInfoList([], from_dir=True)
347
429
  else:
348
430
  files[name] = FileInfoList(dir_files, from_dir=True)
431
+ logger.debug(
432
+ "Added directory mapping: %s -> %s (%d files)",
433
+ name,
434
+ directory,
435
+ len(dir_files),
436
+ )
349
437
 
350
438
  if not files:
439
+ logger.debug("No files found in any mappings")
351
440
  raise ValueError("No files found")
352
441
 
442
+ logger.debug("Collected files total mappings: %d", len(files))
353
443
  return files
354
444
 
355
445
 
ostruct/cli/security.py CHANGED
@@ -96,10 +96,15 @@ class SecurityManager(SecurityManagerProtocol):
96
96
  All paths are normalized using realpath to handle symlinks
97
97
  and relative paths consistently across platforms.
98
98
  """
99
+ logger = logging.getLogger("ostruct")
100
+ logger.debug("Initializing SecurityManager")
99
101
  self._base_dir = Path(os.path.realpath(base_dir or os.getcwd()))
102
+ logger.debug("Base directory set to: %s", self._base_dir)
103
+
100
104
  self._allowed_dirs: Set[Path] = set()
101
105
  if allowed_dirs:
102
106
  for directory in allowed_dirs:
107
+ logger.debug("Adding allowed directory: %s", directory)
103
108
  self.add_allowed_dir(directory)
104
109
 
105
110
  @property
@@ -121,14 +126,21 @@ class SecurityManager(SecurityManagerProtocol):
121
126
  Raises:
122
127
  DirectoryNotFoundError: If directory does not exist
123
128
  """
129
+ logger = logging.getLogger("ostruct")
130
+ logger.debug("Adding allowed directory: %s", directory)
124
131
  real_path = Path(os.path.realpath(directory))
132
+ logger.debug("Resolved real path: %s", real_path)
133
+
125
134
  if not real_path.exists():
135
+ logger.debug("Directory not found: %s", directory)
126
136
  raise DirectoryNotFoundError(f"Directory not found: {directory}")
127
137
  if not real_path.is_dir():
138
+ logger.debug("Path is not a directory: %s", directory)
128
139
  raise DirectoryNotFoundError(
129
140
  f"Path is not a directory: {directory}"
130
141
  )
131
142
  self._allowed_dirs.add(real_path)
143
+ logger.debug("Successfully added allowed directory: %s", real_path)
132
144
 
133
145
  def add_allowed_dirs_from_file(self, file_path: str) -> None:
134
146
  """Add allowed directories from a file.
@@ -9,7 +9,7 @@ from jinja2.ext import Extension
9
9
  from jinja2.parser import Parser
10
10
 
11
11
 
12
- class CommentExtension(Extension): # type: ignore[misc]
12
+ class CommentExtension(Extension):
13
13
  """Extension that ignores variables inside comment blocks.
14
14
 
15
15
  This extension ensures that:
@@ -300,14 +300,6 @@ def render_template(
300
300
  "=== Rendered result (first 1000 chars) ===\n%s",
301
301
  result[:1000],
302
302
  )
303
- if "## File:" not in result:
304
- logger.error(
305
- "WARNING: File headers missing from rendered output!"
306
- )
307
- logger.error(
308
- "Template string excerpt: %r", template_str[:200]
309
- )
310
- logger.error("Result excerpt: %r", result[:200])
311
303
  if progress:
312
304
  progress.update(1)
313
305
  return result # type: ignore[no-any-return]
@@ -87,7 +87,7 @@ __all__ = [
87
87
  ]
88
88
 
89
89
 
90
- class SafeUndefined(jinja2.StrictUndefined): # type: ignore[misc]
90
+ class SafeUndefined(jinja2.StrictUndefined):
91
91
  """A strict Undefined class that validates attribute access during validation."""
92
92
 
93
93
  def __getattr__(self, name: str) -> Any:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ostruct-cli
3
- Version: 0.1.4
3
+ Version: 0.2.0
4
4
  Summary: CLI for OpenAI Structured Output
5
5
  Author: Yaniv Golan
6
6
  Author-email: yaniv@golan.name
@@ -1,27 +1,27 @@
1
1
  ostruct/__init__.py,sha256=X6zo6V7ZNMv731Wi388aTVQngD1410ExGwGx4J6lpyo,187
2
2
  ostruct/cli/__init__.py,sha256=dbdUfKRT4ARSHgjsZOmQ-TJT1mHwL7L3xMuilPkhG4Q,411
3
3
  ostruct/cli/cache_manager.py,sha256=ej3KrRfkKKZ_lEp2JswjbJ5bW2ncsvna9NeJu81cqqs,5192
4
- ostruct/cli/cli.py,sha256=uDh7-_pGXgiq2AMo4vDuNMotk5HNIIki_FJFWb2LWTY,69529
4
+ ostruct/cli/cli.py,sha256=jJ60txGOA4ju-4Z057FA8eRnrpxTHDLBn6bZLNsJy9Q,70170
5
5
  ostruct/cli/errors.py,sha256=vxfjXpUbsarYJoTBaGFpjpa8wXqOMGQryTh-roUhRuU,9454
6
- ostruct/cli/file_info.py,sha256=jjsLVDD8YMpVryBzKVdoQsjE33PLA1BBTVqH3VOHbuA,10167
7
- ostruct/cli/file_list.py,sha256=jTtqNo05GT4m4TpEpd3MT9ZlueH_sQgcjHiERRbt-IY,5217
8
- ostruct/cli/file_utils.py,sha256=NLbYIum2vB3SOPrG5hsE4Djg5WeStRppIX2v7L1Y9Dg,17404
6
+ ostruct/cli/file_info.py,sha256=SdU_V5g4R0o41tOTlobOF5cibZfb9qS12fK7G2XNfRs,10481
7
+ ostruct/cli/file_list.py,sha256=srrouvhimoklNo69nWjrKyUN-5zTOmtVj_swdBZPBTk,7105
8
+ ostruct/cli/file_utils.py,sha256=uqEvRoWskzKUd5akA-NtdKtLWByKMTXOiVJiiO7uXus,21153
9
9
  ostruct/cli/path_utils.py,sha256=jhJsvmxsq0_FU5cWwB-JoEymGbvB2JX9Q0a-dUf0s1w,4461
10
10
  ostruct/cli/progress.py,sha256=rj9nVEco5UeZORMbzd7mFJpFGJjbH9KbBFh5oTE5Anw,3415
11
- ostruct/cli/security.py,sha256=8X3eiK9XN1Fcw6TQ5hxfEb-Dt0vF21vdoXpxr5p-Aos,10332
11
+ ostruct/cli/security.py,sha256=j7oRb7UM_Mndl_CvnQlql8eyudcAVgfCkeQuXIQyME4,10945
12
12
  ostruct/cli/security_types.py,sha256=oSNbaKjhZVHB6pQEG_WOUhUYKlw9cl4uGOBx2R9BxRk,1341
13
13
  ostruct/cli/template_env.py,sha256=S2ZvxuMQMicodSVqUhrw0kOzbNmlpQjSHtWlOwjXCms,1538
14
- ostruct/cli/template_extensions.py,sha256=jGj8nKKyWOaN4u271N9luDNZAZ60xGu17qalf9ybibc,1645
14
+ ostruct/cli/template_extensions.py,sha256=tJN3HGAS2yzGI8Up6STPday8NVL0VV6UCClBrtDKYr0,1623
15
15
  ostruct/cli/template_filters.py,sha256=SNp7PR4ZbuC9BVUlEgwzd6VZYjI0lsobTabLiJe_sZM,19030
16
16
  ostruct/cli/template_io.py,sha256=6rDw2Wx6czK1VntKGUM6cvyMbMWojt41hUlYRpfQuoc,8749
17
- ostruct/cli/template_rendering.py,sha256=JcxwVppl2Q6dNsH_pAv4hW3LH2if76cvgwrh-VNPpL8,12769
17
+ ostruct/cli/template_rendering.py,sha256=GrQAcKpGe6QEjSVQkOjpegMcor9LzVUikGmmEVgiWCE,12391
18
18
  ostruct/cli/template_schema.py,sha256=ckH4rUZnEgfm_BHS9LnMGr8LtDxRmZ0C6UBVrSp8KTc,19604
19
19
  ostruct/cli/template_utils.py,sha256=QGgewxU_Tgn81J5U-Y4xfi67CkN2dEqXI7PsaNiI9es,7812
20
- ostruct/cli/template_validation.py,sha256=ygM_37Cqd2RfOugf9PmhS-Z3WFwDvjPhduB7-C2LMfw,11759
20
+ ostruct/cli/template_validation.py,sha256=q3ACw4TscdekJb3Z3CTYw0YPEYttqjKjm74ap4lWtU4,11737
21
21
  ostruct/cli/utils.py,sha256=1UCl4rHjBWKR5EKugvlVGHiHjO3XXmqvkgeAUSyIPDU,831
22
22
  ostruct/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- ostruct_cli-0.1.4.dist-info/LICENSE,sha256=QUOY6QCYVxAiH8vdrUTDqe3i9hQ5bcNczppDSVpLTjk,1068
24
- ostruct_cli-0.1.4.dist-info/METADATA,sha256=N5YTPqzaX97RPeh1IssYTi9pCIY0lW1GwC11GHX2Jdw,5300
25
- ostruct_cli-0.1.4.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
26
- ostruct_cli-0.1.4.dist-info/entry_points.txt,sha256=NFq9IuqHVTem0j9zKjV8C1si_zGcP1RL6Wbvt9fUDXw,48
27
- ostruct_cli-0.1.4.dist-info/RECORD,,
23
+ ostruct_cli-0.2.0.dist-info/LICENSE,sha256=QUOY6QCYVxAiH8vdrUTDqe3i9hQ5bcNczppDSVpLTjk,1068
24
+ ostruct_cli-0.2.0.dist-info/METADATA,sha256=uduwQEF87qBeSBhzFg9AWdFqnDM1TdRyvdRwq-IKqZQ,5300
25
+ ostruct_cli-0.2.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
26
+ ostruct_cli-0.2.0.dist-info/entry_points.txt,sha256=NFq9IuqHVTem0j9zKjV8C1si_zGcP1RL6Wbvt9fUDXw,48
27
+ ostruct_cli-0.2.0.dist-info/RECORD,,