ara-cli 0.1.13.3__py3-none-any.whl → 0.1.14.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.
Files changed (61) hide show
  1. ara_cli/__init__.py +1 -1
  2. ara_cli/ara_command_action.py +162 -112
  3. ara_cli/ara_config.py +1 -1
  4. ara_cli/ara_subcommands/convert.py +66 -2
  5. ara_cli/ara_subcommands/prompt.py +266 -106
  6. ara_cli/artefact_autofix.py +2 -2
  7. ara_cli/artefact_converter.py +152 -53
  8. ara_cli/artefact_creator.py +41 -17
  9. ara_cli/artefact_lister.py +3 -3
  10. ara_cli/artefact_models/artefact_model.py +1 -1
  11. ara_cli/artefact_models/artefact_templates.py +0 -9
  12. ara_cli/artefact_models/feature_artefact_model.py +8 -8
  13. ara_cli/artefact_reader.py +62 -43
  14. ara_cli/artefact_scan.py +39 -17
  15. ara_cli/chat.py +23 -15
  16. ara_cli/children_contribution_updater.py +737 -0
  17. ara_cli/classifier.py +34 -0
  18. ara_cli/commands/load_command.py +4 -3
  19. ara_cli/commands/load_image_command.py +1 -1
  20. ara_cli/commands/read_command.py +23 -27
  21. ara_cli/completers.py +24 -0
  22. ara_cli/error_handler.py +26 -11
  23. ara_cli/file_loaders/document_reader.py +0 -178
  24. ara_cli/file_loaders/factories/__init__.py +0 -0
  25. ara_cli/file_loaders/factories/document_reader_factory.py +32 -0
  26. ara_cli/file_loaders/factories/file_loader_factory.py +27 -0
  27. ara_cli/file_loaders/file_loader.py +1 -30
  28. ara_cli/file_loaders/loaders/__init__.py +0 -0
  29. ara_cli/file_loaders/{document_file_loader.py → loaders/document_file_loader.py} +1 -1
  30. ara_cli/file_loaders/loaders/text_file_loader.py +47 -0
  31. ara_cli/file_loaders/readers/__init__.py +0 -0
  32. ara_cli/file_loaders/readers/docx_reader.py +49 -0
  33. ara_cli/file_loaders/readers/excel_reader.py +27 -0
  34. ara_cli/file_loaders/{markdown_reader.py → readers/markdown_reader.py} +1 -1
  35. ara_cli/file_loaders/readers/odt_reader.py +59 -0
  36. ara_cli/file_loaders/readers/pdf_reader.py +54 -0
  37. ara_cli/file_loaders/readers/pptx_reader.py +104 -0
  38. ara_cli/file_loaders/tools/__init__.py +0 -0
  39. ara_cli/output_suppressor.py +53 -0
  40. ara_cli/prompt_handler.py +123 -17
  41. ara_cli/tag_extractor.py +8 -7
  42. ara_cli/version.py +1 -1
  43. {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/METADATA +18 -12
  44. {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/RECORD +58 -45
  45. {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/WHEEL +1 -1
  46. tests/test_artefact_converter.py +1 -46
  47. tests/test_artefact_lister.py +11 -8
  48. tests/test_chat.py +4 -4
  49. tests/test_chat_givens_images.py +1 -1
  50. tests/test_children_contribution_updater.py +98 -0
  51. tests/test_document_loader_office.py +267 -0
  52. tests/test_prompt_handler.py +416 -214
  53. tests/test_setup_default_chat_prompt_mode.py +198 -0
  54. tests/test_tag_extractor.py +95 -49
  55. ara_cli/file_loaders/document_readers.py +0 -233
  56. ara_cli/file_loaders/file_loaders.py +0 -123
  57. ara_cli/file_loaders/text_file_loader.py +0 -187
  58. /ara_cli/file_loaders/{binary_file_loader.py → loaders/binary_file_loader.py} +0 -0
  59. /ara_cli/file_loaders/{image_processor.py → tools/image_processor.py} +0 -0
  60. {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/entry_points.txt +0 -0
  61. {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,267 @@
1
+ import unittest
2
+ from unittest.mock import MagicMock, patch, ANY
3
+ import sys
4
+ import os
5
+
6
+ # Mock the new dependencies to avoid import errors during test collection if they are not installed
7
+ sys.modules["pandas"] = MagicMock()
8
+ sys.modules["openpyxl"] = MagicMock()
9
+ sys.modules["pptx"] = MagicMock()
10
+ sys.modules["pptx.presentation"] = MagicMock()
11
+ sys.modules["pptx.enum.shapes"] = MagicMock()
12
+
13
+ from ara_cli.file_loaders.readers.excel_reader import ExcelReader
14
+ from ara_cli.file_loaders.readers.pptx_reader import PptxReader
15
+ from ara_cli.file_loaders.factories.document_reader_factory import DocumentReaderFactory
16
+
17
+
18
+ class TestExcelReader(unittest.TestCase):
19
+ @patch("pandas.read_excel")
20
+ def test_read_excel_success(self, mock_read_excel):
21
+ # Setup mock dataframe
22
+ mock_df = MagicMock()
23
+ mock_df.empty = False
24
+ mock_df.to_markdown.return_value = "| Col1 | Col2 |\n|---|---|\n| Val1 | Val2 |"
25
+ mock_df.fillna.return_value = mock_df
26
+
27
+ # Setup read_excel return value (dict of sheets)
28
+ mock_read_excel.return_value = {"Sheet1": mock_df}
29
+
30
+ reader = ExcelReader("test.xlsx")
31
+ content = reader.read()
32
+
33
+ self.assertIn("### Sheet: Sheet1", content)
34
+ self.assertIn("| Col1 | Col2 |", content)
35
+ mock_read_excel.assert_called_once_with("test.xlsx", sheet_name=None)
36
+ mock_df.to_markdown.assert_called_once()
37
+
38
+ @patch("pandas.read_excel")
39
+ def test_read_excel_empty_sheet(self, mock_read_excel):
40
+ mock_df = MagicMock()
41
+ mock_df.empty = True
42
+
43
+ mock_read_excel.return_value = {"SheetEmpty": mock_df}
44
+
45
+ reader = ExcelReader("test.xlsx")
46
+ content = reader.read()
47
+
48
+ self.assertIn("### Sheet: SheetEmpty", content)
49
+ self.assertIn("_Empty Sheet_", content)
50
+
51
+ @patch("pandas.read_excel")
52
+ def test_read_excel_error(self, mock_read_excel):
53
+ mock_read_excel.side_effect = Exception("File not found")
54
+
55
+ reader = ExcelReader("test.xlsx")
56
+ content = reader.read()
57
+
58
+ self.assertIn("Error reading Excel file: File not found", content)
59
+
60
+
61
+ class TestPptxReader(unittest.TestCase):
62
+ @patch("pptx.Presentation")
63
+ def test_read_pptx_success(self, mock_presentation):
64
+ # Setup mock presentation
65
+ mock_prs = MagicMock()
66
+ mock_slide = MagicMock()
67
+
68
+ # Setup shapes
69
+ mock_shape_title = MagicMock()
70
+ mock_shape_title.has_text_frame = True
71
+ mock_shape_title.text_frame.text = "Slide Title"
72
+ mock_shape_title.top = 0
73
+ mock_shape_title.left = 0
74
+
75
+ mock_shape_content = MagicMock()
76
+ mock_shape_content.has_text_frame = True
77
+ mock_shape_content.text_frame.paragraphs = [
78
+ MagicMock(text="Bullet 1"),
79
+ MagicMock(text="Bullet 2"),
80
+ ]
81
+ mock_shape_content.top = 100
82
+ mock_shape_content.left = 0
83
+
84
+ # slide.shapes must be a mock that is iterable AND has a title attribute
85
+ mock_shapes = MagicMock()
86
+ mock_shapes.__iter__.return_value = [mock_shape_title, mock_shape_content]
87
+ mock_shapes.title = mock_shape_title
88
+
89
+ mock_slide.shapes = mock_shapes
90
+
91
+ mock_slide.shapes = mock_shapes
92
+ mock_prs.slides = [mock_slide]
93
+
94
+ mock_presentation.return_value = mock_prs
95
+
96
+ reader = PptxReader("test.pptx")
97
+ content = reader.read()
98
+
99
+ self.assertIn("## Slide 1", content)
100
+ self.assertIn("### Slide Title", content)
101
+ self.assertIn("- Bullet 1", content)
102
+ self.assertIn("- Bullet 2", content)
103
+
104
+ @patch("pptx.Presentation")
105
+ def test_read_pptx_with_images(self, mock_presentation):
106
+ from pptx.enum.shapes import MSO_SHAPE_TYPE
107
+
108
+ # Setup mock presentation
109
+ mock_prs = MagicMock()
110
+ mock_slide = MagicMock()
111
+
112
+ # Setup shapes
113
+ mock_shape_image = MagicMock()
114
+ mock_shape_image.has_text_frame = False
115
+ mock_shape_image.shape_type = MSO_SHAPE_TYPE.PICTURE
116
+ mock_shape_image.image.blob = b"fake_image_data"
117
+ mock_shape_image.image.ext = "png"
118
+ mock_shape_image.top = 0
119
+ mock_shape_image.left = 0
120
+
121
+ # Mock shapes as list is fine here if we don't access .title, but cleaner to be consistent if code checks .title
122
+ mock_shapes = MagicMock()
123
+ mock_shapes.__iter__.return_value = [mock_shape_image]
124
+ # If code checks slide.shapes.title, we should ensure it doesn't crash or has it
125
+ # The code does: try: if shape == slide.shapes.title: ... except AttributeError: ...
126
+ # So it handles missing title attribute.
127
+
128
+ mock_slide.shapes = mock_shapes
129
+ mock_prs.slides = [mock_slide]
130
+ mock_presentation.return_value = mock_prs
131
+
132
+ # Mock create_image_data_dir and save_and_describe_image
133
+ with patch.object(
134
+ PptxReader, "create_image_data_dir"
135
+ ) as mock_create_dir, patch.object(
136
+ PptxReader, "save_and_describe_image"
137
+ ) as mock_save_describe:
138
+
139
+ mock_create_dir.return_value = "fake/dir"
140
+ mock_save_describe.return_value = (
141
+ "fake/dir/1.png",
142
+ "A fake image description",
143
+ )
144
+
145
+ reader = PptxReader("test.pptx")
146
+ content = reader.read(extract_images=True)
147
+
148
+ self.assertIn("## Slide 1", content)
149
+ self.assertIn("Image: fake/dir/1.png", content)
150
+ self.assertIn("[A fake image description]", content)
151
+
152
+ @patch("pptx.Presentation")
153
+ def test_read_pptx_groups_and_placeholders(self, mock_presentation):
154
+ from pptx.enum.shapes import MSO_SHAPE_TYPE
155
+
156
+ mock_prs = MagicMock()
157
+ mock_slide = MagicMock()
158
+
159
+ # 1. Group Shape containing an image
160
+ mock_group = MagicMock()
161
+ mock_group.shape_type = MSO_SHAPE_TYPE.GROUP
162
+
163
+ mock_sub_image = MagicMock()
164
+ mock_sub_image.shape_type = MSO_SHAPE_TYPE.PICTURE
165
+ mock_sub_image.image.blob = b"group_img"
166
+ mock_sub_image.image.ext = "jpg"
167
+
168
+ mock_group.shapes = [mock_sub_image]
169
+ mock_group.top = 0
170
+ mock_group.left = 0
171
+
172
+ # 2. Placeholder with image
173
+ mock_placeholder = MagicMock()
174
+ mock_placeholder.shape_type = 0 # Not PICTURE
175
+ mock_placeholder.is_placeholder = True
176
+ mock_placeholder.image.blob = b"place_img"
177
+ mock_placeholder.image.ext = "png"
178
+ mock_placeholder.top = 100
179
+ mock_placeholder.left = 0
180
+
181
+ # 3. Shape that raises error on image access (code robustness check)
182
+ mock_broken_image = MagicMock()
183
+ mock_broken_image.shape_type = MSO_SHAPE_TYPE.PICTURE
184
+ type(mock_broken_image.image).blob = unittest.mock.PropertyMock(
185
+ side_effect=Exception("Corrupt")
186
+ )
187
+ mock_broken_image.top = 200
188
+ mock_broken_image.left = 0
189
+
190
+ # 4. Shape with text that looks like a title but raises AttributeError (line 47 coverage)
191
+ mock_title_ish = MagicMock()
192
+ mock_title_ish.has_text_frame = True
193
+ mock_title_ish.text_frame.text = "A Title"
194
+ mock_title_ish.text_frame.paragraphs = [MagicMock(text="A Title")]
195
+ mock_title_ish.is_placeholder = (
196
+ False # Important: default valid MagicMock is truthy!
197
+ )
198
+ # Ensure checking slide.shapes.title raises AttributeError
199
+ mock_slide.shapes.title = unittest.mock.PropertyMock(
200
+ side_effect=AttributeError("No title")
201
+ )
202
+ mock_title_ish.top = 300
203
+ mock_title_ish.left = 0
204
+
205
+ # Configure slide shapes iteration
206
+ mock_slide.shapes.__iter__.return_value = [
207
+ mock_group,
208
+ mock_placeholder,
209
+ mock_broken_image,
210
+ mock_title_ish,
211
+ ]
212
+
213
+ mock_prs.slides = [mock_slide]
214
+ mock_presentation.return_value = mock_prs
215
+
216
+ with patch.object(
217
+ PptxReader, "create_image_data_dir", return_value="dir"
218
+ ), patch.object(
219
+ PptxReader, "save_and_describe_image", return_value=("path", "desc")
220
+ ):
221
+
222
+ reader = PptxReader("test.pptx")
223
+ # Force AttributeError on slide.shapes.title access during loop
224
+ # The code does: `if shape == slide.shapes.title:`
225
+ # We need checking `slide.shapes.title` to fail
226
+ type(mock_slide.shapes).title = unittest.mock.PropertyMock(
227
+ side_effect=AttributeError
228
+ )
229
+
230
+ content = reader.read(extract_images=True)
231
+
232
+ # Should match group image
233
+ # Should match placeholder image
234
+ # Should handle broken image gracefully (print warning)
235
+ # Should handle title attribute error (continue)
236
+
237
+ # Since save_and_describe_image is mocked, we just check count or content existence
238
+ # We expect 2 valid images (group + placeholder)
239
+ self.assertEqual(content.count("Image: path"), 2)
240
+ # Since AttributeError on title check, correct behavior is falling back to paragraph iteration
241
+ self.assertIn("- A Title", content)
242
+
243
+ @patch("pptx.Presentation")
244
+ def test_read_pptx_top_level_error(self, mock_presentation):
245
+ mock_presentation.side_effect = Exception("File corrupt")
246
+ reader = PptxReader("test.pptx")
247
+ content = reader.read()
248
+ self.assertEqual(content, "Error reading PowerPoint file: File corrupt")
249
+
250
+
251
+ class TestDocumentReaderFactory(unittest.TestCase):
252
+ def test_create_reader(self):
253
+ from ara_cli.file_loaders.readers.docx_reader import DocxReader
254
+
255
+ self.assertIsInstance(
256
+ DocumentReaderFactory.create_reader("test.xlsx"), ExcelReader
257
+ )
258
+ self.assertIsInstance(
259
+ DocumentReaderFactory.create_reader("test.pptx"), PptxReader
260
+ )
261
+ self.assertIsInstance(
262
+ DocumentReaderFactory.create_reader("test.DOCX"), DocxReader
263
+ )
264
+
265
+
266
+ if __name__ == "__main__":
267
+ unittest.main()