RNApolis 0.6.1__py3-none-any.whl → 0.7.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.
rnapolis/adapter.py ADDED
@@ -0,0 +1,537 @@
1
+ #! /usr/bin/env python
2
+ import argparse
3
+ import csv
4
+ import logging
5
+ import os
6
+ from enum import Enum
7
+ from typing import Dict, List, Optional, Tuple
8
+
9
+ import orjson
10
+
11
+ from rnapolis.common import (
12
+ BR,
13
+ BaseInteractions,
14
+ BasePair,
15
+ BasePhosphate,
16
+ BaseRibose,
17
+ BPh,
18
+ BpSeq,
19
+ LeontisWesthof,
20
+ OtherInteraction,
21
+ Residue,
22
+ ResidueAuth,
23
+ Stacking,
24
+ StackingTopology,
25
+ Structure2D,
26
+ )
27
+ from rnapolis.parser import read_3d_structure
28
+ from rnapolis.tertiary import Mapping2D3D, Structure3D
29
+ from rnapolis.util import handle_input_file
30
+
31
+
32
+ class ExternalTool(Enum):
33
+ FR3D = "fr3d"
34
+ DSSR = "dssr"
35
+
36
+
37
+ logging.basicConfig(level=os.getenv("LOGLEVEL", "INFO").upper())
38
+
39
+
40
+ def parse_unit_id(nt: str) -> Residue:
41
+ """Parse FR3D unit ID format into a Residue object."""
42
+ fields = nt.split("|")
43
+ icode = fields[7] if len(fields) >= 8 and fields[7] != "" else None
44
+ auth = ResidueAuth(fields[2], int(fields[4]), icode, fields[3])
45
+ return Residue(None, auth)
46
+
47
+
48
+ def unify_classification(fr3d_name: str) -> tuple:
49
+ """Convert FR3D classification to internal format."""
50
+ original_name = fr3d_name # Keep for logging
51
+
52
+ # Handle 'n' prefix (e.g., ncWW -> cWW, ns55 -> s55)
53
+ if fr3d_name.startswith("n"):
54
+ fr3d_name = fr3d_name[1:]
55
+ logging.debug(
56
+ f"Detected 'n' prefix: removed from {original_name} -> {fr3d_name}"
57
+ )
58
+
59
+ # Handle alternative base pairs with 'a' suffix (e.g., cWWa -> cWW)
60
+ if len(fr3d_name) >= 3 and fr3d_name.endswith("a"):
61
+ fr3d_name = fr3d_name[:-1] # Remove the 'a' suffix
62
+ logging.debug(
63
+ f"Detected alternative base pair: removed 'a' suffix from {original_name} -> {fr3d_name}"
64
+ )
65
+
66
+ # Handle backbone interactions: 0BR, 1BR, ... 9BR for base-ribose
67
+ if len(fr3d_name) == 3 and fr3d_name[1:] == "BR" and fr3d_name[0].isdigit():
68
+ try:
69
+ br_type = f"_{fr3d_name[0]}"
70
+ return ("base-ribose", BR[br_type])
71
+ except (ValueError, KeyError):
72
+ logging.debug(f"Unknown base-ribose interaction: {original_name}")
73
+ return ("other", None)
74
+
75
+ # Handle backbone interactions: 0BPh, 1BPh, ... 9BPh for base-phosphate
76
+ if len(fr3d_name) == 4 and fr3d_name[1:] == "BPh" and fr3d_name[0].isdigit():
77
+ try:
78
+ bph_type = f"_{fr3d_name[0]}"
79
+ return ("base-phosphate", BPh[bph_type])
80
+ except (ValueError, KeyError):
81
+ logging.debug(f"Unknown base-phosphate interaction: {original_name}")
82
+ return ("other", None)
83
+
84
+ # Handle the stacking notation from direct FR3D service (s33, s35, s53, s55)
85
+ if (
86
+ len(fr3d_name) == 3
87
+ and fr3d_name.startswith("s")
88
+ and fr3d_name[1] in ("3", "5")
89
+ and fr3d_name[2] in ("3", "5")
90
+ ):
91
+ if fr3d_name == "s33":
92
+ return ("stacking", StackingTopology.downward)
93
+ if fr3d_name == "s55":
94
+ return ("stacking", StackingTopology.upward)
95
+ if fr3d_name == "s35":
96
+ return ("stacking", StackingTopology.outward)
97
+ if fr3d_name == "s53":
98
+ return ("stacking", StackingTopology.inward)
99
+
100
+ # Handle the cWW style notation from direct FR3D service output
101
+ # Support both uppercase and lowercase edge names (e.g., cWW, cww, tHS, ths, tSs, etc.)
102
+ if len(fr3d_name) == 3 and fr3d_name[0].lower() in ("c", "t"):
103
+ try:
104
+ # Convert to the format expected by LeontisWesthof
105
+ edge_type = fr3d_name[0].lower() # c or t
106
+ edge1 = fr3d_name[1].upper() # W, H, S (convert to uppercase)
107
+ edge2 = fr3d_name[2].upper() # W, H, S (convert to uppercase)
108
+
109
+ lw_format = f"{edge_type}{edge1}{edge2}"
110
+ return ("base-pair", LeontisWesthof[lw_format])
111
+ except KeyError:
112
+ logging.debug(
113
+ f"Fr3d unknown interaction from service: {original_name} -> {fr3d_name}"
114
+ )
115
+ return ("other", None)
116
+
117
+ # Handle other classifications with different formatting
118
+ logging.debug(f"Fr3d unknown interaction: {fr3d_name}")
119
+ return ("other", None)
120
+
121
+
122
+ def _process_interaction_line(
123
+ line: str,
124
+ interactions_data: Dict[str, list],
125
+ ):
126
+ """
127
+ Process a single interaction line and add it to the appropriate list.
128
+
129
+ Args:
130
+ line: The tab-separated interaction line
131
+ interactions_data: Dictionary containing all interaction lists
132
+
133
+ Returns:
134
+ True if successfully processed, False otherwise
135
+ """
136
+ try:
137
+ # Split by tabs and get the first three fields
138
+ parts = line.split("\t")
139
+ if len(parts) < 3:
140
+ logging.warning(f"Invalid interaction line format: {line}")
141
+ return False
142
+
143
+ nt1 = parts[0]
144
+ interaction_type = parts[1]
145
+ nt2 = parts[2]
146
+
147
+ nt1_residue = parse_unit_id(nt1)
148
+ nt2_residue = parse_unit_id(nt2)
149
+
150
+ # Convert the interaction type to our internal format
151
+ interaction_category, classification = unify_classification(interaction_type)
152
+
153
+ # Add to the appropriate list based on the interaction category
154
+ if interaction_category == "base-pair":
155
+ interactions_data["base_pairs"].append(
156
+ BasePair(nt1_residue, nt2_residue, classification, None)
157
+ )
158
+ elif interaction_category == "stacking":
159
+ interactions_data["stackings"].append(
160
+ Stacking(nt1_residue, nt2_residue, classification)
161
+ )
162
+ elif interaction_category == "base-ribose":
163
+ interactions_data["base_ribose_interactions"].append(
164
+ BaseRibose(nt1_residue, nt2_residue, classification)
165
+ )
166
+ elif interaction_category == "base-phosphate":
167
+ interactions_data["base_phosphate_interactions"].append(
168
+ BasePhosphate(nt1_residue, nt2_residue, classification)
169
+ )
170
+ elif interaction_category == "other":
171
+ interactions_data["other_interactions"].append(
172
+ OtherInteraction(nt1_residue, nt2_residue)
173
+ )
174
+
175
+ return True
176
+ except (ValueError, IndexError) as e:
177
+ logging.warning(f"Error parsing interaction: {e}")
178
+ return False
179
+
180
+
181
+ def match_dssr_name_to_residue(
182
+ structure3d: Structure3D, nt_id: Optional[str]
183
+ ) -> Optional[Residue]:
184
+ if nt_id is not None:
185
+ nt_id = nt_id.split(":")[-1]
186
+ for residue in structure3d.residues:
187
+ if residue.full_name == nt_id:
188
+ return residue
189
+ logging.warning(f"Failed to find residue {nt_id}")
190
+ return None
191
+
192
+
193
+ def match_dssr_lw(lw: Optional[str]) -> Optional[LeontisWesthof]:
194
+ return LeontisWesthof[lw] if lw in dir(LeontisWesthof) else None
195
+
196
+
197
+ def parse_dssr_output(
198
+ file_path: str, structure3d: Structure3D, model: Optional[int] = None
199
+ ) -> BaseInteractions:
200
+ """
201
+ Parse DSSR JSON output and convert to BaseInteractions.
202
+
203
+ Args:
204
+ file_path: Path to DSSR JSON output file
205
+ structure3d: The 3D structure parsed from PDB/mmCIF
206
+ model: Model number to use (if None, use first model)
207
+
208
+ Returns:
209
+ BaseInteractions object containing the interactions found by DSSR
210
+ """
211
+ base_pairs: List[BasePair] = []
212
+ stackings: List[Stacking] = []
213
+
214
+ with open(file_path) as f:
215
+ dssr = orjson.loads(f.read())
216
+
217
+ # Handle multi-model files
218
+ if "models" in dssr:
219
+ if model is None and dssr.get("models"):
220
+ # If model is None, use the first model
221
+ dssr = dssr.get("models")[0].get("parameters", {})
222
+ else:
223
+ # Otherwise find the specified model
224
+ for result in dssr.get("models", []):
225
+ if result.get("model", None) == model:
226
+ dssr = result.get("parameters", {})
227
+ break
228
+
229
+ for pair in dssr.get("pairs", []):
230
+ nt1 = match_dssr_name_to_residue(structure3d, pair.get("nt1", None))
231
+ nt2 = match_dssr_name_to_residue(structure3d, pair.get("nt2", None))
232
+ lw = match_dssr_lw(pair.get("LW", None))
233
+
234
+ if nt1 is not None and nt2 is not None and lw is not None:
235
+ base_pairs.append(BasePair(nt1, nt2, lw, None))
236
+
237
+ for stack in dssr.get("stacks", []):
238
+ nts = [
239
+ match_dssr_name_to_residue(structure3d, nt)
240
+ for nt in stack.get("nts_long", "").split(",")
241
+ ]
242
+ for i in range(1, len(nts)):
243
+ nt1 = nts[i - 1]
244
+ nt2 = nts[i]
245
+ if nt1 is not None and nt2 is not None:
246
+ stackings.append(Stacking(nt1, nt2, None))
247
+
248
+ return BaseInteractions(base_pairs, stackings, [], [], [])
249
+
250
+
251
+ def parse_external_output(
252
+ file_path: str, tool: ExternalTool, structure3d: Structure3D
253
+ ) -> BaseInteractions:
254
+ """
255
+ Parse the output from an external tool (FR3D, DSSR, etc.) and convert it to BaseInteractions.
256
+
257
+ Args:
258
+ file_path: Path to the external tool output file
259
+ tool: The external tool that generated the output
260
+ structure3d: The 3D structure parsed from PDB/mmCIF
261
+
262
+ Returns:
263
+ BaseInteractions object containing the interactions found by the external tool
264
+ """
265
+ if tool == ExternalTool.FR3D:
266
+ return parse_fr3d_output(file_path)
267
+ elif tool == ExternalTool.DSSR:
268
+ return parse_dssr_output(file_path, structure3d)
269
+ else:
270
+ raise ValueError(f"Unsupported external tool: {tool}")
271
+
272
+
273
+ def parse_fr3d_output(file_path: str) -> BaseInteractions:
274
+ """
275
+ Parse FR3D output file and convert to BaseInteractions.
276
+
277
+ Args:
278
+ file_path: Path to a concatenated FR3D output file containing basepair, stacking,
279
+ and backbone interactions
280
+
281
+ Returns:
282
+ BaseInteractions object containing the interactions found by FR3D
283
+ """
284
+ # Initialize the interaction data dictionary
285
+ interactions_data = {
286
+ "base_pairs": [],
287
+ "stackings": [],
288
+ "base_ribose_interactions": [],
289
+ "base_phosphate_interactions": [],
290
+ "other_interactions": [],
291
+ }
292
+
293
+ # Process the concatenated file
294
+ with open(file_path, "r") as f:
295
+ for line in f:
296
+ line = line.strip()
297
+ if not line or line.startswith("#"):
298
+ continue
299
+
300
+ # Process every non-empty, non-comment line
301
+ _process_interaction_line(line, interactions_data)
302
+
303
+ # Return a BaseInteractions object with all the processed interactions
304
+ return BaseInteractions(
305
+ interactions_data["base_pairs"],
306
+ interactions_data["stackings"],
307
+ interactions_data["base_ribose_interactions"],
308
+ interactions_data["base_phosphate_interactions"],
309
+ interactions_data["other_interactions"],
310
+ )
311
+
312
+
313
+ def process_external_tool_output(
314
+ structure3d: Structure3D,
315
+ external_file_path: str,
316
+ tool: ExternalTool,
317
+ model: Optional[int] = None,
318
+ find_gaps: bool = False,
319
+ all_dot_brackets: bool = False,
320
+ ) -> Tuple[Structure2D, List[str]]:
321
+ """
322
+ Process external tool output and create a secondary structure representation.
323
+
324
+ This function can be used from other code to process external tool outputs
325
+ and get a Structure2D object with the secondary structure information.
326
+
327
+ Args:
328
+ structure3d: The 3D structure parsed from PDB/mmCIF
329
+ external_file_path: Path to the external tool output file
330
+ tool: The external tool that generated the output (FR3D, DSSR, etc.)
331
+ model: Model number to use (if None, use first model)
332
+ find_gaps: Whether to detect gaps in the structure
333
+ all_dot_brackets: Whether to return all possible dot-bracket notations
334
+
335
+ Returns:
336
+ A tuple containing the Structure2D object and a list of dot-bracket notations
337
+ """
338
+ # Parse external tool output
339
+ base_interactions = parse_external_output(external_file_path, tool, structure3d)
340
+
341
+ # Extract secondary structure using the external tool's interactions
342
+ return extract_secondary_structure_from_external(
343
+ structure3d, base_interactions, model, find_gaps, all_dot_brackets
344
+ )
345
+
346
+
347
+ def extract_secondary_structure_from_external(
348
+ tertiary_structure: Structure3D,
349
+ base_interactions: BaseInteractions,
350
+ model: Optional[int] = None,
351
+ find_gaps: bool = False,
352
+ all_dot_brackets: bool = False,
353
+ ) -> Tuple[Structure2D, List[str]]:
354
+ """
355
+ Create a secondary structure representation using interactions from an external tool.
356
+
357
+ Args:
358
+ tertiary_structure: The 3D structure parsed from PDB/mmCIF
359
+ base_interactions: Interactions parsed from external tool output
360
+ model: Model number to use (if None, use all models)
361
+ find_gaps: Whether to detect gaps in the structure
362
+ all_dot_brackets: Whether to return all possible dot-bracket notations
363
+
364
+ Returns:
365
+ A tuple containing the Structure2D object and a list of dot-bracket notations
366
+ """
367
+ mapping = Mapping2D3D(
368
+ tertiary_structure,
369
+ base_interactions.basePairs,
370
+ base_interactions.stackings,
371
+ find_gaps,
372
+ )
373
+ stems, single_strands, hairpins, loops = mapping.bpseq.elements
374
+ structure2d = Structure2D(
375
+ base_interactions,
376
+ str(mapping.bpseq),
377
+ mapping.dot_bracket,
378
+ mapping.extended_dot_bracket,
379
+ stems,
380
+ single_strands,
381
+ hairpins,
382
+ loops,
383
+ )
384
+ if all_dot_brackets:
385
+ return structure2d, mapping.all_dot_brackets
386
+ else:
387
+ return structure2d, [structure2d.dotBracket]
388
+
389
+
390
+ def write_json(path: str, structure2d: BaseInteractions):
391
+ with open(path, "wb") as f:
392
+ f.write(orjson.dumps(structure2d))
393
+
394
+
395
+ def write_csv(path: str, structure2d: Structure2D):
396
+ with open(path, "w") as f:
397
+ writer = csv.writer(f)
398
+ writer.writerow(["nt1", "nt2", "type", "classification-1", "classification-2"])
399
+ for base_pair in structure2d.baseInteractions.basePairs:
400
+ writer.writerow(
401
+ [
402
+ base_pair.nt1.full_name,
403
+ base_pair.nt2.full_name,
404
+ "base pair",
405
+ base_pair.lw.value,
406
+ (
407
+ base_pair.saenger.value or ""
408
+ if base_pair.saenger is not None
409
+ else ""
410
+ ),
411
+ ]
412
+ )
413
+ for stacking in structure2d.baseInteractions.stackings:
414
+ writer.writerow(
415
+ [
416
+ stacking.nt1.full_name,
417
+ stacking.nt2.full_name,
418
+ "stacking",
419
+ stacking.topology.value if stacking.topology is not None else "",
420
+ "",
421
+ ]
422
+ )
423
+ for base_phosphate in structure2d.baseInteractions.basePhosphateInteractions:
424
+ writer.writerow(
425
+ [
426
+ base_phosphate.nt1.full_name,
427
+ base_phosphate.nt2.full_name,
428
+ "base-phosphate interaction",
429
+ base_phosphate.bph.value if base_phosphate.bph is not None else "",
430
+ "",
431
+ ]
432
+ )
433
+ for base_ribose in structure2d.baseInteractions.baseRiboseInteractions:
434
+ writer.writerow(
435
+ [
436
+ base_ribose.nt1.full_name,
437
+ base_ribose.nt2.full_name,
438
+ "base-ribose interaction",
439
+ base_ribose.br.value if base_ribose.br is not None else "",
440
+ "",
441
+ ]
442
+ )
443
+ for other in structure2d.baseInteractions.otherInteractions:
444
+ writer.writerow(
445
+ [
446
+ other.nt1.full_name,
447
+ other.nt2.full_name,
448
+ "other interaction",
449
+ "",
450
+ "",
451
+ ]
452
+ )
453
+
454
+
455
+ def write_bpseq(path: str, bpseq: BpSeq):
456
+ with open(path, "w") as f:
457
+ f.write(str(bpseq))
458
+
459
+
460
+ def main():
461
+ parser = argparse.ArgumentParser()
462
+ parser.add_argument("input", help="Path to PDB or mmCIF file")
463
+ parser.add_argument(
464
+ "--external",
465
+ required=True,
466
+ help="Path to external tool output file (FR3D, DSSR, etc.)",
467
+ )
468
+ parser.add_argument(
469
+ "--tool",
470
+ choices=[t.value for t in ExternalTool],
471
+ required=True,
472
+ help="External tool that generated the output file",
473
+ )
474
+ parser.add_argument(
475
+ "-a",
476
+ "--all-dot-brackets",
477
+ action="store_true",
478
+ help="(optional) print all dot-brackets, not only optimal one (exclusive with -e/--extended)",
479
+ )
480
+ parser.add_argument("-b", "--bpseq", help="(optional) path to output BPSEQ file")
481
+ parser.add_argument("-c", "--csv", help="(optional) path to output CSV file")
482
+ parser.add_argument(
483
+ "-j",
484
+ "--json",
485
+ help="(optional) path to output JSON file",
486
+ )
487
+ parser.add_argument(
488
+ "-e",
489
+ "--extended",
490
+ action="store_true",
491
+ help="(optional) if set, the program will print extended secondary structure to the standard output",
492
+ )
493
+ parser.add_argument(
494
+ "-f",
495
+ "--find-gaps",
496
+ action="store_true",
497
+ help="(optional) if set, the program will detect gaps and break the PDB chain into two or more strands",
498
+ )
499
+ parser.add_argument("-d", "--dot", help="(optional) path to output DOT file")
500
+ args = parser.parse_args()
501
+
502
+ file = handle_input_file(args.input)
503
+ structure3d = read_3d_structure(file, None)
504
+
505
+ # Process external tool output and get secondary structure
506
+ structure2d, dot_brackets = process_external_tool_output(
507
+ structure3d,
508
+ args.external,
509
+ ExternalTool(args.tool),
510
+ None,
511
+ args.find_gaps,
512
+ args.all_dot_brackets,
513
+ )
514
+
515
+ if args.csv:
516
+ write_csv(args.csv, structure2d)
517
+
518
+ if args.json:
519
+ write_json(args.json, structure2d)
520
+
521
+ if args.bpseq:
522
+ write_bpseq(args.bpseq, structure2d.bpseq)
523
+
524
+ if args.extended:
525
+ print(structure2d.extendedDotBracket)
526
+ elif args.all_dot_brackets:
527
+ for dot_bracket in dot_brackets:
528
+ print(dot_bracket)
529
+ else:
530
+ print(structure2d.dotBracket)
531
+
532
+ if args.dot:
533
+ print(BpSeq.from_string(structure2d.bpseq).graphviz)
534
+
535
+
536
+ if __name__ == "__main__":
537
+ main()
rnapolis/unifier.py CHANGED
@@ -73,6 +73,7 @@ def main():
73
73
 
74
74
  structures.append((path, residues))
75
75
 
76
+ residues_to_remove = set()
76
77
  for path, residues in structures:
77
78
  ref_path, ref_residues = structures[0]
78
79
 
@@ -92,7 +93,6 @@ def main():
92
93
  sys.exit(1)
93
94
 
94
95
  # Find residues with different number of atoms
95
- residues_to_remove = set()
96
96
  for i, (residue, ref_residue) in enumerate(zip(residues, ref_residues)):
97
97
  if len(residue.atoms) != len(ref_residue.atoms):
98
98
  print(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: RNApolis
3
- Version: 0.6.1
3
+ Version: 0.7.0
4
4
  Summary: A Python library containing RNA-related bioinformatics functions and classes
5
5
  Home-page: https://github.com/tzok/rnapolis-py
6
6
  Author: Tomasz Zok
@@ -1,3 +1,4 @@
1
+ rnapolis/adapter.py,sha256=n7f5e8dbP-grJI7L9GycYAbMjpMvTuUM5aXiiCqG91k,18239
1
2
  rnapolis/aligner.py,sha256=o7rQyjAZ3n4VXcnSPY3HVB8nLNRkVbl552O3NVh0mfg,3429
2
3
  rnapolis/annotator.py,sha256=hRRzRmneYxbg2tvwVHMWLfzmJb4szV0JL_6EOC09Gwg,22101
3
4
  rnapolis/clashfinder.py,sha256=AC9_tIx7QIk57sELq_aKfU1u3UMOXbgcccQeGHhMR6c,8517
@@ -16,11 +17,11 @@ rnapolis/rfam_folder.py,sha256=SjiiyML_T1__saruFwSMJEoQ7Y55GIU8ktS8ZUn5-fw,11111
16
17
  rnapolis/tertiary.py,sha256=6t9ZB4w33-5n_M3sns1RoFXCOTgVAgGH4WDNG5OG9Kg,23426
17
18
  rnapolis/tertiary_v2.py,sha256=I1uyHWIUePNGO5m-suoL4ibtz02qAJUMvYm0BUKUygY,22480
18
19
  rnapolis/transformer.py,sha256=aC0nBmHHJf5TyLvBIV57Jj3tlwpvHbPo347opfAOlQA,3844
19
- rnapolis/unifier.py,sha256=bXscX3lxeSxT4K1fm2UEURcU9_0JA0HdTbd8ZoHZFAY,5442
20
+ rnapolis/unifier.py,sha256=DR1_IllgaAYT9_FUE6XC9B-2wgqbBHs2D1MjyZT2j2g,5438
20
21
  rnapolis/util.py,sha256=IdquFO3PV1_KDqodjupzm0Rqvgy0CeSzxGHaGEHYXVU,543
21
- rnapolis-0.6.1.dist-info/licenses/LICENSE,sha256=ZGRu12MzCgbYA-Lt8MyBlmjvPZh7xfiD5u5wBx0enq4,1066
22
- rnapolis-0.6.1.dist-info/METADATA,sha256=HA2fdc1DpxzIrn64kSPhcLGrblyyH1OTpHwApay6kvo,54537
23
- rnapolis-0.6.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
24
- rnapolis-0.6.1.dist-info/entry_points.txt,sha256=kS_Ji3_6UaomxkOaYpGHh4aZKaIh9CAfzoexbaS3y50,372
25
- rnapolis-0.6.1.dist-info/top_level.txt,sha256=LcO18koxZcWoJ21KDRRRo_tyIbmXL5z61dPitZpy8yc,9
26
- rnapolis-0.6.1.dist-info/RECORD,,
22
+ rnapolis-0.7.0.dist-info/licenses/LICENSE,sha256=ZGRu12MzCgbYA-Lt8MyBlmjvPZh7xfiD5u5wBx0enq4,1066
23
+ rnapolis-0.7.0.dist-info/METADATA,sha256=Rrnbq7pKHvcPKcHPp9nWjAZZ6x8PflF2WaJCTxaRgbo,54537
24
+ rnapolis-0.7.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
25
+ rnapolis-0.7.0.dist-info/entry_points.txt,sha256=D630mec6slaw_QMmzDNeBIy7p0pTtuOGnu8xTjmx8VA,404
26
+ rnapolis-0.7.0.dist-info/top_level.txt,sha256=LcO18koxZcWoJ21KDRRRo_tyIbmXL5z61dPitZpy8yc,9
27
+ rnapolis-0.7.0.dist-info/RECORD,,
@@ -1,4 +1,5 @@
1
1
  [console_scripts]
2
+ adapter = rnapolis.adapter:main
2
3
  aligner = rnapolis.aligner:main
3
4
  annotator = rnapolis.annotator:main
4
5
  clashfinder = rnapolis.clashfinder:main