portune 0.1.2__py3-none-any.whl → 0.1.4__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.
Potentially problematic release.
This version of portune might be problematic. Click here for more details.
- portune/portune.py +82 -38
- portune/version.py +2 -2
- {portune-0.1.2.dist-info → portune-0.1.4.dist-info}/METADATA +2 -2
- portune-0.1.4.dist-info/RECORD +9 -0
- portune-0.1.2.dist-info/RECORD +0 -9
- {portune-0.1.2.dist-info → portune-0.1.4.dist-info}/WHEEL +0 -0
- {portune-0.1.2.dist-info → portune-0.1.4.dist-info}/entry_points.txt +0 -0
- {portune-0.1.2.dist-info → portune-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {portune-0.1.2.dist-info → portune-0.1.4.dist-info}/top_level.txt +0 -0
portune/portune.py
CHANGED
|
@@ -38,7 +38,9 @@ body {
|
|
|
38
38
|
font-family: Arial, sans-serif;
|
|
39
39
|
overflow: auto;
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
.hidden {
|
|
42
|
+
display: none;
|
|
43
|
+
}
|
|
42
44
|
.icon {
|
|
43
45
|
border-radius: 25px;
|
|
44
46
|
background-color: #444;
|
|
@@ -78,7 +80,16 @@ div {
|
|
|
78
80
|
text-align: right;
|
|
79
81
|
padding-right: 8px;
|
|
80
82
|
}
|
|
81
|
-
|
|
83
|
+
.desc {
|
|
84
|
+
text-overflow: ellipsis;
|
|
85
|
+
max-width: 300px;
|
|
86
|
+
white-space: nowrap;
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
}
|
|
89
|
+
.desc:hover {
|
|
90
|
+
overflow: visible;
|
|
91
|
+
white-space: normal;
|
|
92
|
+
}
|
|
82
93
|
.table-container {
|
|
83
94
|
height: 90%;
|
|
84
95
|
overflow-y: auto;
|
|
@@ -299,6 +310,7 @@ function toggleSort(table, colIndex, sortBtn) {
|
|
|
299
310
|
}
|
|
300
311
|
|
|
301
312
|
function applyFilters(table) {
|
|
313
|
+
table.classList.add('hidden');
|
|
302
314
|
const rows = Array.from(table.querySelectorAll('tbody tr'));
|
|
303
315
|
const filters = Array.from(table.querySelectorAll('.column-filter'))
|
|
304
316
|
.map(filter => ({
|
|
@@ -313,10 +325,10 @@ function applyFilters(table) {
|
|
|
313
325
|
// First apply filters
|
|
314
326
|
const filteredRows = rows.filter(row => {
|
|
315
327
|
// If no filters are active, show all rows
|
|
316
|
-
if (filters.every(f => !f.value)) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
328
|
+
//if (filters.every(f => !f.value)) {
|
|
329
|
+
// row.classList.remove('hidden');
|
|
330
|
+
// return true;
|
|
331
|
+
//}
|
|
320
332
|
const cells = row.cells;
|
|
321
333
|
const shouldShow = !filters.some(filter => {
|
|
322
334
|
if (!filter.value) return false;
|
|
@@ -324,7 +336,11 @@ function applyFilters(table) {
|
|
|
324
336
|
if (filter.regexp) return !filter.regexp.test(cellText);
|
|
325
337
|
return !cellText.toLowerCase().includes(filter.value);
|
|
326
338
|
});
|
|
327
|
-
|
|
339
|
+
if (shouldShow) {
|
|
340
|
+
row.classList.remove('hidden');
|
|
341
|
+
} else {
|
|
342
|
+
row.classList.add('hidden');
|
|
343
|
+
}
|
|
328
344
|
return shouldShow;
|
|
329
345
|
});
|
|
330
346
|
|
|
@@ -362,6 +378,7 @@ function applyFilters(table) {
|
|
|
362
378
|
const tbody = table.querySelector('tbody');
|
|
363
379
|
filteredRows.forEach(row => tbody.appendChild(row));
|
|
364
380
|
}
|
|
381
|
+
table.classList.remove('hidden');
|
|
365
382
|
}
|
|
366
383
|
|
|
367
384
|
function processHtmlContent(element) {
|
|
@@ -600,6 +617,7 @@ def parse_input_file(filename: str) -> List[Tuple[str, List[int]]]:
|
|
|
600
617
|
A list of tuples, each containing:
|
|
601
618
|
- hostname (str): The target hostname or IP address
|
|
602
619
|
- ports (List[int]): List of ports to scan for that host
|
|
620
|
+
- desc (str): Optional description for the host
|
|
603
621
|
|
|
604
622
|
Examples:
|
|
605
623
|
Input file format:
|
|
@@ -615,19 +633,26 @@ def parse_input_file(filename: str) -> List[Tuple[str, List[int]]]:
|
|
|
615
633
|
('host3', [8080])
|
|
616
634
|
]
|
|
617
635
|
"""
|
|
618
|
-
|
|
636
|
+
host_dict = {}
|
|
619
637
|
with open(filename, 'r') as f:
|
|
620
638
|
for line in f:
|
|
621
|
-
line = line.strip()
|
|
639
|
+
line = line.strip()
|
|
622
640
|
if not line or line.startswith('#'):
|
|
623
641
|
continue
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
642
|
+
words = line.split()
|
|
643
|
+
fqdn = words[0].lower()
|
|
644
|
+
ports = words[1] if len(words) > 1 else '22'
|
|
645
|
+
port_list = [int(p) for p in ports.split(',')]
|
|
646
|
+
desc = ' '.join(words[2:]).strip() if len(words) > 2 else ''
|
|
647
|
+
if fqdn in host_dict:
|
|
648
|
+
existing_ports, existing_desc = host_dict[fqdn]
|
|
649
|
+
host_dict[fqdn] = (list(set(existing_ports + port_list)), existing_desc or desc)
|
|
650
|
+
else:
|
|
651
|
+
host_dict[fqdn] = (port_list, desc)
|
|
652
|
+
hosts = []
|
|
653
|
+
for fqdn in host_dict:
|
|
654
|
+
ports, desc = host_dict[fqdn]
|
|
655
|
+
hosts.append((fqdn, sorted(ports), desc))
|
|
631
656
|
return hosts
|
|
632
657
|
|
|
633
658
|
def ping_host(ip: str, timeout: float = 2.0) -> bool:
|
|
@@ -698,7 +723,7 @@ def resolve_and_ping_host(hostname: str, timeout: float = 2.0, noping: bool = Fa
|
|
|
698
723
|
except Exception:
|
|
699
724
|
return hostname, {'ip': 'N/A', 'ping': False}
|
|
700
725
|
|
|
701
|
-
def ping_hosts(hosts: List[Tuple[str, List[int]]],
|
|
726
|
+
def ping_hosts(hosts: List[Tuple[str, List[int], str]],
|
|
702
727
|
timeout: float = 2.0,
|
|
703
728
|
parallelism: int = 10,
|
|
704
729
|
noping: bool = False) -> Dict[str, Dict[str, Union[str, bool]]]:
|
|
@@ -739,7 +764,7 @@ def ping_hosts(hosts: List[Tuple[str, List[int]]],
|
|
|
739
764
|
with ThreadPoolExecutor(max_workers=parallelism) as executor:
|
|
740
765
|
future_to_host = {
|
|
741
766
|
executor.submit(resolve_and_ping_host, hostname, timeout, noping): hostname
|
|
742
|
-
for hostname, _ in hosts
|
|
767
|
+
for hostname, _, _ in hosts
|
|
743
768
|
}
|
|
744
769
|
|
|
745
770
|
# Add callback to update progress bar when each future completes
|
|
@@ -760,7 +785,8 @@ def ping_hosts(hosts: List[Tuple[str, List[int]]],
|
|
|
760
785
|
|
|
761
786
|
def check_port(hostname: str,
|
|
762
787
|
port: int,
|
|
763
|
-
host_info: Dict[str, Union[str, bool]],
|
|
788
|
+
host_info: Dict[str, Union[str, bool]],
|
|
789
|
+
desc: str,
|
|
764
790
|
timeout: float = 2.0) -> Tuple[str, str, int, str, bool]:
|
|
765
791
|
"""Check if a specific TCP port is accessible on a host.
|
|
766
792
|
|
|
@@ -774,6 +800,7 @@ def check_port(hostname: str,
|
|
|
774
800
|
- 'ip': IP address or 'N/A' if resolution failed
|
|
775
801
|
- 'ping': Boolean indicating ping status
|
|
776
802
|
- 'hostname': Optional resolved hostname from reverse DNS
|
|
803
|
+
desc: Optional description for the host
|
|
777
804
|
timeout: Maximum time to wait for connection in seconds
|
|
778
805
|
|
|
779
806
|
Returns:
|
|
@@ -783,6 +810,7 @@ def check_port(hostname: str,
|
|
|
783
810
|
- port: The port number that was checked
|
|
784
811
|
- status: Connection status (CONNECTED/TIMEOUT/REFUSED/UNREACHABLE)
|
|
785
812
|
- ping: Boolean indicating if the host responded to ping
|
|
813
|
+
- desc: Optional description for the host
|
|
786
814
|
|
|
787
815
|
Status meanings:
|
|
788
816
|
CONNECTED: Successfully established TCP connection
|
|
@@ -792,7 +820,7 @@ def check_port(hostname: str,
|
|
|
792
820
|
RESOLVE_FAIL: Could not resolve hostname to IP
|
|
793
821
|
"""
|
|
794
822
|
if host_info['ip'] == 'N/A':
|
|
795
|
-
return (hostname, host_info['ip'], port, 'RESOLVE_FAIL', host_info['ping'])
|
|
823
|
+
return (hostname, host_info['ip'], port, 'RESOLVE_FAIL', host_info['ping'], desc)
|
|
796
824
|
|
|
797
825
|
# Use resolved hostname if available
|
|
798
826
|
display_hostname = host_info.get('hostname', hostname)
|
|
@@ -802,16 +830,16 @@ def check_port(hostname: str,
|
|
|
802
830
|
try:
|
|
803
831
|
s.connect((host_info['ip'], port))
|
|
804
832
|
s.close()
|
|
805
|
-
return (display_hostname, host_info['ip'], port, 'CONNECTED', host_info['ping'])
|
|
833
|
+
return (display_hostname, host_info['ip'], port, 'CONNECTED', host_info['ping'], desc)
|
|
806
834
|
except ConnectionAbortedError:
|
|
807
|
-
return (display_hostname, host_info['ip'], port, 'CONNECTED', host_info['ping'])
|
|
835
|
+
return (display_hostname, host_info['ip'], port, 'CONNECTED', host_info['ping'], desc)
|
|
808
836
|
except (TimeoutError, socket.timeout):
|
|
809
|
-
return (display_hostname, host_info['ip'], port, 'TIMEOUT', host_info['ping'])
|
|
837
|
+
return (display_hostname, host_info['ip'], port, 'TIMEOUT', host_info['ping'], desc)
|
|
810
838
|
except ConnectionRefusedError:
|
|
811
|
-
return (display_hostname, host_info['ip'], port, 'REFUSED', host_info['ping'])
|
|
839
|
+
return (display_hostname, host_info['ip'], port, 'REFUSED', host_info['ping'], desc)
|
|
812
840
|
except Exception:
|
|
813
841
|
# Handle other network errors (filtered, network unreachable, etc)
|
|
814
|
-
return (display_hostname, host_info['ip'], port, 'UNREACHABLE', host_info['ping'])
|
|
842
|
+
return (display_hostname, host_info['ip'], port, 'UNREACHABLE', host_info['ping'], desc)
|
|
815
843
|
|
|
816
844
|
def send_email_report(
|
|
817
845
|
output_file: str,
|
|
@@ -979,7 +1007,7 @@ def compute_stats(
|
|
|
979
1007
|
|
|
980
1008
|
|
|
981
1009
|
# Collect VLAN statistics for timeouts
|
|
982
|
-
for hostname, ip, port, status, _ in results:
|
|
1010
|
+
for hostname, ip, port, status, _, _ in results:
|
|
983
1011
|
if ip != 'N/A':
|
|
984
1012
|
try:
|
|
985
1013
|
vlan = get_vlan_base(ip, bits)
|
|
@@ -993,7 +1021,7 @@ def compute_stats(
|
|
|
993
1021
|
# Group results by hostname for host statistics
|
|
994
1022
|
host_stats = defaultdict(lambda: {'statuses': [], 'ping': False})
|
|
995
1023
|
for result in results:
|
|
996
|
-
hostname, _, _, status, ping = result
|
|
1024
|
+
hostname, _, _, status, ping, _ = result
|
|
997
1025
|
host_stats[hostname]['statuses'].append(status)
|
|
998
1026
|
host_stats[hostname]['ping'] |= ping
|
|
999
1027
|
|
|
@@ -1020,7 +1048,7 @@ def compute_stats(
|
|
|
1020
1048
|
})
|
|
1021
1049
|
})
|
|
1022
1050
|
|
|
1023
|
-
for hostname, ip, port, status, _ in results:
|
|
1051
|
+
for hostname, ip, port, status, _, _ in results:
|
|
1024
1052
|
# For IP addresses, use VLAN/16 as domain
|
|
1025
1053
|
try:
|
|
1026
1054
|
socket.inet_aton(hostname)
|
|
@@ -1138,7 +1166,8 @@ def generate_html_report(
|
|
|
1138
1166
|
parallelism: int,
|
|
1139
1167
|
noping: bool,
|
|
1140
1168
|
stats: Dict[str, Any],
|
|
1141
|
-
input_file: str = ''
|
|
1169
|
+
input_file: str = '',
|
|
1170
|
+
desc_titles: List[str] = [],
|
|
1142
1171
|
) -> None:
|
|
1143
1172
|
"""Generate a complete HTML report of the port scan results.
|
|
1144
1173
|
|
|
@@ -1183,6 +1212,9 @@ def generate_html_report(
|
|
|
1183
1212
|
f.write(f'<h3 class="icon">Port Accessibility Report from {HOSTNAME} ({MY_IP}) to {os.path.basename(input_file)} - {time.strftime("%Y-%m-%d %H:%M:%S", scan_time)}</h3>\n')
|
|
1184
1213
|
|
|
1185
1214
|
# Write detailed results table
|
|
1215
|
+
if not desc_titles:
|
|
1216
|
+
_, _, _, _, _, desc = results[0]
|
|
1217
|
+
desc_titles = ["Description" for d in desc.split("|")]
|
|
1186
1218
|
f.write(f'''
|
|
1187
1219
|
<div class="table-container" id="result-container">
|
|
1188
1220
|
<table id="commandTable">
|
|
@@ -1194,13 +1226,14 @@ def generate_html_report(
|
|
|
1194
1226
|
<th>Port</th>
|
|
1195
1227
|
<th>Status</th>
|
|
1196
1228
|
<th>Ping</th>
|
|
1229
|
+
{"\n".join([f"<th>{d}</th>" for d in desc_titles])}
|
|
1197
1230
|
</tr>
|
|
1198
1231
|
</thead>
|
|
1199
1232
|
<tbody>
|
|
1200
1233
|
''')
|
|
1201
1234
|
|
|
1202
1235
|
# Add result rows
|
|
1203
|
-
for hostname, ip, port, status, ping in results:
|
|
1236
|
+
for hostname, ip, port, status, ping, desc in results:
|
|
1204
1237
|
ping_status = 'UP' if ping else 'N/A' if noping else 'DOWN'
|
|
1205
1238
|
ping_class = 'green' if ping else 'blue' if noping else 'red'
|
|
1206
1239
|
status_class = 'green' if status == 'CONNECTED' else 'blue' if status == 'REFUSED' else 'red'
|
|
@@ -1212,6 +1245,7 @@ def generate_html_report(
|
|
|
1212
1245
|
<td style="text-align: right;">{port}</td>
|
|
1213
1246
|
<td style="text-align: center;"><span class="{status_class} status">{escape(status)}</span></td>
|
|
1214
1247
|
<td style="text-align: center;"><span class="{ping_class} ping">{ping_status}</span></td>
|
|
1248
|
+
{"\n".join([f'<td class="desc">{escape(str(d))}</td>' for d in desc.split("|")])}
|
|
1215
1249
|
</tr>
|
|
1216
1250
|
''')
|
|
1217
1251
|
|
|
@@ -1374,7 +1408,7 @@ def format_table_output(
|
|
|
1374
1408
|
table.append(separator)
|
|
1375
1409
|
|
|
1376
1410
|
# Add data rows
|
|
1377
|
-
for hostname, ip, port, status, ping in results:
|
|
1411
|
+
for hostname, ip, port, status, ping, desc in results:
|
|
1378
1412
|
ping_status = 'UP' if ping else 'N/A' if noping else 'DOWN'
|
|
1379
1413
|
table.append(row_format.format(
|
|
1380
1414
|
str(hostname), widths['Hostname'],
|
|
@@ -1395,7 +1429,7 @@ def main():
|
|
|
1395
1429
|
parser.add_argument('-n', '--noping', action="store_true", help='No ping check')
|
|
1396
1430
|
parser.add_argument('-s', '--summary', action="store_true", help='Print scan summary information')
|
|
1397
1431
|
parser.add_argument('-b', '--bits', type=int, default=16, help='VLAN bits for timeout summary (default: 16)')
|
|
1398
|
-
|
|
1432
|
+
parser.add_argument('-d', '--desc_titles', type=str, nargs='*', help='List of custom description titles for hosts (optional)')
|
|
1399
1433
|
# Email related arguments
|
|
1400
1434
|
email_group = parser.add_argument_group('Email Options')
|
|
1401
1435
|
email_group.add_argument('--email-to', help='Comma-separated list of email recipients')
|
|
@@ -1420,14 +1454,14 @@ def main():
|
|
|
1420
1454
|
host_info = ping_hosts(hosts, args.timeout, args.parallelism, args.noping)
|
|
1421
1455
|
|
|
1422
1456
|
# Calculate total tasks and initialize progress bar
|
|
1423
|
-
total_tasks = sum(len(ports) for _, ports in hosts)
|
|
1457
|
+
total_tasks = sum(len(ports) for _, ports, _ in hosts)
|
|
1424
1458
|
print(f"Preparing to scan {len(hosts)} hosts with {total_tasks} total ports...", file=sys.stderr)
|
|
1425
1459
|
|
|
1426
1460
|
# Prepare tasks with pre-resolved data
|
|
1427
1461
|
tasks = []
|
|
1428
|
-
for hostname, ports in hosts:
|
|
1462
|
+
for hostname, ports, desc in hosts:
|
|
1429
1463
|
for port in ports:
|
|
1430
|
-
tasks.append((hostname, port, host_info[hostname]))
|
|
1464
|
+
tasks.append((hostname, port, host_info[hostname], desc))
|
|
1431
1465
|
|
|
1432
1466
|
results = []
|
|
1433
1467
|
lock = threading.Lock()
|
|
@@ -1436,8 +1470,8 @@ def main():
|
|
|
1436
1470
|
progress_bar = ProgressBar(total_tasks, prefix='Scanning')
|
|
1437
1471
|
|
|
1438
1472
|
with ThreadPoolExecutor(max_workers=args.parallelism) as executor:
|
|
1439
|
-
future_to_task = {executor.submit(check_port, hostname, port, info, args.timeout): (hostname, port, info)
|
|
1440
|
-
for hostname, port, info in tasks}
|
|
1473
|
+
future_to_task = {executor.submit(check_port, hostname, port, info, desc, args.timeout): (hostname, port, info)
|
|
1474
|
+
for hostname, port, info, desc in tasks}
|
|
1441
1475
|
|
|
1442
1476
|
for future in as_completed(future_to_task):
|
|
1443
1477
|
res = future.result()
|
|
@@ -1453,7 +1487,17 @@ def main():
|
|
|
1453
1487
|
|
|
1454
1488
|
# Generate report
|
|
1455
1489
|
results.sort(key=lambda x: (x[0], x[2]))
|
|
1456
|
-
generate_html_report(
|
|
1490
|
+
generate_html_report(
|
|
1491
|
+
results,
|
|
1492
|
+
args.output,
|
|
1493
|
+
time.localtime(start_time),
|
|
1494
|
+
args.timeout,
|
|
1495
|
+
args.parallelism,
|
|
1496
|
+
args.noping,
|
|
1497
|
+
stats,
|
|
1498
|
+
args.input_file,
|
|
1499
|
+
args.desc_titles,
|
|
1500
|
+
)
|
|
1457
1501
|
|
|
1458
1502
|
# Print summary
|
|
1459
1503
|
# Display detailed results in table format
|
portune/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: portune
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Simple Python HTTP Exec Server
|
|
5
5
|
Author: Franck Jouvanceau
|
|
6
6
|
Maintainer: Franck Jouvanceau
|
|
@@ -58,7 +58,7 @@ Multitreaded port scanner
|
|
|
58
58
|
|
|
59
59
|
# features
|
|
60
60
|
|
|
61
|
-
* parallel check of port availability against
|
|
61
|
+
* parallel check of port availability against servers/ports
|
|
62
62
|
* console output result / summary
|
|
63
63
|
* html full report / dns domain summary / vlan timeout summary
|
|
64
64
|
* mail with html summary / report attachment (mailhost relay)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
portune/__init__.py,sha256=RfXuNfHBqfRt_z4IukwN1a0oeCXahuMOO8_eBt4T8NM,58
|
|
2
|
+
portune/portune.py,sha256=Ar5GLB8hCTV9C1NbFKRKoK2nlyRReiDz5uMK5yXmb28,63495
|
|
3
|
+
portune/version.py,sha256=hcPkC9vIGgfrKK6ft7ysLT7iOCjpFmCBmyKLmXiaZ1g,511
|
|
4
|
+
portune-0.1.4.dist-info/licenses/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
|
|
5
|
+
portune-0.1.4.dist-info/METADATA,sha256=qtTM2_95cVJjfAELw9v7QP7hQijJUotJEGnC2SGq2hQ,2838
|
|
6
|
+
portune-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
7
|
+
portune-0.1.4.dist-info/entry_points.txt,sha256=6j7jAf5fOZrLPbVIs3z92R1tT891WyY9YxQ6OVnPKG0,80
|
|
8
|
+
portune-0.1.4.dist-info/top_level.txt,sha256=CITDikHhRKAsSOGmNJzD-xSp6D5iBhSr9ZS1qy8-SL0,8
|
|
9
|
+
portune-0.1.4.dist-info/RECORD,,
|
portune-0.1.2.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
portune/__init__.py,sha256=RfXuNfHBqfRt_z4IukwN1a0oeCXahuMOO8_eBt4T8NM,58
|
|
2
|
-
portune/portune.py,sha256=ODPpO7E9-pXoFRxKavyn22_b9dhVjxUp-aIxKhnxamY,61908
|
|
3
|
-
portune/version.py,sha256=bSmADqydH8nBu-J4lG8UVuR7hnU_zcwhnSav2oQ0W0A,511
|
|
4
|
-
portune-0.1.2.dist-info/licenses/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
|
|
5
|
-
portune-0.1.2.dist-info/METADATA,sha256=qEkffZiY8znnWihHuYtJlzf7t4bRPFTPvCcajLqmYBs,2837
|
|
6
|
-
portune-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
7
|
-
portune-0.1.2.dist-info/entry_points.txt,sha256=6j7jAf5fOZrLPbVIs3z92R1tT891WyY9YxQ6OVnPKG0,80
|
|
8
|
-
portune-0.1.2.dist-info/top_level.txt,sha256=CITDikHhRKAsSOGmNJzD-xSp6D5iBhSr9ZS1qy8-SL0,8
|
|
9
|
-
portune-0.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|