apar-parser 1.0.0__tar.gz

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,9 @@
1
+ *.pyc
2
+ __pycache__/
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .DS_Store
7
+ output*/
8
+ *.txt
9
+ !README.md
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 apar-parser contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: apar-parser
3
+ Version: 1.0.0
4
+ Summary: Parse IBM APAR information from web pages
5
+ Author: apar-parser contributors
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: apar,ibm,mainframe,parser
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.8
19
+ Requires-Dist: beautifulsoup4>=4.12.0
20
+ Requires-Dist: requests>=2.31.0
21
+ Provides-Extra: gui
22
+ Requires-Dist: tk; extra == 'gui'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # APAR Parser
26
+
27
+ Parse IBM APAR (Authorized Program Analysis Report) information from IBM support pages.
28
+
29
+ ## Installation
30
+
31
+ ### Using uvx (Recommended)
32
+
33
+ **Note:** If you don't have uvx installed, see [Installing uv/uvx](#installing-uvuvx) below.
34
+
35
+ ```bash
36
+ # Run directly
37
+ uvx apar-parser -i apar_list.txt -o output_dir
38
+
39
+ # Or run from local directory
40
+ uvx --from . apar-parser -i apar_list.txt -o output_dir
41
+ ```
42
+
43
+ ### Using pip
44
+
45
+ ```bash
46
+ # Install from PyPI
47
+ pip install apar-parser
48
+
49
+ # Or install from local directory
50
+ pip install .
51
+ ```
52
+
53
+ ## Usage
54
+
55
+ ### Process APAR list from file
56
+ ```bash
57
+ apar-parser -i apar_list.txt -o output_dir
58
+ ```
59
+
60
+ ### Process single APAR
61
+ ```bash
62
+ apar-parser -a OA41368 -o output_dir
63
+ ```
64
+
65
+ ### Output formats
66
+
67
+ **Text format (default):**
68
+ ```bash
69
+ apar-parser -i apar_list.txt -o output_dir
70
+ ```
71
+
72
+ **JSON format (structured data):**
73
+ ```bash
74
+ apar-parser -i apar_list.txt -o output_dir -f json
75
+ ```
76
+
77
+ ### GUI mode (Windows only, requires tkinter)
78
+ ```bash
79
+ apar-parser --gui
80
+ ```
81
+
82
+ ## Input File Format
83
+
84
+ Plain text file with one APAR number per line:
85
+ ```
86
+ OA41368
87
+ OA36415
88
+ OA12345
89
+ ```
90
+
91
+ ## Output
92
+
93
+ ### Text format
94
+ Creates text files in the output directory:
95
+ - `{APAR}.txt` - Successfully parsed APAR
96
+ - `{APAR}.notfound.txt` - APAR not found
97
+ - `{APAR}.logon.txt` - Requires IBM ID login
98
+
99
+ ### JSON format
100
+ Creates JSON files with structured data:
101
+ ```json
102
+ {
103
+ "apar_number": "OA41368",
104
+ "title": "ABEND0C4-11 IN EDGFLTS...",
105
+ "modified_date": "21 May 2013",
106
+ "apar_information": {
107
+ "apar_number": "OA41368",
108
+ "reported_component_name": "DFSMSRMM",
109
+ ...
110
+ },
111
+ "problem_summary": "...",
112
+ "problem_conclusion": "..."
113
+ }
114
+ ```
115
+
116
+ ## Options
117
+
118
+ - `-i, --input`: Input file with APAR numbers (one per line)
119
+ - `-o, --output`: Output directory (required unless using --gui)
120
+ - `-a, --apar`: Single APAR number to process
121
+ - `-f, --format`: Output format: `txt` or `json` (default: txt)
122
+ - `--gui`: Use GUI for file selection (Windows only, requires tkinter)
123
+
124
+ ---
125
+
126
+ ## Installing uv/uvx
127
+
128
+ **macOS/Linux:**
129
+ ```bash
130
+ curl -LsSf https://astral.sh/uv/install.sh | sh
131
+ ```
132
+
133
+ **Windows:**
134
+ ```powershell
135
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
136
+ ```
137
+
138
+ Or with pip:
139
+ ```bash
140
+ pip install uv
141
+ ```
142
+
143
+ ## Disclaimer
144
+
145
+ This is an unofficial tool and is not affiliated with or endorsed by IBM. This tool parses publicly available APAR information from IBM support pages for convenience purposes only. Users are responsible for complying with IBM's terms of service and acceptable use policies when using this tool.
146
+
147
+ ## Acknowledgments
148
+
149
+ Special thanks to *Tom Tsai*, the original author of this function.
150
+
151
+
@@ -0,0 +1,127 @@
1
+ # APAR Parser
2
+
3
+ Parse IBM APAR (Authorized Program Analysis Report) information from IBM support pages.
4
+
5
+ ## Installation
6
+
7
+ ### Using uvx (Recommended)
8
+
9
+ **Note:** If you don't have uvx installed, see [Installing uv/uvx](#installing-uvuvx) below.
10
+
11
+ ```bash
12
+ # Run directly
13
+ uvx apar-parser -i apar_list.txt -o output_dir
14
+
15
+ # Or run from local directory
16
+ uvx --from . apar-parser -i apar_list.txt -o output_dir
17
+ ```
18
+
19
+ ### Using pip
20
+
21
+ ```bash
22
+ # Install from PyPI
23
+ pip install apar-parser
24
+
25
+ # Or install from local directory
26
+ pip install .
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ### Process APAR list from file
32
+ ```bash
33
+ apar-parser -i apar_list.txt -o output_dir
34
+ ```
35
+
36
+ ### Process single APAR
37
+ ```bash
38
+ apar-parser -a OA41368 -o output_dir
39
+ ```
40
+
41
+ ### Output formats
42
+
43
+ **Text format (default):**
44
+ ```bash
45
+ apar-parser -i apar_list.txt -o output_dir
46
+ ```
47
+
48
+ **JSON format (structured data):**
49
+ ```bash
50
+ apar-parser -i apar_list.txt -o output_dir -f json
51
+ ```
52
+
53
+ ### GUI mode (Windows only, requires tkinter)
54
+ ```bash
55
+ apar-parser --gui
56
+ ```
57
+
58
+ ## Input File Format
59
+
60
+ Plain text file with one APAR number per line:
61
+ ```
62
+ OA41368
63
+ OA36415
64
+ OA12345
65
+ ```
66
+
67
+ ## Output
68
+
69
+ ### Text format
70
+ Creates text files in the output directory:
71
+ - `{APAR}.txt` - Successfully parsed APAR
72
+ - `{APAR}.notfound.txt` - APAR not found
73
+ - `{APAR}.logon.txt` - Requires IBM ID login
74
+
75
+ ### JSON format
76
+ Creates JSON files with structured data:
77
+ ```json
78
+ {
79
+ "apar_number": "OA41368",
80
+ "title": "ABEND0C4-11 IN EDGFLTS...",
81
+ "modified_date": "21 May 2013",
82
+ "apar_information": {
83
+ "apar_number": "OA41368",
84
+ "reported_component_name": "DFSMSRMM",
85
+ ...
86
+ },
87
+ "problem_summary": "...",
88
+ "problem_conclusion": "..."
89
+ }
90
+ ```
91
+
92
+ ## Options
93
+
94
+ - `-i, --input`: Input file with APAR numbers (one per line)
95
+ - `-o, --output`: Output directory (required unless using --gui)
96
+ - `-a, --apar`: Single APAR number to process
97
+ - `-f, --format`: Output format: `txt` or `json` (default: txt)
98
+ - `--gui`: Use GUI for file selection (Windows only, requires tkinter)
99
+
100
+ ---
101
+
102
+ ## Installing uv/uvx
103
+
104
+ **macOS/Linux:**
105
+ ```bash
106
+ curl -LsSf https://astral.sh/uv/install.sh | sh
107
+ ```
108
+
109
+ **Windows:**
110
+ ```powershell
111
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
112
+ ```
113
+
114
+ Or with pip:
115
+ ```bash
116
+ pip install uv
117
+ ```
118
+
119
+ ## Disclaimer
120
+
121
+ This is an unofficial tool and is not affiliated with or endorsed by IBM. This tool parses publicly available APAR information from IBM support pages for convenience purposes only. Users are responsible for complying with IBM's terms of service and acceptable use policies when using this tool.
122
+
123
+ ## Acknowledgments
124
+
125
+ Special thanks to *Tom Tsai*, the original author of this function.
126
+
127
+
@@ -0,0 +1,101 @@
1
+ # APAR Parser Skill
2
+
3
+ ## Description
4
+
5
+ Parse IBM APAR (Authorized Program Analysis Report) information from IBM support pages and save to text or JSON format.
6
+
7
+ ## Capabilities
8
+
9
+ - Fetch APAR information from IBM support website
10
+ - Parse APAR details including status, problem summary, fix information, etc.
11
+ - Support batch processing from input file
12
+ - Output in text format (with section separators) or structured JSON
13
+ - Handle special cases (login required, not found)
14
+
15
+ ## Usage
16
+
17
+ ### Parse single APAR
18
+ ```bash
19
+ apar-parser -a <APAR_NUMBER> -o <OUTPUT_DIR>
20
+ ```
21
+
22
+ ### Parse multiple APARs from file
23
+ ```bash
24
+ apar-parser -i <INPUT_FILE> -o <OUTPUT_DIR>
25
+ ```
26
+
27
+ ### Output as JSON
28
+ ```bash
29
+ apar-parser -a <APAR_NUMBER> -o <OUTPUT_DIR> -f json
30
+ ```
31
+
32
+ ## Input Format
33
+
34
+ For batch processing, create a text file with one APAR number per line:
35
+ ```
36
+ OA41368
37
+ OA36415
38
+ OA12345
39
+ ```
40
+
41
+ ## Output
42
+
43
+ ### Text Format
44
+ Creates `{APAR}.txt` files with sections separated by `--`:
45
+ - APAR status
46
+ - Error description
47
+ - Problem summary
48
+ - Problem conclusion
49
+ - APAR Information (metadata)
50
+ - Fix information
51
+ - Applicable component levels
52
+
53
+ ### JSON Format
54
+ Creates `{APAR}.json` files with structured data containing all fields as key-value pairs.
55
+
56
+ ### Special Cases
57
+ - `{APAR}.notfound.txt` - APAR does not exist
58
+ - `{APAR}.logon.txt` - Requires IBM ID authentication
59
+
60
+ ## Installation
61
+
62
+ ```bash
63
+ # Using uvx (recommended)
64
+ uvx apar-parser -a OA41368 -o output
65
+
66
+ # Using pip
67
+ pip install apar-parser
68
+ ```
69
+
70
+ ## When to Use This Skill
71
+
72
+ - User needs to fetch IBM APAR information
73
+ - User wants to analyze multiple APARs
74
+ - User needs structured APAR data for further processing
75
+ - User is researching IBM system issues or fixes
76
+
77
+ ## Example Scenarios
78
+
79
+ **Scenario 1: Single APAR lookup**
80
+ ```
81
+ User: "Get information for APAR OA41368"
82
+ Agent: apar-parser -a OA41368 -o ./apar_data
83
+ ```
84
+
85
+ **Scenario 2: Batch processing**
86
+ ```
87
+ User: "Parse all APARs in this list"
88
+ Agent: apar-parser -i apar_list.txt -o ./apar_results
89
+ ```
90
+
91
+ **Scenario 3: JSON output for analysis**
92
+ ```
93
+ User: "Get APAR data in JSON format for analysis"
94
+ Agent: apar-parser -a OA41368 -o ./data -f json
95
+ ```
96
+
97
+ ## Notes
98
+
99
+ - Requires internet connection to fetch from IBM support website
100
+ - Some APARs may require IBM ID login (will be saved as .logon.txt)
101
+ - APAR numbers are typically 7 characters (e.g., OA41368)
@@ -0,0 +1,345 @@
1
+ #!/usr/bin/env python3
2
+ """APAR Parser - Extract IBM APAR information from web pages"""
3
+
4
+ import argparse
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Optional
9
+ import requests
10
+ from bs4 import BeautifulSoup
11
+
12
+
13
+ class APARParser:
14
+ BASE_URL = "https://www.ibm.com/support/pages/apar/"
15
+
16
+ INFO_TYPES = {
17
+ 'type1': ['APAR Information'],
18
+ 'type2': ['Error description', 'Local fix', 'Problem summary',
19
+ 'Problem conclusion', 'Temporary fix', 'Comments', 'Modules/Macros'],
20
+ 'type3': ['Fix information', 'Applicable component levels'],
21
+ 'type4': ['APAR status']
22
+ }
23
+
24
+ def __init__(self, output_dir: Path, output_format: str = 'txt'):
25
+ self.output_dir = output_dir
26
+ self.output_format = output_format
27
+ self.output_dir.mkdir(parents=True, exist_ok=True)
28
+
29
+ def fetch_apar(self, apar_number: str) -> Optional[str]:
30
+ """Fetch APAR page content"""
31
+ try:
32
+ response = requests.get(f"{self.BASE_URL}{apar_number}", timeout=30)
33
+ response.raise_for_status()
34
+ return response.text.replace('<p/>', '')
35
+ except requests.RequestException as e:
36
+ print(f"Error fetching {apar_number}: {e}")
37
+ return None
38
+
39
+ def parse_apar(self, apar_number: str, html: str) -> tuple[str, str | dict]:
40
+ """Parse APAR HTML and return status and content"""
41
+ soup = BeautifulSoup(html, 'html.parser')
42
+
43
+ if not soup.title:
44
+ return 'logon', html
45
+
46
+ if apar_number not in soup.title.text:
47
+ return 'notfound', html
48
+
49
+ if self.output_format == 'json':
50
+ content = self._extract_json(soup, apar_number)
51
+ else:
52
+ content = self._extract_content(soup, apar_number)
53
+
54
+ return 'success', content
55
+
56
+ def _extract_content(self, soup: BeautifulSoup, apar_number: str) -> str:
57
+ """Extract all APAR information"""
58
+ lines = []
59
+
60
+ # Title - split APAR number and title
61
+ title_text = soup.title.text
62
+ lines.append(title_text[:7]) # APAR number
63
+ if len(title_text) > 9:
64
+ lines.append(title_text[9:]) # Title after "OA41368: "
65
+
66
+ # Modified date
67
+ doc_info = soup.find(id='ibm-document-information')
68
+ if doc_info:
69
+ date_p = doc_info.find('p')
70
+ if date_p and len(date_p.contents) > 4:
71
+ # Keep original formatting, remove first newline char
72
+ date_text = date_p.contents[4][1:]
73
+ lines.append(f"Document Modified Date: {date_text}")
74
+
75
+ # Process sections
76
+ for h2 in soup.find_all("h2"):
77
+ section_name = h2.text
78
+
79
+ if section_name in self.INFO_TYPES['type1']:
80
+ lines.extend(self._parse_type1(h2))
81
+ elif section_name in self.INFO_TYPES['type2']:
82
+ lines.extend(self._parse_type2(h2, section_name))
83
+ elif section_name in self.INFO_TYPES['type3']:
84
+ lines.extend(self._parse_type3(h2, section_name))
85
+ elif section_name in self.INFO_TYPES['type4']:
86
+ lines.extend(self._parse_type4(h2, section_name))
87
+
88
+ return '\n'.join(lines)
89
+
90
+ def _extract_json(self, soup: BeautifulSoup, apar_number: str) -> dict:
91
+ """Extract APAR information as JSON"""
92
+ data = {'apar_number': apar_number}
93
+
94
+ # Title
95
+ title_text = soup.title.text
96
+ data['title'] = title_text[9:] if len(title_text) > 9 else ''
97
+
98
+ # Modified date
99
+ doc_info = soup.find(id='ibm-document-information')
100
+ if doc_info:
101
+ date_p = doc_info.find('p')
102
+ if date_p and len(date_p.contents) > 4:
103
+ data['modified_date'] = date_p.contents[4].strip()
104
+
105
+ # Process sections
106
+ for h2 in soup.find_all("h2"):
107
+ section_name = h2.text
108
+ section_key = section_name.lower().replace(' ', '_')
109
+
110
+ if section_name in self.INFO_TYPES['type1']:
111
+ data[section_key] = self._parse_type1_json(h2)
112
+ elif section_name in self.INFO_TYPES['type2']:
113
+ data[section_key] = self._parse_type2_json(h2)
114
+ elif section_name in self.INFO_TYPES['type3']:
115
+ data[section_key] = self._parse_type3_json(h2)
116
+ elif section_name in self.INFO_TYPES['type4']:
117
+ data[section_key] = self._parse_type4_json(h2)
118
+
119
+ return data
120
+
121
+ def _parse_type1_json(self, h2) -> dict:
122
+ """Parse APAR Information as JSON"""
123
+ info = {}
124
+ div = h2.find_next_sibling('div')
125
+ if not div:
126
+ return info
127
+
128
+ ul = div.find('ul')
129
+ if ul:
130
+ for li in ul.find_all('li'):
131
+ h3 = li.find('h3')
132
+ p = li.find('p')
133
+ if h3:
134
+ key = h3.text.lower().replace(' ', '_')
135
+ info[key] = p.text if p else ''
136
+
137
+ ul = div.find_next_sibling('ul')
138
+ if ul:
139
+ routes = {'from': [], 'to': []}
140
+ for li in ul.find_all('li'):
141
+ h3 = li.find('h3')
142
+ p = li.find('p')
143
+ if h3 and 'FROM' in h3.text:
144
+ routes['from'] = p.text.strip().split() if p else []
145
+ elif h3 and 'TO' in h3.text:
146
+ routes['to'] = p.text.strip().split() if p else []
147
+ info['sysroute'] = routes
148
+
149
+ return info
150
+
151
+ def _parse_type2_json(self, h2) -> str:
152
+ """Parse pre-formatted sections as JSON"""
153
+ ul = h2.find_next_sibling('ul')
154
+ if ul:
155
+ li = ul.find('li')
156
+ if li:
157
+ pre = li.find('pre')
158
+ if pre:
159
+ return pre.text
160
+ return ''
161
+
162
+ def _parse_type3_json(self, h2) -> list[dict]:
163
+ """Parse tabular sections as JSON"""
164
+ items = []
165
+ ul = h2.find_next_sibling('ul')
166
+ if ul:
167
+ for li in ul.find_all('li'):
168
+ parts = [child.text for child in li.children if hasattr(child, 'text')]
169
+ if parts:
170
+ items.append({'fields': parts})
171
+ return items
172
+
173
+ def _parse_type4_json(self, h2) -> str:
174
+ """Parse APAR status as JSON"""
175
+ ul = h2.find_next_sibling('ul')
176
+ if ul:
177
+ h3 = ul.find('h3')
178
+ if h3:
179
+ return h3.text
180
+ return ''
181
+
182
+ def _parse_type1(self, h2) -> list[str]:
183
+ """Parse APAR Information section"""
184
+ lines = [h2.text]
185
+ div = h2.find_next_sibling('div')
186
+ if not div:
187
+ return lines
188
+
189
+ # First ul - basic info
190
+ ul = div.find('ul')
191
+ if ul:
192
+ for li in ul.find_all('li'):
193
+ h3 = li.find('h3')
194
+ p = li.find('p')
195
+ if h3:
196
+ lines.append(f"{h3.text}\t{p.text}" if p else h3.text)
197
+
198
+ lines.append('')
199
+
200
+ # Second ul - relationships
201
+ ul = div.find_next_sibling('ul')
202
+ if ul:
203
+ for li in ul.find_all('li'):
204
+ h3 = li.find('h3')
205
+ p = li.find('p')
206
+ if h3:
207
+ lines.append(f"{h3.text}\t{p.text[1:]}" if p else h3.text)
208
+
209
+ lines.extend(['', '--', ''])
210
+ return lines
211
+
212
+ def _parse_type2(self, h2, section_name: str) -> list[str]:
213
+ """Parse sections with pre-formatted text"""
214
+ lines = [section_name]
215
+ ul = h2.find_next_sibling('ul')
216
+ if ul:
217
+ li = ul.find('li')
218
+ if li:
219
+ pre = li.find('pre')
220
+ if pre and pre.text.strip():
221
+ lines.append(pre.text.rstrip('\n'))
222
+ lines.extend(['', '--', ''])
223
+ return lines
224
+
225
+ def _parse_type3(self, h2, section_name: str) -> list[str]:
226
+ """Parse sections with tabular data"""
227
+ lines = [section_name]
228
+ ul = h2.find_next_sibling('ul')
229
+ if ul:
230
+ for li in ul.find_all('li'):
231
+ parts = [child.text for child in li.children if hasattr(child, 'text')]
232
+ lines.append('\t'.join(parts))
233
+ lines.extend(['', ''])
234
+ return lines
235
+
236
+ def _parse_type4(self, h2, section_name: str) -> list[str]:
237
+ """Parse APAR status section"""
238
+ lines = [section_name]
239
+ ul = h2.find_next_sibling('ul')
240
+ if ul:
241
+ h3 = ul.find('h3')
242
+ if h3:
243
+ lines.append(h3.text)
244
+ lines.extend(['', ''])
245
+ return lines
246
+
247
+ def save_apar(self, apar_number: str, content: str | dict, status: str = 'success'):
248
+ """Save APAR content to file"""
249
+ suffix = '' if status == 'success' else f'.{status}'
250
+
251
+ if self.output_format == 'json':
252
+ output_file = self.output_dir / f"{apar_number}{suffix}.json"
253
+ try:
254
+ if isinstance(content, dict):
255
+ output_file.write_text(json.dumps(content, indent=2, ensure_ascii=False))
256
+ else:
257
+ output_file.write_text(content, encoding='utf-8', errors='ignore')
258
+ print(f"{apar_number} {'processing..' if status == 'success' else status}")
259
+ except IOError as e:
260
+ print(f"Error saving {apar_number}: {e}")
261
+ else:
262
+ output_file = self.output_dir / f"{apar_number}{suffix}.txt"
263
+ try:
264
+ if isinstance(content, str):
265
+ content = content.rstrip('\n') + '\n\n\n'
266
+ output_file.write_text(content, encoding='utf-8', errors='ignore')
267
+ print(f"{apar_number} {'processing..' if status == 'success' else status}")
268
+ except IOError as e:
269
+ print(f"Error saving {apar_number}: {e}")
270
+
271
+ def process_apar(self, apar_number: str):
272
+ """Process single APAR"""
273
+ apar_number = apar_number.strip()[:7].upper()
274
+
275
+ html = self.fetch_apar(apar_number)
276
+ if not html:
277
+ return
278
+
279
+ status, content = self.parse_apar(apar_number, html)
280
+ self.save_apar(apar_number, content, status)
281
+
282
+ def process_file(self, input_file: Path):
283
+ """Process APAR list from file"""
284
+ try:
285
+ with input_file.open('r') as f:
286
+ for line in f:
287
+ if line.strip():
288
+ self.process_apar(line)
289
+ except IOError as e:
290
+ print(f"Error reading input file: {e}")
291
+ sys.exit(1)
292
+
293
+
294
+ def main():
295
+ parser = argparse.ArgumentParser(description='Parse IBM APAR information')
296
+ parser.add_argument('-i', '--input', type=Path, help='Input file with APAR numbers')
297
+ parser.add_argument('-o', '--output', type=Path, help='Output directory')
298
+ parser.add_argument('-a', '--apar', help='Single APAR number to process')
299
+ parser.add_argument('-f', '--format', choices=['txt', 'json'], default='txt',
300
+ help='Output format (default: txt)')
301
+ parser.add_argument('--gui', action='store_true', help='Use GUI for file selection')
302
+
303
+ args = parser.parse_args()
304
+
305
+ # GUI mode
306
+ if args.gui:
307
+ try:
308
+ import tkinter as tk
309
+ from tkinter import filedialog
310
+ root = tk.Tk()
311
+ root.withdraw()
312
+
313
+ output_dir = filedialog.askdirectory(title='Select output directory')
314
+ if not output_dir:
315
+ print('No output directory selected')
316
+ sys.exit(1)
317
+
318
+ input_file = filedialog.askopenfilename(title='Select APAR list file')
319
+ if not input_file:
320
+ print('No input file selected')
321
+ sys.exit(1)
322
+
323
+ args.output = Path(output_dir)
324
+ args.input = Path(input_file)
325
+ except ImportError:
326
+ print('tkinter not available')
327
+ sys.exit(1)
328
+
329
+ # Validate arguments
330
+ if not args.output:
331
+ parser.error('--output is required (or use --gui)')
332
+ if not args.apar and not args.input:
333
+ parser.error('Either --input or --apar must be specified')
334
+
335
+ # Process
336
+ apar_parser = APARParser(args.output, args.format)
337
+
338
+ if args.apar:
339
+ apar_parser.process_apar(args.apar)
340
+ else:
341
+ apar_parser.process_file(args.input)
342
+
343
+
344
+ if __name__ == '__main__':
345
+ main()
@@ -0,0 +1,36 @@
1
+ [project]
2
+ name = "apar-parser"
3
+ version = "1.0.0"
4
+ description = "Parse IBM APAR information from web pages"
5
+ readme = "README.md"
6
+ requires-python = ">=3.8"
7
+ license = {text = "MIT"}
8
+ authors = [
9
+ {name = "apar-parser contributors"}
10
+ ]
11
+ keywords = ["ibm", "apar", "parser", "mainframe"]
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Intended Audience :: Developers",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.8",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ ]
23
+ dependencies = [
24
+ "requests>=2.31.0",
25
+ "beautifulsoup4>=4.12.0",
26
+ ]
27
+
28
+ [project.scripts]
29
+ apar-parser = "apar_parser:main"
30
+
31
+ [project.optional-dependencies]
32
+ gui = ["tk"]
33
+
34
+ [build-system]
35
+ requires = ["hatchling"]
36
+ build-backend = "hatchling.build"