5 from io
import StringIO
7 def open_compressed_file(filename
):
8 """ Open a file, trying various compression methods if available. """
9 if filename
.endswith('.gz'):
10 return gzip
.open(filename
, 'rt', encoding
='utf-8')
11 elif filename
.endswith('.bz2'):
12 return bz2
.open(filename
, 'rt', encoding
='utf-8')
13 elif filename
.endswith('.xz') or filename
.endswith('.lzma'):
14 return lzma
.open(filename
, 'rt', encoding
='utf-8')
16 return open(filename
, 'r', encoding
='utf-8')
19 """ Parse a line in the format 'var: value' and return the key and value. """
20 parts
= line
.split(":", 1)
22 key
= parts
[0].strip()
23 value
= parts
[1].strip()
27 def validate_integer(value
, variable_name
, line_number
):
28 """ Validate and convert a value to an integer. """
32 raise ValueError(f
"{variable_name} on line {line_number} should be an integer, but got '{value}'.")
34 def parse_file(filename
, validate_only
=False, verbose
=False):
35 with
open_compressed_file(filename
) as file:
36 lines
= file.readlines()
37 return parse_lines(lines
, validate_only
, verbose
)
39 def parse_string(data
, validate_only
=False, verbose
=False):
40 lines
= StringIO(data
).readlines()
41 return parse_lines(lines
, validate_only
, verbose
)
43 def parse_lines(lines
, validate_only
=False, verbose
=False):
45 current_service
= None
47 in_message_list
= False
48 in_message_thread
= False
50 in_message_post
= False
52 in_message_body
= False
53 in_comment_section
= False
54 in_include_service
= False
55 in_include_users
= False
56 in_include_messages
= False
60 current_message
= None
64 def parse_include_files(file_list
):
65 included_services
= []
66 for include_file
in file_list
:
67 included_services
.extend(parse_file(include_file
, validate_only
, verbose
))
68 return included_services
70 def parse_include_users(file_list
):
72 for include_file
in file_list
:
73 included_users
= parse_file(include_file
, validate_only
, verbose
)
74 for service
in included_users
:
75 users
.update(service
['Users'])
78 def parse_include_messages(file_list
):
80 for include_file
in file_list
:
81 included_messages
= parse_file(include_file
, validate_only
, verbose
)
82 for service
in included_messages
:
83 messages
.extend(service
['MessageThreads'])
87 for line_number
, line
in enumerate(lines
, 1):
89 if line
== "--- Include Service Start ---":
90 in_include_service
= True
93 print(f
"Line {line_number}: {line} (Starting include service section)")
95 elif line
== "--- Include Service End ---":
96 in_include_service
= False
98 print(f
"Line {line_number}: {line} (Ending include service section)")
99 services
.extend(parse_include_files(include_files
))
101 elif in_include_service
:
102 include_files
.append(line
)
104 print(f
"Line {line_number}: {line} (Including file for service)")
106 elif line
== "--- Include Users Start ---":
107 in_include_users
= True
110 print(f
"Line {line_number}: {line} (Starting include users section)")
112 elif line
== "--- Include Users End ---":
113 in_include_users
= False
115 print(f
"Line {line_number}: {line} (Ending include users section)")
117 current_service
['Users'].update(parse_include_users(include_files
))
119 elif in_include_users
:
120 include_files
.append(line
)
122 print(f
"Line {line_number}: {line} (Including file for users)")
124 elif line
== "--- Include Messages Start ---":
125 in_include_messages
= True
128 print(f
"Line {line_number}: {line} (Starting include messages section)")
130 elif line
== "--- Include Messages End ---":
131 in_include_messages
= False
133 print(f
"Line {line_number}: {line} (Ending include messages section)")
135 current_service
['MessageThreads'].extend(parse_include_messages(include_files
))
137 elif in_include_messages
:
138 include_files
.append(line
)
140 print(f
"Line {line_number}: {line} (Including file for messages)")
142 elif line
== "--- Start Archive Service ---":
143 current_service
= {'Users': {}, 'MessageThreads': [], 'Interactions': []}
145 print(f
"Line {line_number}: {line} (Starting new archive service)")
147 elif line
== "--- End Archive Service ---":
148 services
.append(current_service
)
149 current_service
= None
151 print(f
"Line {line_number}: {line} (Ending archive service)")
153 elif line
== "--- Start Comment Section ---":
154 in_comment_section
= True
156 print(f
"Line {line_number}: {line} (Starting comment section)")
158 elif line
== "--- End Comment Section ---":
159 in_comment_section
= False
161 print(f
"Line {line_number}: {line} (Ending comment section)")
163 elif in_comment_section
:
165 print(f
"Line {line_number}: {line} (Comment)")
167 elif current_service
is not None:
168 key
, value
= parse_line(line
)
170 current_service
['Entry'] = validate_integer(value
, "Entry", line_number
)
171 elif key
== "Service":
172 current_service
['Service'] = value
173 elif line
== "--- Start User List ---":
176 print(f
"Line {line_number}: {line} (Starting user list)")
178 elif line
== "--- End User List ---":
181 print(f
"Line {line_number}: {line} (Ending user list)")
183 elif line
== "--- Start User Info ---":
186 print(f
"Line {line_number}: {line} (Starting user info)")
188 elif line
== "--- End User Info ---":
192 print(f
"Line {line_number}: {line} (Ending user info)")
194 elif line
== "--- Start Message List ---":
195 in_message_list
= True
197 print(f
"Line {line_number}: {line} (Starting message list)")
199 elif line
== "--- End Message List ---":
200 in_message_list
= False
202 print(f
"Line {line_number}: {line} (Ending message list)")
204 elif line
== "--- Start Message Thread ---":
205 in_message_thread
= True
206 current_thread
= {'Title': '', 'Messages': []}
209 print(f
"Line {line_number}: {line} (Starting message thread)")
211 elif line
== "--- End Message Thread ---":
212 in_message_thread
= False
213 current_service
['MessageThreads'].append(current_thread
)
214 current_thread
= None
216 print(f
"Line {line_number}: {line} (Ending message thread)")
218 elif line
== "--- Start Message Post ---":
219 in_message_post
= True
222 print(f
"Line {line_number}: {line} (Starting message post)")
224 elif line
== "--- End Message Post ---":
225 in_message_post
= False
227 current_thread
['Messages'].append(current_message
)
228 current_message
= None
230 print(f
"Line {line_number}: {line} (Ending message post)")
232 elif in_message_list
and key
== "Interactions":
233 current_service
['Interactions'] = [interaction
.strip() for interaction
in value
.split(",")]
235 print(f
"Line {line_number}: Interactions set to {current_service['Interactions']}")
237 if in_user_list
and in_user_info
:
239 user_id
= validate_integer(value
, "User", line_number
)
240 current_service
['Users'][user_id
] = {'Bio': ""}
242 print(f
"Line {line_number}: User ID set to {user_id}")
244 if user_id
is not None:
245 current_service
['Users'][user_id
]['Name'] = value
247 print(f
"Line {line_number}: Name set to {value}")
248 elif key
== "Handle":
249 if user_id
is not None:
250 current_service
['Users'][user_id
]['Handle'] = value
252 print(f
"Line {line_number}: Handle set to {value}")
253 elif key
== "Location":
254 if user_id
is not None:
255 current_service
['Users'][user_id
]['Location'] = value
257 print(f
"Line {line_number}: Location set to {value}")
258 elif key
== "Joined":
259 if user_id
is not None:
260 current_service
['Users'][user_id
]['Joined'] = value
262 print(f
"Line {line_number}: Joined date set to {value}")
263 elif key
== "Birthday":
264 if user_id
is not None:
265 current_service
['Users'][user_id
]['Birthday'] = value
267 print(f
"Line {line_number}: Birthday set to {value}")
268 elif line
== "--- Start Bio Body ---":
269 if user_id
is not None:
273 print(f
"Line {line_number}: Starting bio body")
274 elif line
== "--- End Bio Body ---":
275 if user_id
is not None and current_bio
is not None:
276 current_service
['Users'][user_id
]['Bio'] = "\n".join(current_bio
)
280 print(f
"Line {line_number}: Ending bio body")
281 elif in_bio_body
and current_bio
is not None:
282 current_bio
.append(line
)
284 print(f
"Line {line_number}: Adding to bio body: {line}")
285 elif in_message_list
and in_message_thread
:
287 current_thread
['Thread'] = validate_integer(value
, "Thread", line_number
)
289 print(f
"Line {line_number}: Thread ID set to {value}")
291 current_thread
['Title'] = value
293 print(f
"Line {line_number}: Title set to {value}")
294 elif key
== "Author":
295 current_message
['Author'] = value
297 print(f
"Line {line_number}: Author set to {value}")
299 current_message
['Time'] = value
301 print(f
"Line {line_number}: Time set to {value}")
304 if message_type
not in current_service
['Interactions']:
305 raise ValueError(f
"Unexpected message type '{message_type}' found on line {line_number}. Expected one of {current_service['Interactions']}")
306 current_message
['Type'] = message_type
308 print(f
"Line {line_number}: Type set to {message_type}")
310 post_value
= validate_integer(value
, "Post", line_number
)
311 current_message
['Post'] = post_value
312 if 'post_ids' not in current_thread
:
313 current_thread
['post_ids'] = set()
314 current_thread
['post_ids'].add(post_value
)
316 print(f
"Line {line_number}: Post ID set to {post_value}")
317 elif key
== "Nested":
318 nested_value
= validate_integer(value
, "Nested", line_number
)
319 if nested_value
!= 0 and nested_value
not in current_thread
.get('post_ids', set()):
321 f
"Nested value '{nested_value}' on line {line_number} does not match any existing Post values in the current thread. Existing Post IDs: {list(current_thread.get('post_ids', set()))}"
323 current_message
['Nested'] = nested_value
325 print(f
"Line {line_number}: Nested set to {nested_value}")
326 elif line
== "--- Start Message Body ---":
327 if current_message
is not None:
328 current_message
['Message'] = []
329 in_message_body
= True
331 print(f
"Line {line_number}: Starting message body")
332 elif line
== "--- End Message Body ---":
333 if current_message
is not None and 'Message' in current_message
:
334 current_message
['Message'] = "\n".join(current_message
['Message'])
335 in_message_body
= False
337 print(f
"Line {line_number}: Ending message body")
338 elif in_message_body
and current_message
is not None and 'Message' in current_message
:
339 current_message
['Message'].append(line
)
341 print(f
"Line {line_number}: Adding to message body: {line}")
342 except Exception as e
:
344 return False, f
"Error: {str(e)}", lines
[line_number
- 1]
353 def display_services(services
):
354 for service
in services
:
355 print(f
"Service Entry: {service['Entry']}")
356 print(f
"Service: {service['Service']}")
357 print(f
"Interactions: {', '.join(service['Interactions'])}")
359 for user_id
, user_info
in service
['Users'].items():
360 print(f
" User ID: {user_id}")
361 print(f
" Name: {user_info['Name']}")
362 print(f
" Handle: {user_info['Handle']}")
363 print(f
" Location: {user_info.get('Location', '')}")
364 print(f
" Joined: {user_info.get('Joined', '')}")
365 print(f
" Birthday: {user_info.get('Birthday', '')}")
366 print(f
" Bio: {user_info.get('Bio', '').strip()}")
368 print("Message Threads:")
369 for idx
, thread
in enumerate(service
['MessageThreads']):
370 print(f
" --- Message Thread {idx+1} ---")
372 print(f
" Title: {thread['Title']}")
373 for message
in thread
['Messages']:
374 print(f
" {message['Author']} ({message['Time']}): [{message['Type']}] Post ID: {message['Post']} Nested: {message['Nested']}")
375 print(f
" {message['Message'].strip()}")
378 def to_json(services
):
379 """ Convert the services data structure to JSON """
380 return json
.dumps(services
, indent
=2)
382 def from_json(json_str
):
383 """ Convert a JSON string back to the services data structure """
384 return json
.loads(json_str
)