chgksuite 0.26.0b11__py3-none-any.whl → 0.26.1__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.
@@ -199,11 +199,11 @@ class LatexExporter(BaseExporter):
199
199
  firsttour = True
200
200
  for element in self.structure:
201
201
  if element[0] == "heading":
202
- tex += "\n{{\\huge {}}}\n" "\\vspace{{0.8em}}\n".format(
202
+ tex += "\n{{\\huge {}}}\n\\vspace{{0.8em}}\n".format(
203
203
  self.tex_element_layout(element[1])
204
204
  )
205
205
  if element[0] == "date":
206
- tex += "\n{{\\large {}}}\n" "\\vspace{{0.8em}}\n".format(
206
+ tex += "\n{{\\large {}}}\n\\vspace{{0.8em}}\n".format(
207
207
  self.tex_element_layout(element[1])
208
208
  )
209
209
  if element[0] in {"meta", "editor"}:
@@ -5,7 +5,12 @@ import re
5
5
  import toml
6
6
 
7
7
  from chgksuite.common import log_wrap, replace_escaped, tryint
8
- from chgksuite.composer.composer_common import BaseExporter, backtick_replace, parseimg
8
+ from chgksuite.composer.composer_common import (
9
+ BaseExporter,
10
+ backtick_replace,
11
+ parseimg,
12
+ remove_accents_standalone,
13
+ )
9
14
  from pptx import Presentation
10
15
  from pptx.dml.color import RGBColor
11
16
  from pptx.enum.text import MSO_AUTO_SIZE, MSO_VERTICAL_ANCHOR, PP_ALIGN
@@ -109,15 +114,20 @@ class PptxExporter(BaseExporter):
109
114
  r.font.underline = True
110
115
 
111
116
  def pptx_process_text(
112
- self, s, image=None, strip_brackets=True, replace_spaces=True
117
+ self,
118
+ s,
119
+ image=None,
120
+ strip_brackets=True,
121
+ replace_spaces=True,
122
+ do_not_remove_accents=False,
113
123
  ):
114
124
  hs = self.labels["question_labels"]["handout_short"]
115
125
  if isinstance(s, list):
116
126
  for i in range(len(s)):
117
127
  s[i] = self.pptx_process_text(s[i], image=image)
118
128
  return s
119
- if not self.args.do_not_remove_accents:
120
- s = s.replace("\u0301", "")
129
+ if not (self.args.do_not_remove_accents or do_not_remove_accents):
130
+ s = remove_accents_standalone(s, self.labels)
121
131
  if strip_brackets:
122
132
  s = self.remove_square_brackets(s)
123
133
  s = s.replace("]\n", "]\n\n")
@@ -408,7 +418,9 @@ class PptxExporter(BaseExporter):
408
418
  if number is not None:
409
419
  self.set_question_number(slide, number)
410
420
  p = self.init_paragraph(tf, text=handout)
411
- self.pptx_format(self.pptx_process_text(handout), p, tf, slide)
421
+ self.pptx_format(
422
+ self.pptx_process_text(handout, do_not_remove_accents=True), p, tf, slide
423
+ )
412
424
 
413
425
  def process_question_text(self, q):
414
426
  image = self._get_image_from_4s(q["question"])
@@ -126,7 +126,9 @@ class TelegramExporter(BaseExporter):
126
126
  if result:
127
127
  msg_data = json.loads(result["raw_data"])
128
128
  if msg_data["message"]["chat"]["type"] != "private":
129
- print("You should post to the PRIVATE chat, not to the channel/group")
129
+ print(
130
+ "You should post to the PRIVATE chat, not to the channel/group"
131
+ )
130
132
  continue
131
133
  self.control_chat_id = msg_data["message"]["chat"]["id"]
132
134
  self.send_api_request(
@@ -869,7 +871,7 @@ class TelegramExporter(BaseExporter):
869
871
  raise Exception("Failed to get channel ID from forwarded message")
870
872
  else:
871
873
  raise Exception("Channel ID is undefined")
872
-
874
+
873
875
  # Handle chat resolution
874
876
  if isinstance(chat_result, int):
875
877
  chat_id = chat_result
@@ -881,7 +883,9 @@ class TelegramExporter(BaseExporter):
881
883
  f"Please write a message in the discussion group with text: {self.chat_auth_uuid}"
882
884
  )
883
885
  print("This will allow me to extract the group ID automatically.")
884
- print("The bot MUST be added do the group and made admin, else it won't work!")
886
+ print(
887
+ "The bot MUST be added do the group and made admin, else it won't work!"
888
+ )
885
889
  print("=" * 50 + "\n")
886
890
 
887
891
  # Wait for a forwarded message with chat information
@@ -1158,7 +1162,10 @@ class TelegramExporter(BaseExporter):
1158
1162
  if get_text(msg_data) != self.chat_auth_uuid:
1159
1163
  continue
1160
1164
  extracted_id = msg_data["message"]["chat"]["id"]
1161
- if extracted_id == channel_numeric_id or extracted_id == self.control_chat_id:
1165
+ if (
1166
+ extracted_id == channel_numeric_id
1167
+ or extracted_id == self.control_chat_id
1168
+ ):
1162
1169
  self.logger.warning(
1163
1170
  "User posted a message in the channel, not the discussion group"
1164
1171
  )
@@ -1168,7 +1175,7 @@ class TelegramExporter(BaseExporter):
1168
1175
  "chat_id": self.control_chat_id,
1169
1176
  "text": (
1170
1177
  "⚠️ You posted a message in the channel, not in the discussion group."
1171
- )
1178
+ ),
1172
1179
  },
1173
1180
  )
1174
1181
  # Skip this message and continue waiting
@@ -1176,7 +1183,10 @@ class TelegramExporter(BaseExporter):
1176
1183
  elif entity_type == "channel":
1177
1184
  if msg_data["message"]["chat"]["id"] != self.control_chat_id:
1178
1185
  continue
1179
- if "message" in msg_data and "forward_from_chat" in msg_data["message"]:
1186
+ if (
1187
+ "message" in msg_data
1188
+ and "forward_from_chat" in msg_data["message"]
1189
+ ):
1180
1190
  forward_info = msg_data["message"]["forward_from_chat"]
1181
1191
 
1182
1192
  # Extract chat ID from the message
@@ -1187,9 +1197,10 @@ class TelegramExporter(BaseExporter):
1187
1197
  else:
1188
1198
  extracted_id = chat_id
1189
1199
  # For channels, check the type; for chats, accept any type except "channel" if check_type is False
1190
- if extracted_id and ((check_type and forward_info.get("type") == "channel") or (
1191
- not check_type
1192
- )):
1200
+ if extracted_id and (
1201
+ (check_type and forward_info.get("type") == "channel")
1202
+ or (not check_type)
1203
+ ):
1193
1204
  resolved = True
1194
1205
  self.created_at = row["created_at"]
1195
1206
  self.logger.info(
@@ -124,6 +124,9 @@ class HandoutGenerator:
124
124
 
125
125
  def generate(self):
126
126
  for block in self.parse_input(self.args.filename):
127
+ if not block:
128
+ self.blocks.append("\n\\clearpage\n")
129
+ continue
127
130
  if self.args.debug:
128
131
  print(block)
129
132
  if block.get("for_question"):
@@ -45,8 +45,29 @@ def wrap_val(key, val):
45
45
  return val.strip()
46
46
 
47
47
 
48
+ def split_array_by_value(arr, delimiter):
49
+ result = []
50
+ current_subarray = []
51
+ for item in arr:
52
+ if item == delimiter:
53
+ result.append(current_subarray)
54
+ current_subarray = []
55
+ else:
56
+ current_subarray.append(item)
57
+ result.append(current_subarray)
58
+ return result
59
+
60
+
61
+ def split_blocks(contents):
62
+ lines = contents.split("\n")
63
+ sp = ["\n".join(x) for x in split_array_by_value(lines, "---")]
64
+ if not sp[0].strip():
65
+ sp = sp[1:]
66
+ return sp
67
+
68
+
48
69
  def parse_handouts(contents):
49
- blocks = contents.split("\n---\n")
70
+ blocks = split_blocks(contents)
50
71
  result = []
51
72
  for block_ in blocks:
52
73
  block = block_.strip()
chgksuite/parser.py CHANGED
@@ -3,6 +3,7 @@
3
3
  import base64
4
4
  import codecs
5
5
  import datetime
6
+ import hashlib
6
7
  import itertools
7
8
  import json
8
9
  import os
@@ -13,12 +14,14 @@ import subprocess
13
14
  import sys
14
15
  import tempfile
15
16
  import urllib
17
+ import time
16
18
 
17
19
  import bs4
18
20
  import chardet
19
21
  import dashtable
20
22
  import mammoth
21
23
  import pypandoc
24
+ import requests
22
25
  import toml
23
26
  from bs4 import BeautifulSoup
24
27
  from parse import parse
@@ -26,11 +29,11 @@ from parse import parse
26
29
  import chgksuite.typotools as typotools
27
30
  from chgksuite.common import (
28
31
  QUESTION_LABELS,
29
- DefaultArgs,
30
32
  DefaultNamespace,
31
33
  DummyLogger,
32
34
  check_question,
33
35
  compose_4s,
36
+ get_chgksuite_dir,
34
37
  get_lastdir,
35
38
  init_logger,
36
39
  load_settings,
@@ -40,6 +43,7 @@ from chgksuite.common import (
40
43
  from chgksuite.composer import gui_compose
41
44
  from chgksuite.composer.composer_common import make_filename
42
45
  from chgksuite.parser_db import chgk_parse_db
46
+ from chgksuite.typotools import re_url
43
47
  from chgksuite.typotools import remove_excessive_whitespace as rew
44
48
 
45
49
  ENC = sys.stdout.encoding or "utf8"
@@ -107,7 +111,7 @@ class ChgkParser:
107
111
 
108
112
  def __init__(self, defaultauthor=None, args=None, logger=None):
109
113
  self.defaultauthor = defaultauthor
110
- args = args or DefaultArgs()
114
+ args = args or DefaultNamespace()
111
115
  self.regexes = load_regexes(args.regexes)
112
116
  self.logger = logger or init_logger("parser")
113
117
  self.args = args
@@ -121,6 +125,148 @@ class ChgkParser:
121
125
  if self.args.language == "en":
122
126
  self.args.typography_quotes = "off"
123
127
 
128
+ def _setup_image_cache(self):
129
+ """Setup image download cache directory and load existing cache"""
130
+ if not hasattr(self, "_image_cache"):
131
+ self.image_cache_dir = os.path.join(
132
+ get_chgksuite_dir(), "downloaded_images"
133
+ )
134
+ os.makedirs(self.image_cache_dir, exist_ok=True)
135
+
136
+ self.image_cache_file = os.path.join(
137
+ get_chgksuite_dir(), "image_download_cache.json"
138
+ )
139
+ if os.path.isfile(self.image_cache_file):
140
+ try:
141
+ with open(self.image_cache_file, encoding="utf8") as f:
142
+ self._image_cache = json.load(f)
143
+ except (json.JSONDecodeError, OSError):
144
+ self._image_cache = {}
145
+ else:
146
+ self._image_cache = {}
147
+
148
+ def _download_image(self, url):
149
+ """Download image from URL and return local filename"""
150
+ self._setup_image_cache()
151
+ url = url.replace("\\", "")
152
+
153
+ # Check cache first
154
+ url_hash = hashlib.sha256(url.encode("utf-8")).hexdigest()[:20]
155
+ if url_hash in self._image_cache:
156
+ cached_filename = self._image_cache[url_hash]
157
+ cached_path = os.path.join(self.image_cache_dir, cached_filename)
158
+ if os.path.isfile(cached_path):
159
+ return cached_path
160
+
161
+ # Determine file extension
162
+ parsed_url = urllib.parse.urlparse(url)
163
+ path_lower = parsed_url.path.lower()
164
+ ext = None
165
+ for image_ext in [".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".svg"]:
166
+ if path_lower.endswith(image_ext):
167
+ ext = image_ext
168
+ break
169
+
170
+ if not ext:
171
+ # Try to guess from URL structure
172
+ if any(
173
+ img_ext in path_lower
174
+ for img_ext in [
175
+ ".jpg",
176
+ ".jpeg",
177
+ ".png",
178
+ ".webp",
179
+ ".gif",
180
+ ".bmp",
181
+ ".svg",
182
+ ]
183
+ ):
184
+ for image_ext in [
185
+ ".jpg",
186
+ ".jpeg",
187
+ ".png",
188
+ ".webp",
189
+ ".gif",
190
+ ".bmp",
191
+ ".svg",
192
+ ]:
193
+ if image_ext in path_lower:
194
+ ext = image_ext
195
+ break
196
+ else:
197
+ ext = ".jpg" # Default extension
198
+
199
+ filename = url_hash + ext
200
+ filepath = os.path.join(self.image_cache_dir, filename)
201
+
202
+ try:
203
+ self.logger.info(f"Downloading image from {url}")
204
+ headers = {
205
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
206
+ "Accept": "image/png,image/jpeg,image/webp,image/gif,image/*,*/*;q=0.8",
207
+ "Accept-Language": "en-US,en;q=0.9",
208
+ "Accept-Encoding": "gzip, deflate, br",
209
+ "Connection": "keep-alive",
210
+ "Upgrade-Insecure-Requests": "1",
211
+ }
212
+ response = requests.get(url, timeout=30, stream=True, headers=headers)
213
+ response.raise_for_status()
214
+ time.sleep(0.5) # rate limiting
215
+
216
+ with open(filepath, "wb") as f:
217
+ for chunk in response.iter_content(chunk_size=8192):
218
+ f.write(chunk)
219
+
220
+ # Update cache
221
+ self._image_cache[url_hash] = filename
222
+ with open(self.image_cache_file, "w", encoding="utf8") as f:
223
+ json.dump(self._image_cache, f, indent=2, sort_keys=True)
224
+
225
+ return filepath
226
+
227
+ except Exception as e:
228
+ self.logger.warning(f"Failed to download image from {url}: {e}")
229
+ return None
230
+
231
+ def _process_images_in_text(self, text):
232
+ """Process text to find image URLs and replace them with local references"""
233
+ if not text or not getattr(self.args, "download_images", False):
234
+ return text
235
+
236
+ if isinstance(text, list):
237
+ return [self._process_images_in_text(item) for item in text]
238
+
239
+ if not isinstance(text, str):
240
+ return text
241
+
242
+ # Find all URLs in the text
243
+ for match in re_url.finditer(text):
244
+ url = match.group(0)
245
+ url_lower = url.lower()
246
+
247
+ # Check if it's a direct image URL
248
+ if any(
249
+ url_lower.endswith(ext)
250
+ for ext in [".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".svg"]
251
+ ):
252
+ local_filename = self._download_image(url)
253
+ if local_filename:
254
+ # Replace URL with chgksuite image syntax
255
+ img_reference = f"(img {local_filename})"
256
+ text = text.replace(url, img_reference)
257
+
258
+ return text
259
+
260
+ def _process_question_images(self, question):
261
+ """Process a question dict to download images from URLs"""
262
+ if not getattr(self.args, "download_images", False):
263
+ return
264
+
265
+ # Process all fields except 'source'
266
+ for field in question:
267
+ if field != "source":
268
+ question[field] = self._process_images_in_text(question[field])
269
+
124
270
  def merge_to_previous(self, index):
125
271
  target = index - 1
126
272
  if self.structure[target][1]:
@@ -513,7 +659,19 @@ class ChgkParser:
513
659
  ):
514
660
  self.merge_to_next(0)
515
661
 
516
- for _id, element in enumerate(self.structure):
662
+ if debug:
663
+ with codecs.open("debug_3a.json", "w", "utf8") as f:
664
+ f.write(
665
+ json.dumps(
666
+ list(enumerate(self.structure)), ensure_ascii=False, indent=4
667
+ )
668
+ )
669
+
670
+ idx = 0
671
+ cycle = -1
672
+ while idx < len(self.structure):
673
+ cycle += 1
674
+ element = self.structure[idx]
517
675
  if element[0] == "":
518
676
  element[0] = "meta"
519
677
  if element[0] in regexes and element[0] not in [
@@ -525,31 +683,42 @@ class ChgkParser:
525
683
  try:
526
684
  num = regexes["question"].search(element[1])
527
685
  if num and num.group("number"):
528
- self.structure.insert(_id, ["number", num.group("number")])
686
+ self.structure.insert(idx, ["number", num.group("number")])
687
+ idx += 1
529
688
  except Exception as e:
689
+ num = None
530
690
  sys.stderr.write(
531
- f"exception at line 445 of parser: {type(e)} {e}\n"
691
+ f"exception at setting number: {type(e)} {e}\n"
532
692
  )
533
693
  if (
534
- not num
535
- and ("нулевой вопрос" in element[1].lower())
694
+ num is None
695
+ or num and not num.group("number")
696
+ ) and (
697
+ ("нулевой вопрос" in element[1].lower())
536
698
  or ("разминочный вопрос" in element[1].lower())
537
699
  ):
538
- self.structure.insert(_id, ["number", "0"])
700
+ self.structure.insert(idx, ["number", "0"])
701
+ idx += 1
539
702
  if element[0] == "question":
540
703
  lines = element[1].split(SEP)
541
704
  for i, line in enumerate(lines):
542
705
  if regexes["question"].search(line):
543
706
  lines[i] = regexes["question"].sub("", line, 1)
544
707
  element[1] = SEP.join([x.strip() for x in lines if x.strip()])
708
+ before_replacement = None
545
709
  else:
546
710
  before_replacement = element[1]
547
711
  element[1] = regexes[element[0]].sub("", element[1], 1)
548
712
  if element[1].startswith(SEP):
549
713
  element[1] = element[1][len(SEP) :]
550
714
  # TODO: переделать корявую обработку авторки на нормальную
551
- if element[0] == "author" and "авторка:" in before_replacement.lower():
715
+ if (
716
+ element[0] == "author"
717
+ and before_replacement
718
+ and "авторка:" in before_replacement.lower()
719
+ ):
552
720
  element[1] = "!!Авторка" + element[1]
721
+ idx += 1
553
722
 
554
723
  if debug:
555
724
  with codecs.open("debug_4.json", "w", "utf8") as f:
@@ -648,6 +817,7 @@ class ChgkParser:
648
817
  ):
649
818
  if self.defaultauthor and "author" not in current_question:
650
819
  current_question["author"] = self.defaultauthor
820
+ self._process_question_images(current_question)
651
821
  check_question(current_question, logger=logger)
652
822
  final_structure.append(["Question", current_question])
653
823
  current_question = {}
@@ -681,6 +851,7 @@ class ChgkParser:
681
851
  if current_question != {}:
682
852
  if self.defaultauthor and "author" not in current_question:
683
853
  current_question["author"] = self.defaultauthor
854
+ self._process_question_images(current_question)
684
855
  check_question(current_question, logger=logger)
685
856
  final_structure.append(["Question", current_question])
686
857
 
@@ -724,6 +895,11 @@ class ChgkParser:
724
895
  elif element[0] == "tour" and self.args.tour_numbers_as_words == "on":
725
896
  element[1] = f"{self.TOUR_NUMBERS_AS_WORDS[tour_cnt]} тур"
726
897
  tour_cnt += 1
898
+ elif element[0] not in ["Question", "source"] and getattr(
899
+ self.args, "download_images", False
900
+ ):
901
+ # Process images in metadata fields (excluding source)
902
+ element[1] = self._process_images_in_text(element[1])
727
903
 
728
904
  if debug:
729
905
  with codecs.open("debug_final.json", "w", "utf8") as f:
@@ -733,7 +909,8 @@ class ChgkParser:
733
909
 
734
910
  def chgk_parse(text, defaultauthor=None, args=None):
735
911
  parser = ChgkParser(defaultauthor=defaultauthor, args=args)
736
- return parser.parse(text)
912
+ parsed = parser.parse(text)
913
+ return parsed
737
914
 
738
915
 
739
916
  class UnknownEncodingException(Exception):
@@ -779,7 +956,7 @@ def ensure_line_breaks(tag):
779
956
 
780
957
  def chgk_parse_docx(docxfile, defaultauthor="", args=None, logger=None):
781
958
  logger = logger or DummyLogger()
782
- args = args or DefaultArgs()
959
+ args = args or DefaultNamespace()
783
960
  for_ol = {}
784
961
 
785
962
  def get_number(tag):
@@ -807,6 +984,11 @@ def chgk_parse_docx(docxfile, defaultauthor="", args=None, logger=None):
807
984
  else:
808
985
  with open(docxfile, "rb") as docx_file:
809
986
  html = mammoth.convert_to_html(docx_file).value
987
+ if args.debug:
988
+ with codecs.open(
989
+ os.path.join(target_dir, "debugdebug.pydocx"), "w", "utf8"
990
+ ) as dbg:
991
+ dbg.write(html)
810
992
  input_docx = (
811
993
  html.replace("</strong><strong>", "")
812
994
  .replace("</em><em>", "")
chgksuite/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.26.0b11"
1
+ __version__ = "0.26.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chgksuite
3
- Version: 0.26.0b11
3
+ Version: 0.26.1
4
4
  Summary: A package for chgk automation
5
5
  Home-page: https://gitlab.com/peczony/chgksuite
6
6
  Author: Alexander Pecheny
@@ -1,34 +1,34 @@
1
1
  chgksuite/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  chgksuite/__main__.py,sha256=0-_jfloveTW3SZYW5XEagbyaHKGCiDhGNgcLxsT_dMs,140
3
- chgksuite/cli.py,sha256=6mmd_TcGaBykW9tye9km6hp6STNF0LZzlleDRfOh4-o,40276
3
+ chgksuite/cli.py,sha256=iZGpx4-_vTxdKsKsRXCu7vjpiFhR_oyhLRGXyvGl7i8,40901
4
4
  chgksuite/common.py,sha256=VkOhoBA_P3qY5VgtvfrBjOsm5uVNL2s2Th2AhGB2pg8,11207
5
- chgksuite/parser.py,sha256=vouLNwSAoITBa7E8fye2Y0ZnN-WNapkoqW93qvpIkY0,39114
5
+ chgksuite/parser.py,sha256=rv8n1ff0xpcHHMA1fsjKmJZ5EcFupChHwXch8pncCNk,45855
6
6
  chgksuite/parser_db.py,sha256=W1--OcDnx18mehH1T2ISHu3Saeq-9mqHo-xJopNySXI,11135
7
7
  chgksuite/trello.py,sha256=BG1Qb_W7Uu4o3Mfc_tK71ElU8ysdSplGlj_sAKfvUn4,14730
8
8
  chgksuite/typotools.py,sha256=Jdk65Wn_bXqpQtOT7PkBZyD2ZG1MBeeZFPMzcHEPkf4,12771
9
- chgksuite/version.py,sha256=ibWtFzx4N9iZIKpM-hhKG9nm0d5VsBrsioedlZ33Dgg,26
9
+ chgksuite/version.py,sha256=u0eTmljUU0kO8AAW-e1ESQ49mK2SuhpCy7eCliBLlDU,23
10
10
  chgksuite/vulture_whitelist.py,sha256=P__p_X0zt10ivddIf81uyxsobV14vFg8uS2lt4foYpc,3582
11
11
  chgksuite/composer/__init__.py,sha256=MAOVZIYXmZmD6nNQSo9DueV6b5RgxF7_HGeLvsAhMJs,6490
12
- chgksuite/composer/chgksuite_parser.py,sha256=MFcLUWbccMqo3OYEuaAIA0loEvWM_PNS9vR7c1z_c60,8843
13
- chgksuite/composer/composer_common.py,sha256=S5ipehxep6LlGIZ9dcBnifbVaMYXijMhq6-pUHRISo8,15309
12
+ chgksuite/composer/chgksuite_parser.py,sha256=XxopZLVY4DQxgxw2a6NT70ga_2XZHzuvn58RnlriiqE,8847
13
+ chgksuite/composer/composer_common.py,sha256=AQ14sX2Luo2TAxdMaPCV8m0XckE0Bm14kQ4WO3gKVqw,15962
14
14
  chgksuite/composer/db.py,sha256=71cINE_V8s6YidvqpmBmmlWbcXraUEGZA1xpVFAUENw,8173
15
- chgksuite/composer/docx.py,sha256=5MASXACM-ztWrr3VdO8HZ-W-hWWQ5TY1jXMsCQIufGc,18346
16
- chgksuite/composer/latex.py,sha256=WtLdUICxeX4_5vHEJRF0YhFLpTsOUwBkQFunQS488FA,9248
15
+ chgksuite/composer/docx.py,sha256=CrefvNynaPyJ5qni9C259LL8DapWobUuLBxbwvPQWCM,23095
16
+ chgksuite/composer/latex.py,sha256=_IKylzdDcokgXYvvxsVSiq-Ba5fVirWcfCp6eOyx6zQ,9242
17
17
  chgksuite/composer/lj.py,sha256=nty3Zs3N1H0gNK378U04aAHo71_5cABhCM1Mm9jiUEA,15213
18
18
  chgksuite/composer/openquiz.py,sha256=4adZewvRXpZhKrh9H4epKoMMDhmki9US55-Q0LcpZW0,7019
19
- chgksuite/composer/pptx.py,sha256=KszvRAbSbKmbPS257YcYhE6wB1gSf7gIXRH8VwiSqyg,23775
19
+ chgksuite/composer/pptx.py,sha256=U7Ekfn_Wvfzp6VOHizom870KdCcReWl4ynw_9DtHRac,23988
20
20
  chgksuite/composer/reddit.py,sha256=-Eg4CqMHhyGGfCteVwdQdtE1pfUXQ42XcP5OYUrBXmo,3878
21
21
  chgksuite/composer/stats.py,sha256=GbraSrjaZ8Mc2URs5aGAsI4ekboAKzlJJOqsbe96ELA,3995
22
- chgksuite/composer/telegram.py,sha256=ZiCBFXxKJJ5vD5ttkfvmURd4Q9Gif6eT8R93nz-LAq0,47146
22
+ chgksuite/composer/telegram.py,sha256=hfU0EF0UhAq3rR8etyQN5S1CUdOnItluc7vuX3UY_TE,47387
23
23
  chgksuite/composer/telegram_bot.py,sha256=xT5D39m4zGmIbHV_ZfyQ9Rc8PAmG2V5FGUeDKpkgyTw,3767
24
24
  chgksuite/composer/telegram_parser.py,sha256=50WqOuvzzdMJJm5wsSLS49oURAQRYToPnbPJjQbMYC4,8096
25
25
  chgksuite/handouter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  chgksuite/handouter/gen.py,sha256=Rnrcg1APBt35Joloce-q3gA4OJUXudH8snbGLSmVXJw,5045
27
27
  chgksuite/handouter/installer.py,sha256=u4jQKeCn0VjOnaDFezx35g8oRjji5edvYGj5xSHCEW4,7574
28
28
  chgksuite/handouter/pack.py,sha256=H-Ln1JqKK2u3jFI5wwsh7pQdJBpQJ-s8gV9iECQ3kgU,2504
29
- chgksuite/handouter/runner.py,sha256=hrq8aoSwX0xCvqHL9c3DPwo3kya9Kwc4GbOErBNj9qg,8275
29
+ chgksuite/handouter/runner.py,sha256=SHxGCAbQ9s6h23IaqNhJtWN0VMjM9hwy6oIgt0-WuD0,8380
30
30
  chgksuite/handouter/tex_internals.py,sha256=GtcoGd1FD4Oi9nnMzb2KcdCNIHNQV1fcKmQyW9AXA1k,1394
31
- chgksuite/handouter/utils.py,sha256=ZzUfSlK1oug-K3e5SlntuRBVnOrGFmnJU8V2cCfILoY,1730
31
+ chgksuite/handouter/utils.py,sha256=0RoECvHzfmwWnRL2jj4WKh22oTCPh2MXid_a9ShplDA,2243
32
32
  chgksuite/resources/cheader.tex,sha256=Jfe3LESk0VIV0HCObbajSQpEMljaIDAIEGSs6YY9rTk,3454
33
33
  chgksuite/resources/fix-unnumbered-sections.sty,sha256=FN6ZSWC6MvoRoThPm5AxCF98DdgcxbxyBYG6YImM05s,1409
34
34
  chgksuite/resources/labels_by.toml,sha256=kSbIYBnxneRJAcDmeoFGlPpjogWuoMO11302p0BxB-s,824
@@ -51,9 +51,9 @@ chgksuite/resources/regexes_uz_cyr.json,sha256=GlGkA6ys2e1-9xPcgOL8Sy9ZuDZHQZXp_
51
51
  chgksuite/resources/template.docx,sha256=Do29TAsg3YbH0rRSaXhVzKEoh4pwXkklW_idWA34HVE,11189
52
52
  chgksuite/resources/template.pptx,sha256=hEFWqE-yYpwZ8ejrMCJIPEyoMT3eDqaqtiEeQ7I4fyk,29777
53
53
  chgksuite/resources/trello.json,sha256=M5Q9JR-AAJF1u16YtNAxDX-7c7VoVTXuq4POTqYvq8o,555
54
- chgksuite-0.26.0b11.dist-info/licenses/LICENSE,sha256=_a1yfntuPmctLsuiE_08xMSORuCfGS8X5hQph2U_PUw,1081
55
- chgksuite-0.26.0b11.dist-info/METADATA,sha256=wSSFZjHl01qih4-d1kdY8g0R1TaSE3l2VnAIGct-DOE,1342
56
- chgksuite-0.26.0b11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
- chgksuite-0.26.0b11.dist-info/entry_points.txt,sha256=lqjX6ULQZGDt0rgouTXBuwEPiwKkDQkSiNsT877A_Jg,54
58
- chgksuite-0.26.0b11.dist-info/top_level.txt,sha256=cSWiRBOGZW9nIO6Rv1IrEfwPgV2ZWs87QV9wPXeBGqM,10
59
- chgksuite-0.26.0b11.dist-info/RECORD,,
54
+ chgksuite-0.26.1.dist-info/licenses/LICENSE,sha256=_a1yfntuPmctLsuiE_08xMSORuCfGS8X5hQph2U_PUw,1081
55
+ chgksuite-0.26.1.dist-info/METADATA,sha256=mrofe1zLMG4aMVRK_d0FLVa0TYpKNoLLrSaJGdVmowk,1339
56
+ chgksuite-0.26.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
+ chgksuite-0.26.1.dist-info/entry_points.txt,sha256=lqjX6ULQZGDt0rgouTXBuwEPiwKkDQkSiNsT877A_Jg,54
58
+ chgksuite-0.26.1.dist-info/top_level.txt,sha256=cSWiRBOGZW9nIO6Rv1IrEfwPgV2ZWs87QV9wPXeBGqM,10
59
+ chgksuite-0.26.1.dist-info/RECORD,,